docs(link): protocol 6 part b -- leases, participation, and what the walk found

Six files. `v6.md` gains sections 7-10: one version with two halves, the lease
plane, the participation ledger, and 11b's verification.

Three corrections to what the plan and the design of record assumed.

Phase 11b is FIVE repos, not four, and in the opposite direction from 11a's
correction: a lease's ledger row had no reconcile path anywhere, because the step
that made it names `core.lease` and that is core's own action. `website` joins.

EVENTS.md's §D frames the 258 `Config.Get` call sites as splitting into two
patterns. Measured on 57.4: of the 158 non-Bridge sites in `Scripts/`, roughly
eight are read live. The allowlist is not a curated subset of a large pool, it is
nearly the whole of what exists. And `Config.Set` has exactly one caller in the
entire tree, so on a stock shard a GM cannot drift a configuration lease even
deliberately -- which is why proving `drifted` needed a scaffolding verb.

§G's "participation attribution is now the largest piece of new UO work" closes,
and the live-config-lease row goes to built-with-one-key.

§10.1 records the defect the phase's own deferral found in 11a's shipped code:
`bridge.busy` answered 200 instead of 425 because the frame carried two `kind`
fields and parsers take the last. Unreachable in 11a by construction; produced on
the first collision here.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-04 19:32:11 -05:00
parent 1521c5af7e
commit eabaf2635f
6 changed files with 546 additions and 23 deletions

View File

@@ -317,6 +317,41 @@ kind including ones a later protocol adds. A command with no key behaves exactly
One new outbound kind comes with it, **`bridge.busy`**: a repeat arrived while the original is still
in flight, nothing ran, come back.
### The event plane, and the Bridge's first persisted state (Protocol 6)
```jsonc
{"kind":"lease.apply","key":"PlayerCaps.SkillCap","value":"1200","holdMs":600000,"runId":"77"}
{"kind":"participation.open","runId":"99","map":"Felucca","x":1496,"y":1628,"radius":40}
```
Six commands behind one new gate, `Bridge.EventsEnabled`, default off and deliberately not the admin
write plane's switch. Two facts about ServUO shaped both halves and are worth recording here rather
than only in the spec.
**`Server/Config.cs` is a real runtime store, and almost nothing reads it live.** `Config.Set`
mutates the in-memory entry table and `Config.Load()` is guarded by `_Initialized`, so a Set survives
every later Get — but of the **158** non-Bridge `Config.Get` call sites in `Scripts/`, roughly
**eight** are read at the call site. The rest are cached at type initialisation, where a lease
applies cleanly and does nothing at all. That is why the lease catalog is a verified allowlist and
never "any config key".
Two consequences, both deliberate: **nothing calls `Config.Save()`**, so a lease never reaches disk
and a shard restart is a free restore; and **`Config.Set` has exactly one caller in the whole tree**
(`Server/ScriptCompiler.cs`), so on a stock shard no GM can drift a configuration lease even
deliberately — the compare-and-set is still required for Phase 12's object-property leases, and
proving it needs the scaffolding driver.
**The participation ledger is the first thing this plugin has ever persisted.** A run spans hours and
a restart mid-event is ordinary, so an in-memory tally would silently regress every attendee's score.
`Server.Persistence` plus `EventSink.WorldSave` writes `Saves/Bridge/Participation.bin` beside the
world save — no persistence *item*, so no world object and nothing for a GM to delete by accident.
The hooks attach in `Configure()`, because `EventSink.WorldLoad` fires inside `World.Load()` and
`Initialize()` is too late.
**And the first handler that defers.** `participation.snapshot` walks a large run's members across
Core ticks rather than in one inbound call, which makes it the first handler to complete after
`OnInboundLine` returned — and therefore the first that can actually produce `bridge.busy`.
### `server.hello` is per-connection, not per-boot
The sidecar restarts independently of the shard, so anything it needs up front must be re-sent on **every** connect. An earlier draft emitted `server.started` once at `EventSink.ServerStarted`; a sidecar that came up second never received it and had no idea which shard it was attached to.