fix(bridge): make the market sweep notice a vendor running out of gold
`BridgeMarket.Signature()` diffs shop name, owner, map, coordinates and the item/price list -- the things a LISTING is made of. Protocol 5 added a `fees` block to the frame and the change detector never learned about it. So a vendor quietly running down its gold altered nothing the sweep compared, emitted no frame, and `uo.vendor.expiring` -- the notification whose entire subject is a vendor running out of gold -- could fire only by coincidence: when somebody happened to reprice an item on a shop that was already broke. Proved on the engagement Phase 11b live rig by setting a vendor's held gold to zero and watching no frame follow. The signature carries the DERIVED values, `exempt` and `periodsRemaining`, not the raw ones. An integer division moves only when the shard's own answer to "is this vendor in danger" moves; `HoldGold` changes on every sale and `NextPayTime` on every tick, and keying on either would re-emit a fat listing frame for a shop whose listings had not changed. Emit CADENCE, not frame shape: no field added, PROTOCOL_VERSION untouched, and `overlay.toml` unchanged. The general form is worth carrying forward -- a sweep-based kind has a change detector, and a field added to the frame but not to the detector ships correct and arrives never. Also adds `tools/scaffolding/BridgeRigDriver.cs`: the shard driven from outside the game over a polled command file. A walk asserts what happened BETWEEN two steps, so the steps have to be separated by the observer rather than by a hard-coded delay -- and ServUO's console takes a fixed verb set, so `[p5probe` cannot be typed at a headless shard at all. Never deployed; `deploy.ps1` copies only `overlay/`. The README gains the two ServUO facts the walk cost a rebuild each to learn: a condemned house cannot be refreshed, and only a clean shutdown emits. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ These two scripts produced the measured budget in [PLAN.md](https://gitea.whitlo
|
||||
| `BridgeCrierProbe.cs` | `Scripts/Custom/BridgeCrierProbe.cs` | Logs the global town-crier entry list every 3s so `towncrier.add` / `remove` can be seen landing in game state. Flag: `CrierProbeOnStart`. |
|
||||
| `BridgeVendorSaleProbe.cs` | `Scripts/Custom/BridgeVendorSaleProbe.cs` | Fires `PlayerVendorSale` (Phase 7) with real seeded-vendor data so `vendor.sale` can be verified without a live buy. Requires the Phase 7 patches applied. Flag: `VendorSaleProbeOnStart`. |
|
||||
| `BridgeDemoDress.cs` | `Scripts/Custom/BridgeDemoDress.cs` | Renames a seeded world so it is presentable in a screenshot: shop signs, vendor and character names, house signs. Also stages a few condemned houses back into IDOC, and sets a known password on `seed_000` so a character can be logged in. Flags: `DemoDressOnStart`, `DemoDressPassword`. In game: `[demodress`. |
|
||||
| `BridgeRigDriver.cs` | `Scripts/Custom/BridgeRigDriver.cs` | Drives the shard from OUTSIDE the game, one verb per line in `Config/rigcmd.txt`, which the driver polls and truncates. Written for the engagement Phase 11b acceptance walk, where each step's assertion is what happened BETWEEN two steps, so the steps have to be separated by the observer rather than by a hard-coded delay -- and ServUO's console takes a fixed verb set (`Scripts/Misc/ConsoleCommands.cs`), so `[p5probe` cannot be typed at a headless shard at all. Verbs: `decaylist`, `decay`, `vendorlist`, `vendorfunds`, `citylist`, `governor`, `election`, `activate`, `password`, `save`, `shutdown`. Flag: `RigDriverEnabled`. **Sets passwords and mutates the world.** |
|
||||
| `BridgeProtocol5Probe.cs` | `Scripts/Custom/BridgeProtocol5Probe.cs` | Drives all three Protocol 5 enrichments so their frames can be observed: walks one house Fairly -> Greatly -> IDOC (the PAIR is the assertion -- `estimatedCollapse` must appear only on the IDOC frame), reports each player vendor's fee state straight off the `PlayerVendor` so the emitted `fees` block can be checked against the shard's own numbers, and fires `EventSink.AccountLogin`. Flags: `Protocol5ProbeOnStart`, `Protocol5ProbeAccount`, `Protocol5ProbePassword`. In game: `[p5probe`. **Sets a password on the named account.** |
|
||||
|
||||
## Deploy overwrites Bridge.cfg
|
||||
@@ -139,3 +140,33 @@ reports `Ageless`, so a forced `SetDynamicDecay` is wiped on the very next read,
|
||||
change, and **nothing is emitted at all**. That looks exactly like a broken emitter. Filter on
|
||||
`house.CanDecay`, and expect a seeded world to have only one or two houses that qualify -- both
|
||||
probably already at IDOC, so the walk has to put one back down first.
|
||||
|
||||
## A decaying house cannot be refreshed — only its owner coming back rescues it
|
||||
|
||||
`BaseHouse.RefreshDecay()` returns `false` immediately when `DecayType == Condemned`, and on a
|
||||
seeded world **every house that can decay is Condemned** — the seeder backdates 18 accounts past
|
||||
`Account.InactiveDuration` precisely to make them decay. So `SetDynamicDecay(DecayLevel.LikeNew)`
|
||||
is wiped by the next read and `RefreshDecay()` does nothing: the sweep sees no change and emits
|
||||
nothing, which looks exactly like a broken emitter for the second time on the same page.
|
||||
|
||||
The rescue is the OWNER LOGGING IN (`BridgeRigDriver`'s `activate <account>` reproduces it by
|
||||
setting `LastLogin`). What the shard then reports depends on how many houses that owner has:
|
||||
|
||||
| the house | `DecayType` after the login | `DecayLevel` reads |
|
||||
|---|---|---|
|
||||
| their newest | `AutoRefresh` | **`Ageless`** — off the decay clock entirely |
|
||||
| any older one | `ManualRefresh` | **`LikeNew`** — back on the clock, at the top |
|
||||
|
||||
Both are "out of danger", and the newest-house case is the common one. A consumer that watches only
|
||||
for `LikeNew` misses most rescues — which is what the engagement mapper did until this walk.
|
||||
|
||||
## The console takes a fixed verb set, so `[commands` cannot be typed at a headless shard
|
||||
|
||||
`Scripts/Misc/ConsoleCommands.cs` handles `save`, `shutdown`, `restart`, `online`, `kick` and a
|
||||
handful more; it does **not** dispatch arbitrary `[commands`. Every other probe here therefore runs
|
||||
either at boot or from an in-game client, and neither works for a walk driven from a script. That is
|
||||
what `BridgeRigDriver` and its `rigcmd.txt` are for.
|
||||
|
||||
Also: only a CLEAN shutdown emits. `Stop-Process` drops the socket and the shard says nothing, so a
|
||||
killed shard is indistinguishable from a wedged one and `server.shutdown` never reaches the sidecar —
|
||||
use the driver's `shutdown` verb (`Core.Kill`) when the shutdown itself is what is being tested.
|
||||
|
||||
Reference in New Issue
Block a user