feat(bridge): protocol 5 — cutover 2a of 7 (edge → main)
#20
Reference in New Issue
Block a user
No description provided.
Delete Branch "edge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Engagement Phase 13, step 2 of 7. Merge with
link's cutover PR, in the same window — the emitter,overlay.tomland the sidecar'sPROTOCOL_VERSIONare one protocol bump with three declaration sites, and CI publishes the paired bundle from the two releases these produce.Merge
docs(step 1) first.The three declaration sites, verified as agreeing
servuo-plugins/overlay.tomlprotocol = 5← this PRlink/sidecar/src/main.rsPROTOCOL_VERSION5← link's cutover PRuo_link_config.protocoldefault5←module-uo, step 4The installer refuses to pair a sidecar and an overlay that disagree, so these two land together or a shard cannot be set up.
What lands (4 commits)
BridgeEvents.cs—house.decaygainsownerNameand a decayschedule; a newaccount.login.resultkind, which exists becauseEventSink.AccountLoginis a pre-decision veto hook and fires on successful logins too. A trigger on it would have mailed "someone tried to get into your account" every time the player logged in.BridgeMarket.cs/BridgeSweeps.cs—vendor.listinggainsownerAcctand afeesblock. Nothing anywhere on the wire carried a vendor's held gold or charge before this.fix/vendor-fee-signature— the market sweep never diffed the newfeesblock, so a vendor running out of gold was invisible to it. Found by Phase 11b's live walk.tools/scaffolding/— the protocol-5 probe and rig driver. Never deployed.Not in scope, deliberately
uo.vendor.salestays in the opt-in patch tier. It is real and carriesownerAcct, but it needs aPlayerVendorSaleEventSink the patches add to core, so moving it intooverlay/would mean shipping a core patch as though it were an overlay file. The consequence is documented rather than fixed: on a shard that declined the tier, the trigger is silently dormant.All three enrichments were proved on the live rig — real ServUO 57.4 against the release sidecar.
Three emitter changes and the overlay's protocol declaration, in one PR because "The bridge is a contract": overlay.toml must be bumped in the same change as the emitters or the next bundle silently fails to compose. BridgeSweeps — house.decay gains ownerName and a nested `schedule` {dynamicDecay, nextStage, decayPeriodSec, estimatedCollapse}. estimatedCollapse is emitted only where ServUO can actually know it. Dynamic decay (Core.ML) draws each stage's duration at RANDOM when the stage is entered, so NextDecayStage is exact for the next transition and nothing beyond it is known — collapse becomes exact only at IDOC, where the next transition IS the collapse. Static decay is a pure function of LastRefreshed and DecayPeriod, so it is exact at every stage. Emitting it anywhere else would publish a guess as a fact, and on the website's side that becomes a dated promise in someone's mail. BridgeMarket — vendor.listing gains ownerAcct and a nested `fees` block. ownerAcct is the one that matters structurally: the frame has carried ownerName since v3, but a character name joins to nothing — only the game account is the website's link key. The fees block resolves PlayerVendor.PayTimer's dismissal rule (pay > totalGold => Destroy) on the shard, because both halves of that comparison differ between ServUO's two vendor systems and re-deriving them downstream would be a second implementation of a rule that lives in core. No daysRemaining: under the old vendor system a pay period is a UO day (Clock.MinutesPerUODay, about two real hours), so the obvious name would be wrong by a factor of twelve on exactly the shards least likely to notice. periodsRemaining plus the interval, and dismissalAt as an instant. A commission vendor has no pay timer at all and reports exempt with no schedule — "never dismissed" is not the same as "dismissed in 400 days". BridgeEvents — a new account.login.result kind. EventSink.AccountLogin is a veto hook that fires BEFORE the auth decision, and AccountLoginEventArgs constructs with Accepted = true, so the existing account.login.attempt fires on successful logins too and cannot carry a verdict. A security rule built on it would have mailed "someone tried to get into your account" every time the player logged in. The verdict is read one Core slice later via DelayCall(Zero). That needs no core patch AND does not depend on handler subscription order, which ServUO does not define and a shard's own scripts can change. reason is omitted on an accept, because ALRReason's zero value is Invalid and would read as a failure reason. The address is resolved inside the handler, since AccountLogin_ReplyRej disposes the NetState before the deferred read runs. The password is never read, logged or emitted. tools/scaffolding/BridgeProtocol5Probe.cs drives all three on a live shard, and the README records the two traps it took to get there — both of which produce SILENCE rather than an error, so each looks exactly like a broken emitter: * An in-process login probe can never produce accepted:true. AccountHandler calls acct.HasAccess(e.State) BEFORE it checks the password, and a null NetState fails that. Only a real socket proves the accepted half — and it is the better test anyway, since it also produces the real ip. * Forcing a decay stage on a house that cannot decay emits nothing at all. Only Condemned and ManualRefresh houses decay; an AutoRefresh one — and the owner's NEWEST house is always AutoRefresh — has a DecayLevel getter that calls ResetDynamicDecay() and reports Ageless, wiping the forced stage before the sweep reads it. Verified on the local rig against the release sidecar: a house walked Fairly -> Greatly -> IDOC carried estimatedCollapse on the IDOC frame and only there; every vendor's periodsRemaining matched funds/chargePerPeriod, including one at 0 whose dismissalAt equals its next tick; a real socket login gave accepted:false reason:BadPass and then accepted:true. Compiles clean against ServUO 57.4 reference assemblies. Docs: RunicGateway/docs link/v5.md. Co-Authored-By: Claude <noreply@anthropic.com>