feat(bridge): protocol 6 — an idempotency key, and champ.boss.killed (Phase 11a)

A command carrying an `idempotencyKey` is now executed at most once: a repeat is
answered with the original reply rather than re-run. That is the precondition
every world verb in Phase 12 is waiting on, and it is what let `uo.broadcast`
stop being un-retryable.

The gate sits in BridgeBoot's inbound dispatch, not in each handler, so it covers
every kind including ones a later protocol adds. A command with no key behaves
exactly as it did before, which leaves the admin screens unchanged.

Four rules, each a decision rather than an implementation detail: reserve on
receipt (so a handler that defers is covered, answering `bridge.busy` to a repeat
in flight); a key that has begun is never released, not even when the handler
throws; a replay is stamped with the REPEAT's correlation id, because the
sidecar's reqId is fresh per call and replaying the original would hang the retry;
and the bound is loud, because an evicted key is the guarantee's one hole.

`champ.boss.killed` rides along because a bump costs a release, a bundle and an
operator update on every shard. It fires from EventSink.CreatureDeath, detected
by type so a boss that popped and died inside one sweep is still reported, and it
carries the damage table that exists at the death and nowhere else.

overlay.toml protocol = 6, in this commit rather than a later one.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-04 14:56:49 -05:00
parent 827de04471
commit 7aa7bc8032
8 changed files with 1023 additions and 7 deletions

View File

@@ -107,7 +107,17 @@ namespace Server.Custom.Bridge
/// </summary>
public static void Emit(string line)
{
if (!_running || line == null)
if (line == null)
return;
// Protocol 6. While a keyed command's handler runs — Core thread, one at a time — every
// line it emits is offered to the recent-key store so the correlated reply can be
// replayed to a retry later. Deliberately BEFORE the `_running` check: a reply the link
// was too dead to deliver is precisely the one a retry will come back for.
if (BridgeIdempotency.Capturing)
BridgeIdempotency.Observe(line);
if (!_running)
return;
// Drop-oldest. Bound first, then enqueue, so the queue can transiently sit one over