# Test scaffolding **Not part of the bridge. Never deployed.** `deploy.ps1` only copies `overlay/`, so nothing here reaches a server unless you put it there by hand. These two scripts produced the measured budget in [PLAN.md](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md) §1. They are kept because those numbers should be reproducible, and because re-running the probe is the only honest way to check whether a change to the plugin's read path got more expensive. | File | Server path when testing | What | |------|--------------------------|------| | `BridgeSeeder.cs` | `Scripts/Custom/BridgeSeeder.cs` | Populates a synthetic world: 50 accounts, 150 characters, 30 houses, 30 player vendors with 40 listings each. | | `BridgeProbe.cs` | `Scripts/Custom/BridgeProbe.cs` | Times every read the plugin performs, on the Core thread. Read-only. | | `BridgeEventProbe.cs` | `Scripts/Custom/BridgeEventProbe.cs` | Fires gold/fame/karma/save events through their real code paths so the emit path can be verified without a game client. **Mutates the world and saves.** Flag: `EventProbeOnStart`. | | `BridgeSweepProbe.cs` | `Scripts/Custom/BridgeSweepProbe.cs` | Bumps one seeded house's decay stage after baseline so the decay sweep's transition detection can be observed without waiting a real IDOC stage. Flag: `SweepProbeOnStart`. Pair with short `*SweepSeconds` overrides. | | `BridgeLinkProbe.cs` | `Scripts/Custom/BridgeLinkProbe.cs` | Triggers `[link` for seed_001 without a client, then saves so the `WebsiteUserId` tag reaches `accounts.xml`. Flag: `LinkProbeOnStart`. Pair with a sidecar that reads the code and sends `link.confirm`. | | `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`. | ## Deploy overwrites Bridge.cfg `deploy.ps1` copies `overlay/Config/Bridge.cfg`, which deliberately omits the scaffolding flags. So **every deploy strips `SeedOnStart` / `EventProbeOnStart` / etc.** Re-append the flag you need after deploying, or the probe silently does nothing on the next boot. (This bit once during Phase 2 testing.) ## Using them Copy both into `Scripts/Custom/`, then append the flags to `Config/Bridge.cfg`: ```ini SeedOnStart=True CensusOnStart=False ProbeOnStart=False ``` Boot once to seed and save, then set `SeedOnStart=False`. `CensusOnStart` reports what the loaded world actually contains; `ProbeOnStart` prints timings two seconds after `ServerStarted`. Because `Config.Get` returns `false` for a missing key, a server whose `Bridge.cfg` lacks these keys never runs the scaffolding — even if the `.cs` files are sitting in `Scripts/Custom/`. That is the safety net, not an excuse to ship them. In-game, `[seedworld` and `[unseedworld` (Administrator) do the same work on a live shard. ## Dressing a seeded world for screenshots `BridgeSeeder` builds a world at realistic **scale**, which is all the bridge ever needed. It does not build one that looks like anything: a vendor is `seed vendor` trading as `Seed Shop 810`, a character is `Seed004A`, a house sign says `Seed House 12`. Those strings travel the whole bridge and land on the marketplace, the guild roster and the housing pages of the website — fine for a protocol test, wrong for a screenshot. `BridgeDemoDress.cs` renames them in place. It seeds nothing: prices, listing counts, decay stages, fame and skills stay exactly as the seeder left them and as the shard has moved them since, so the data keeps its provenance and only the strings a human reads change. Names are drawn from fixed tables by a hash of each object's serial, so a re-run reproduces the same world, and shop and house names are re-dressed when they are names the pass itself produced — so a change to the tables can be applied to a world that has already been through here. ```ini DemoDressOnStart=True DemoDressPassword= ``` Boot once, then set `DemoDressOnStart=False`. The password is written to `seed_000` so a real client can log a character in — the only way to make the website's online roster non-empty — and it is read from the config rather than compiled in, so it never lands in source control. **It dresses seeded objects only, which means your own characters keep their names.** That is the right behaviour for a test shard and a thing to remember before pointing a camera at one: a dev world usually also holds the accounts, characters, guilds and houses of whoever built it, and those are real identifiers on a page that may end up public. **The sidecar's board is cached, so the website lags a rename.** A shop name reaches the site on the next market sweep, and a sweep advances `MarketSweepBatch` vendors per tick — 27 vendors at the defaults is two ticks. Allow a couple of minutes before concluding that a rename failed. This cost a debugging detour once: the shard had the new names all along and the sidecar was still serving the previous ones. ## Back up `Saves/` first `[seedworld` and `SeedOnStart` **write to the live world**. Copy `Saves/` somewhere outside the repo before running either. `Backups/Automatic` is rotated by `AutoSave.cs` and `Backups/Temp` is deleted outright, so neither is a safe destination. `[unseedworld` deletes every `seed_*` account, which takes their characters and houses with it — but not necessarily their `PlayerVendor` mobiles. Restoring a backup is the reliable reset. ## What the seeder had to work around Worth knowing before you trust its output: - **Plate needs strength.** `BaseArmor.CanEquip` rejects when `from.Str < strReq` (`PlateChest` needs 95). A rejected `EquipItem` leaves the item parentless, and the `Cleanup` pass later deletes it en masse. The seeder gives characters `Str` 100–125 and deletes any item whose equip is refused, rather than orphaning it. - **`VendorItem.Price` is get-only** and `PlayerVendor.SetVendorItem` is private. Dropping an item into a vendor's pack fires `OnSubItemAdded`, which registers the item at the default price of 999. The seeder reaches `SetVendorItem` by reflection to set a real price. Acceptable in throwaway scaffolding; do not do this in the plugin. - **Houses only decay when condemned.** `BaseHouse.CanDecay` is true only for `DecayType.Condemned` or `ManualRefresh`. An active owner's newest house is `AutoRefresh` and never decays. The seeder backdates 18 accounts past `Account.InactiveDuration` (180 days) to condemn them, then forces stages with `SetDynamicDecay` — not by backdating `LastRefreshed`, because `DynamicDecay.Enabled` is true on this expansion and `GetOldDecayLevel` is unreachable. ## Reference output Census after a fresh load of the seeded world: ``` [BridgeSeeder] houses=35 [BridgeSeeder] decay Ageless 13, Slightly 3, Somewhat 7, Fairly 3, Greatly 3, IDOC 6 [BridgeSeeder] seeded chars=150 avgEquipped=8.00 naked=0 [BridgeSeeder] playervendors=30 ``` Probe, best-of-20 on the Core thread: ``` [BridgeProbe] char.profile 0.069 ms/char 2386 bytes json [BridgeProbe] vitals sweep 0.223 ms for 150 chars (0.0015 ms/char) [BridgeProbe] decay sweep 0.007 ms for 35 houses (0.0002 ms/house) [BridgeProbe] economy sweep 0.001 ms for 51 accounts (supply 110,478,209 gold) [BridgeProbe] vendor snap 0.343 ms for 30 vendors (1200 listings) ``` Seeded characters carry 8 items with ~6 mods each and ~12 trained skills. A real endgame character has more of both, so profile cost and payload are a **floor** — budget 2–4× for a fully-kitted character.