Claude 11169c52a6 feat(admin): forward in-game moderation to the website (bidirectional audit)
Phase C / §5.5: so the site's moderation log is complete regardless of origin,
in-game uses of the write-plane verbs are forwarded as admin.audit
(origin:"in-game").

- patches/commandlogging-event.patch: adds CommandLogging.OnWrite, raised in
  WriteLine before the m_Enabled guard so it fires even when file logging is
  off. Scripts-layer file -> dynamic build, no core rebuild.
- patches/BridgeModerationAudit.cs: subscriber. Taps OnWrite for resolved
  ban/kick (parsing the target from the log line) and EventSink.Command for
  [bcast. Lives in patches/ (not overlay/) because it references OnWrite,
  which only exists post-patch — same rule as BridgeVendorSale.cs.
- tools/scaffolding/BridgeAuditProbe.cs: gated headless verification.

Verified live: a genuine [bcast plus simulated ban/kick log lines produced
admin.audit frames with origin=in-game, actor, and the target parsed
(seed_010); a non-moderation line was correctly ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
2026-07-13 02:12:30 -05:00
2026-07-10 20:47:09 -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.
sidecar/ The Rust sidecar: terminates the loopback link to the shard, exposes WS + REST to the website. See sidecar/README.md.
tools/ Never deployed. Test scaffolding and anything else that must not reach a server.
docs/INTEGRATION.md Website integration guide — the WebSocket feed, REST endpoints, auth, event catalog, and examples. Start here to build the front end.
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 (BridgeRequests) done, acceptance in docs/PLAN.md §14
5 — [link account linking (BridgeAccountLink) done, acceptance in docs/PLAN.md §15
6 — town-crier inbound (BridgeTownCrier) done, acceptance in docs/PLAN.md §16
7 — PlayerVendorSale core event (patches/ + BridgeVendorSale) done, acceptance in docs/PLAN.md §17

Every phase on the ServUO side is complete. Phases 06 are drop-in (overlay/); Phase 7 is the one core change, shipped as patches/. Remaining work is the Rust sidecar.

Cheat-detection signals are not a separate phase — they are folded into the streams above: cheat.fastwalk, audit.set, audit.command, and vendor.sale (buyer + owner for laundering detection).

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.
BridgeProfile.cs Read-model builders (Phase 4): full character profile, account roster. Core-thread reads.
BridgeRequests.cs Inbound request handlers (Phase 4): char.request, account.roster, vendor.snapshot, with bridge.error replies.
BridgeAccountLink.cs [link account linking (Phase 5): one-time code, link.confirm, WebsiteUserId account tag.
BridgeTownCrier.cs Town-crier news (Phase 6): inbound towncrier.add / remove into the global crier list, with abuse caps.

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. It survives a just-killed instance (SO_REUSEADDR) and won't die on a transient error.

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

tools/stub_sidecar_request.ps1 additionally sends inbound requests (char.request, account.roster, vendor.snapshot, plus an error case) right after the shard connects, and logs the replies — the harness used to validate Phase 4.

Note: the throwaway PowerShell sidecars are fragile — they get reaped and contend on their log file. The real Rust sidecar replaces them; don't read their flakiness as a shard problem. The shard buffers non-perishable events through any outage and reconnects on its own (observed reconnecting 5× unattended in one session).

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%