colby afc97c414e Phase 3: polled sweeps (vitals, house decay, economy)
BridgeSweeps runs three repeating Core-thread timers for the state that has no
EventSink. Cost measured in Phase 1 is why they can run on the main thread: a
full pass of all three is well under a millisecond at the seeded scale.

  - Vitals: online players only. Small and volatile; the sidecar diffs snapshots.
    Offline characters do not move, so they are served on demand as full profiles
    instead, not swept.
  - House decay: emits only on a level transition. A silent baseline on
    ServerStarted records every house's current stage, so a restart does not
    re-announce them. Payload carries from/to, coords, nested ban location,
    region, sign name, owner serial+account, and built/refreshed timestamps, all
    null-guarded.
  - Economy supply: periodic sum of every account's currency as a snapshot. The
    level; AccountGoldChange and the vendor events are the flow.

All three re-arm on `[bridge reload`; `[bridge sweepnow` runs one of each on
demand; `[bridge status` reports sweep counters. Sweeps skip emitting while the
sidecar is disconnected, since their state is perishable and re-emitted next
tick anyway (unlike events, which queue through an outage).

Verified on the seeded world with 8s intervals: baseline recorded 29 houses
silently, a probe bumped one Somewhat->Fairly, and the next sweep emitted exactly
one house.decay and none for the other 28. Economy emitted a supply snapshot per
interval. Vitals emitted nothing, correctly, since all seeded characters are
offline. Evidence in docs/PLAN.md §13.

Adds tools/scaffolding/BridgeSweepProbe.cs (never deployed) to force a decay
transition on demand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:59:21 -05:00

uo-link

ServUO ⇄ Rust sidecar bridge. The shard emits newline-delimited JSON over a loopback TCP socket; the sidecar owns the WebSocket the website consumes.

ServUO plugin (C#, net48)  ──loopback TCP, newline-JSON──►  Rust sidecar  ──WebSocket/JSON──►  website
      (Core-thread reads)  ◄──inbound commands─────────────┘  (owns WS, auth, buffering, fan-out)

The shard never speaks WebSocket. Every world read happens on the Core thread; the socket is touched only by a dedicated writer thread draining a bounded queue.

Layout

Path What
overlay/ Mirrors the ServUO server root. Everything here — and only this — copies over an install.
patches/ Unified diffs against stock ServUO for files we must modify rather than add.
tools/ Never deployed. Test scaffolding and anything else that must not reach a server.
docs/PLAN.md Implementation plan, measured performance budget, and the full data catalog.
docs/RESEARCH.md Original source-level research. Partly superseded — see the corrections table in PLAN.md §8.
docs/SHARD_PREREQS.md Repairs the target shard needed before any of this could load.
deploy.ps1 Copies overlay/ into a server root. -Verify diffs instead of writing.

Anything under overlay/ is authoritative. Do not edit files in the server tree directly — edit here and deploy.

Deploy

.\deploy.ps1 -ServerPath C:\Users\colby\Desktop\servuo -Verify   # show what would change
.\deploy.ps1 -ServerPath C:\Users\colby\Desktop\servuo           # write

Status

Phase State
0 — build fix (Scripts.csproj) done, verified end-to-end
1 — transport (BridgeLink) done, acceptance in docs/PLAN.md §11
2 — event streams (BridgeEvents) done, acceptance in docs/PLAN.md §12
3 — sweeps (BridgeSweeps) done, acceptance in docs/PLAN.md §13
4 — request/response not started
5 — [link account linking not started
6 — town-crier inbound not started
7 — PlayerVendorSale core event not started
8 — cheat signals not started

Phase 0 — what it fixes

ScriptCompiler.Compile() runs dotnet build Scripts/Scripts.csproj -c Release, prints the output, and never checks the exit code, then Assembly.LoadFrom("Scripts.dll") and returns true. Because that build passed no Platform, MSBuild defaulted to AnyCPU, and Scripts.csproj gated both OutputPath and DefineConstants on Configuration|Platform == Release|x64. So:

  • the DLL landed in Scripts/bin/Release/ while the core loads Scripts.dll from the base directory, and
  • TRACE;NEWTIMERS;ServUO went undefined, so XmlSpawner compiled its non-ServUO branches.

Runtime script compilation therefore had no effect, silently. overlay/Scripts/Scripts.csproj conditions both property groups on Configuration alone.

Server.csproj is deliberately left alone: nothing under Server/ uses those symbols, and giving it OutputPath=..\ would make the boot-time build try to overwrite the running ServUO.exe.

The plugin (Phase 1)

overlay/Scripts/Custom/Bridge/:

File Responsibility
BridgeConfig.cs Reads Config/Bridge.cfg in Configure(), before World.Load.
BridgeJson.cs Outbound JSON by hand (Core thread, so no reflection serializer). Inbound via JavaScriptSerializer.
BridgeLink.cs The socket. Link thread owns it; a bounded drop-oldest queue fronts it; a reader thread marshals inbound lines to the Core thread.
BridgeBoot.cs Lifecycle, inbound dispatch, [bridge status|reload|ping].
BridgeEvents.cs EventSink subscriptions (Phase 2). Read-only, player-filtered, never emits secrets.
BridgeSweeps.cs Polled streams (Phase 3): vitals, house decay on transition, economy supply. Core-thread timers.

Emit() is called from the Core thread. It enqueues and returns — it never touches the socket, never blocks, never allocates a syscall. A wedged or absent sidecar cannot stall the shard, and that is the property everything else depends on.

Testing

tools/stub_sidecar.ps1 is a loopback listener that logs every line the shard sends. Run it, boot the shard, watch server.hello arrive.

.\tools\stub_sidecar.ps1 -Port 7788 -Log .\sidecar.log

tools/scaffolding/ holds the world seeder and the performance probe. Neither is deployed — deploy.ps1 only copies overlay/. They produced the budget in docs/PLAN.md §1. See tools/scaffolding/README.md.

Description
No description provided
Readme 947 KiB
v1.0.0 Latest
2026-08-01 06:47:57 +00:00
Languages
Rust 98.1%
Python 1.9%