feat(bridge)!: Protocol 3.0 cutover — world.ruleset, points.board, vendor.listing #6
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?
What & why
Order 6 of the Protocol 3.0 plan (
v3.md§4) — theedge→maincutover, plugin side. One of four PRs that merge together; that merge is the version bump.3.0's whole point was that the bridge covered live activity well and shard content and standings almost not at all. Three new emitters close that on the plugin side:
BridgeRuleset.cs—world.ruleset(v3.md§5, #3). The shard's published ruleset: expansion, skill/stat caps, housing, PvP and the systems block. The first bridge stream that is neither an event subscription nor a sweep — it ridesBridgeLink.Connected_Core, likeserver.hello, because shard config only changes when an operator edits a file. Where a system's on/off state is derived rather than configured (shadowguard,factions) it reads the system's own static instead of inventing a.cfgkey that doesn't exist.BridgePoints.cs—points.board(v3.md§7, #4). The loyalty/points leaderboards, and the widest read the bridge performs: ten of ServUO's ~25 point systems keep a row for every character ever created, so it selects the top N in a single bounded pass rather than sorting, on a deliberately slow 300 s interval.BridgeMarket.cs—vendor.listing/vendor.listing.remove(v3.md§8, #5). The shard-wide player-vendor index, and the one sweep shape the bridge did not previously have: an amortized round-robin. Every other sweep walks its whole collection per tick, which is fine for tens of houses and not fine for a world of shops whose inventories recurse into containers, so it inventories at mostMarketSweepBatchvendors per tick from a persistent cursor — per-tick cost bounded by the batch, not by world size. It is also the first stream to honour a per-player privacy toggle: ServUO's ownPlayerVendor.VendorSearch, so a shop hidden in game is hidden on the site.How it was tested
Each phase was verified on the live ServUO tree before it landed on
edge(see the individual PRs). The plugin also compiles standalone, contrary to "no standalone build": Roslyn over the wholeScriptstree withoverlay/Scripts/Custom/Bridge/*.cssubstituted, 6,205 files, which catches every signature error a boot would.Measured on a shard of 209k items / 43k mobiles: a cold market tick of 25 vendors × 40 listings is 15.4 ms, steady state 0.3 ms;
[bridge statusreportslastMs/maxMsand warns past 50 ms. End-to-end, 27 real vendors / 1,040 listings swept off the tree, through the sidecar, onto the site.Merge order
Merge with the other three cutover PRs — link #21, website #118, docs #73. Nothing in this repo carries the protocol version, so this half is version-agnostic — but the shard emits kinds a v2 sidecar has no table for, so it should not go out ahead of link.
Checklist
AI-assisted contributions (required)
Claude Code. I have reviewed and understandevery change, and take responsibility for it. AI-authored commits are
marked with a
Co-Authored-By/Assisted-Bytrailer.License
(GNU GPL v3.0 or later), and I have the right to contribute it.
Protocol 3.0 §7 (docs/link/v3.md). ServUO carries ~25 separate point currencies — Queen's Loyalty, Void Pool, Casino, Clean Up Britannia, the nine city loyalties, the Doom/Khaldun/Kotl treasure systems — every one a standing players build over months, and none of them visible outside an in-game gump until now. BridgePoints.cs - A diff sweep shaped like BridgeHousing: ServerStarted arms the timer, a sidecar connect clears the diff state so a fresh sidecar gets every board, and each pass emits only the systems whose top N or participant count moved. One ~600 B frame per system rather than one 12 KB frame, matching champ.update / guild.update. No points.remove — the system set is fixed at startup by PointsSystem.Configure, the same argument city.update makes. - Selection is a single bounded pass into a fixed N-element array kept sorted by insertion, NOT OrderByDescending().Take(N). PlayerTable is a plain List and ten of the ~25 systems have AutoAdd = true, so they hold a row for every character ever created: the naive version is ~25 full sorts on the Core thread, which BRIDGE_PLUGIN_PLAN.md §1 measured as the second thing in the bridge capable of blowing a frame budget. - Which systems publish defaults to the shard's OWN answer — ShowOnLoyaltyGump — rather than a list here that would drift; Bridge.cfg PointsSystems= overrides it, and an unrecognised name is logged rather than dropped. - Entries are written inline as {serial, name}, never via BridgeJson.Actor. A board is the widest-audience surface the bridge has, so acct/webId deliberately do not cross the wire; the site resolves serial → user from its own link mirror. char.profile gains a points block, the titles precedent from PROTOCOL_2.md §10.3 - Never uses PointsSystem.GetEntry/GetPoints: both MUTATE THE WORLD, since GetEntry(create: false) still calls AddEntry when the system has AutoAdd (PointsSystem.cs:207). Using them would have appended up to ten rows to the points save file every time anyone opened a character sheet. Hand-rolled read-only scan instead. - rank is off by default (PointsProfileRank). A points lookup stops at the character's own row; a rank must count every row that beats them, in every system, on every profile build. Verified by running it, not by reading it: the whole Scripts tree (6,207 files) compiles clean against real ServUO 57.4 assemblies, and a boot against the local shard with a 43,011-mobile world emitted five live boards. That run caught a bug no fake shard could — ServUO's uncapped idiom is MaxPoints = double.MaxValue, and (long) on it is an UNCHECKED conversion yielding long.MinValue, so the first sweep published "maxPoints": -9223372036854775808 for three of the five boards. Cap()/Score() now normalise anything unrepresentable, and maxPoints: 0 is the documented "uncapped" value — which on a real shard is the common case, not an edge case. Re-verified after the fix: 0 for the uncapped systems, 15000 and 10000 for the two that genuinely cap. Co-Authored-By: Claude <noreply@anthropic.com>