feat(bridge): publish the player-vendor market index as vendor.listing
Protocol 3.0 §8. Every player vendor's shop name, owner, location and priced inventory, so the website can offer the search the in-game Vendor Search gump offers — from outside the game, and honouring the same per-player opt-out. It cannot be an RPC. rpc.rs correlates a reply on the FIRST frame carrying a matching reqId, so a chunked reply sharing one reqId would deliver chunk 1 to the HTTP caller and leak chunks 2..N onto the broadcast feed; a whole-world snapshot would not fit in one frame inside the 10 s timeout either. So it is a diff sweep on the broadcast stream, one authoritative frame per vendor. The one genuinely new pattern here is an amortized round-robin: every other sweep walks its whole collection per tick, which is fine for tens of houses and is not fine for a world of shops whose inventories recurse into containers. MarketSweepBatch (25) vendors are inventoried per tick from a persistent cursor, so per-tick cost is bounded by the batch rather than by world size. VendorSearch.GetItemName is never called: it builds an ObjectPropertyList, serialises it and byte-parses the packet per item. The frame carries itemId, hue, amount, price, the plain item.Name field and item.LabelNumber; the website resolves names against its own cliloc table. (It would not work anyway — every current client ships its cliloc files compressed and ServUO's Ultima.StringList cannot read them, so the in-game gump has the same gap.) Measured on the live shard (27 vendors x 40 listings, 209k items / 43k mobiles): 15.4 ms for the first cold tick of 25 vendors, 3.4 ms for the next, 0.3 ms in steady state. `[bridge status` now reports lastMs/maxMs and a tick over 50 ms warns, naming the knob — the batch cap is a claim about that number and an operator tuning it was otherwise tuning blind. - location is ONE nested object, not flat map/x/y/region, so the website's single market.location visibility rule can hide a vendor's whereabouts on both the live frame and the stored read model. Flat keys would need five rules. - Owner is flat ownerSerial/ownerName, never BridgeJson.Actor, which would add acct and webId. Same argument points.board makes. - pv.VendorSearch is honoured, so a shop hidden in game is hidden on the site; the seen-set removal then emits vendor.listing.remove. - Container-priced items carry child:true, exactly as DoSearch reports them. - Over MarketMaxListings (250) the frame says truncated and carries the real total, so the site shows "250 of 3,104" rather than a partial shop as complete. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -83,6 +83,29 @@ PointsProfileEnabled=true
|
||||
# board for anyone in the top N.
|
||||
PointsProfileRank=false
|
||||
|
||||
# Player-vendor market index (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §8). Every player vendor's shop name,
|
||||
# owner, location and priced inventory, published as one vendor.listing frame per vendor so the
|
||||
# website can offer the search the in-game Vendor Search gump offers. Honours each player's own
|
||||
# in-game opt-out (the vendor's VendorSearch flag) — hide your vendor in game and it is hidden
|
||||
# on the site too.
|
||||
MarketEnabled=true
|
||||
|
||||
# Sweep interval. UNLIKE every other sweep here, a tick does NOT walk the whole world: it
|
||||
# inventories at most MarketSweepBatch vendors and a persistent cursor round-robins through the
|
||||
# rest, so the per-tick cost is bounded by the batch rather than by how many vendors exist. Full
|
||||
# coverage takes ceil(vendors / batch) x MarketSweepSeconds — 500 vendors at the defaults is one
|
||||
# complete pass every 20 minutes, and the site labels the data with how stale it may be.
|
||||
#
|
||||
# Lower this (or raise the batch) for faster coverage; both trade directly against per-tick cost,
|
||||
# and the expensive part is the item walk, which recurses into every container a vendor is selling.
|
||||
MarketSweepSeconds=60
|
||||
MarketSweepBatch=25
|
||||
|
||||
# Per-vendor listing cap, after which the frame carries "truncated": true. A commodity reseller
|
||||
# with thousands of stacked resources is a real thing, and an uncapped frame for one is measured
|
||||
# in megabytes. Clamped to 1..5000.
|
||||
MarketMaxListings=250
|
||||
|
||||
# Shard ruleset (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §5). One world.ruleset frame — expansion, which
|
||||
# systems are on, skill/stat caps, account and house limits, champion scroll rules —
|
||||
# emitted on every sidecar connect (and on [bridge reload), so the website's rules page
|
||||
|
||||
Reference in New Issue
Block a user