From 744e5b7944a1b1dda09e468e64a23523addc9459 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 19 Aug 2026 04:11:03 -0500 Subject: [PATCH 1/2] docs(book): a mod is a plugin too, and RCON is not the Rust answer The kit taught, to an audience outside this org, that Rust needs "no game-side plugin to write at all" because it ships RCON. That is overruled: the Rust dry run reaches the game through a MOD - a plugin loaded by the server's own framework, hooking events and dialling out - exactly as the ServUO overlay does (docs#170). Chapter 3's section kept its question and lost its example, which turned out to improve it. The useful test is not "does my game expose a protocol" but "does it DELIVER EVENTS": a remote-control channel is built for an operator typing commands and tells you what you asked about, when you ask, and a website needs what happened whether or not anyone was listening. A channel that answers questions can only be polled, and polling turns "someone left the clan at 14:02" into "the count was different at 14:03". Rust now appears in that section as the counter-example rather than the example, and carries the finding that is actually worth having: its server is a BINARY where ServUO is source you compile, and the three-part shape survives that unchanged. The plugin-dials-out arrangement is not a property of having source access. Chapter 4 said a game with a remote-control protocol may not need any of it, and that its worked example is source you build. Both now say what is true - the rules in that chapter are properties of being inside a game loop, and apply identically to a mod in a closed server. Co-Authored-By: Claude --- README.md | 8 +++++--- book/03-sidecar.md | 45 +++++++++++++++++++++++++----------------- book/04-game-plugin.md | 15 ++++++++++---- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 9346eb4..760dada 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,11 @@ in the contract ([`MODULE_API.md`][api] §2.7, `MODULE_API_VERSION` 1.4.0), not style preference, and chapter 3 is mostly about why. The short version: the website is the internet-facing process and your game is not; the sidecar persists before it forwards, so a website that is down or mid-deploy loses nothing; and a -game must never block on a web request. A game that already exposes a -remote-control surface — Rust's RCON over WebSocket, say — needs a *thin* sidecar, -not none. +game must never block on a web request. A game that genuinely delivers events on a +surface of its own needs a *thin* sidecar, not none — but check that it delivers +events rather than answering questions, because a channel built for an operator +typing commands can only be polled, and polling turns "someone left at 14:02" into +"the count was different at 14:03". ## Start here diff --git a/book/03-sidecar.md b/book/03-sidecar.md index 82a732c..eb0a467 100644 --- a/book/03-sidecar.md +++ b/book/03-sidecar.md @@ -132,28 +132,37 @@ forwarded, a lossy live feed, an authenticated read API with a version on it. ## "But my game already speaks a remote-control protocol" -Then your sidecar is **thin**, not absent. +Then your sidecar is **thin**, not absent — and be sure the surface you are +thinking of actually carries what your module needs, because that is where this +question usually goes wrong. -Rust — the survival game — is the worked example here, in -[`rust-dryrun.md`][dryrun]: a module designed on paper for a game chosen for how -little it shares with Ultima Online. Rust ships RCON over WebSocket, so a -`rust-link` has no protocol to invent and no game-side plugin to write at all. It -keeps: +A remote-control channel is built for an operator typing commands: it tells you +what you asked about, when you ask. What a website needs is what *happened* — +every kill, every join, every departure, delivered whether or not anyone was +listening at that moment. Those are different products, and a channel that +answers the first can only approximate the second by polling it, which turns +"someone left the clan at 14:02" into "the count was different at 14:03". -- the RCON connection, its credentials and its reconnect loop, **out of an Express - process** — where the failure mode is a wedged request handler; -- a store, so the site is not blank whenever the game is restarting, which for that - genre is a daily scheduled event; -- an HTTP + WS API with a version on it, so the module talks to one shape of thing - regardless of what the game speaks. +So the honest test is not *does my game expose a protocol* but **does it deliver +events**. If it does, your sidecar keeps that connection, its credentials and its +reconnect loop out of an Express process, keeps a store so the site is not blank +whenever the game restarts, and presents your module one versioned HTTP + WS +shape. That is what thin means: less code, the same architecture. -It drops the bespoke wire protocol and the plugin. That is what "thin" means: less -code, not a different architecture. +**Rust is the worked example, and it is not that case.** The dry run in +[`rust-dryrun.md`][dryrun] designs a module for it precisely because it shares so +little with Ultima Online — and its answer is a **mod**: a plugin loaded by the +server's own mod framework, hooking the game's events and dialling out to a +sidecar, exactly as the ServUO overlay does. The Rust server is a *binary*, where +ServUO is source a shard owner compiles, so the way in is a published hook API +rather than a file you edit. **The three-part shape survives that unchanged**, +which is the more useful finding: the plugin-dials-out arrangement is not a +property of having source access. -That document originally concluded the opposite — "no sidecar, the module dials -RCON directly" — and it carries a dated correction saying so, rather than having -been quietly rewritten. The value of a dry run is the record of what it found, -including where it was overruled. +That document reached the game over RCON until 2026-08-19, and before that +concluded there should be **no sidecar at all**. It carries both corrections, +dated, rather than having been quietly rewritten — the value of a dry run is the +record of what it found, including where it was overruled. ## Building yours diff --git a/book/04-game-plugin.md b/book/04-game-plugin.md index ffd139a..3dc8669 100644 --- a/book/04-game-plugin.md +++ b/book/04-game-plugin.md @@ -4,10 +4,17 @@ The chapter with the least code and the highest stakes. Everything else in this book fails by showing an operator a broken web page; this part fails by taking the game down while people are playing it. -If your game already speaks a remote-control protocol, you may not need any of -this — see the end of [chapter 3](03-sidecar.md). If it does not, something has to -run inside the game and feed your sidecar, and the rules below are what keep that -something from being the reason the server froze. +If your game already **delivers events** on a surface of its own, you may not need +any of this — see the end of [chapter 3](03-sidecar.md), and read the test there +before deciding, because a channel that answers questions is not the same thing. +Otherwise something has to run inside the game and feed your sidecar, and the +rules below are what keep that something from being the reason the server froze. + +**That something does not have to be source you compile.** The worked example +below is an overlay built into a server whose code you have; the dry run for Rust +is a **mod** loaded by a closed server's own framework, hooking published events. +Every rule in this chapter applies identically to both — they are properties of +being inside a game loop, not of how you got there. The worked example is `servuo-plugins`, the Ultima Online shard plugin, whose link layer is one file: `overlay/Scripts/Custom/Bridge/BridgeLink.cs`. It is C# against -- 2.49.1 From d497a3b09a612af1efe4474cc0dfb0c35ef49890 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 19 Aug 2026 04:16:48 -0500 Subject: [PATCH 2/2] docs(book): name Oxide, and the question "how many sidecars" answers Follows the org lead's two corrections on docs#170. The Rust example is an OXIDE plugin - naming the framework is the difference between a design a reader can start from and one they have to go and choose for themselves - and the architecture pairs one sidecar to one game server, on that server's own host. Chapter 3 gains the general form of that second one, since it is the chapter where a reader decides what to build: if your game runs as a fleet, "how many sidecars" is answered by where the loopback boundary is, not by how many processes you would rather run. Your module holding several clients is the cheaper end of that trade, and core never learns there is more than one. Co-Authored-By: Claude --- book/03-sidecar.md | 23 ++++++++++++++++------- book/04-game-plugin.md | 6 +++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/book/03-sidecar.md b/book/03-sidecar.md index eb0a467..9cc8872 100644 --- a/book/03-sidecar.md +++ b/book/03-sidecar.md @@ -151,13 +151,22 @@ shape. That is what thin means: less code, the same architecture. **Rust is the worked example, and it is not that case.** The dry run in [`rust-dryrun.md`][dryrun] designs a module for it precisely because it shares so -little with Ultima Online — and its answer is a **mod**: a plugin loaded by the -server's own mod framework, hooking the game's events and dialling out to a -sidecar, exactly as the ServUO overlay does. The Rust server is a *binary*, where -ServUO is source a shard owner compiles, so the way in is a published hook API -rather than a file you edit. **The three-part shape survives that unchanged**, -which is the more useful finding: the plugin-dials-out arrangement is not a -property of having source access. +little with Ultima Online — and its answer is an **Oxide plugin**: C# loaded by +the mod framework a modded Rust server already runs, hooking the game's events and +dialling out to a sidecar, exactly as the ServUO overlay does. The Rust server is a +*binary*, where ServUO is source a shard owner compiles, so the way in is a +published hook API rather than a file you edit. **The three-part shape survives +that unchanged**, which is the more useful finding: the plugin-dials-out +arrangement is not a property of having source access. + +It also pairs **one sidecar to one game server**, on that server's own host, +rather than one sidecar fronting a community's several — because those servers sit +on separate machines, and a shared sidecar would be reached across a network by +plugins that are supposed to talk to it over loopback. Worth knowing before you +design yours: if your game runs as a fleet, the question "how many sidecars" is +answered by where the loopback boundary is, not by how many processes you would +rather run. Your module holding several clients is the cheaper end of that trade, +and core never learns there is more than one. That document reached the game over RCON until 2026-08-19, and before that concluded there should be **no sidecar at all**. It carries both corrections, diff --git a/book/04-game-plugin.md b/book/04-game-plugin.md index 3dc8669..c3bc865 100644 --- a/book/04-game-plugin.md +++ b/book/04-game-plugin.md @@ -12,9 +12,9 @@ rules below are what keep that something from being the reason the server froze. **That something does not have to be source you compile.** The worked example below is an overlay built into a server whose code you have; the dry run for Rust -is a **mod** loaded by a closed server's own framework, hooking published events. -Every rule in this chapter applies identically to both — they are properties of -being inside a game loop, not of how you got there. +is an **Oxide plugin**, C# loaded by a closed server's own mod framework and +hooking published events. Every rule in this chapter applies identically to both — +they are properties of being inside a game loop, not of how you got there. The worked example is `servuo-plugins`, the Ultima Online shard plugin, whose link layer is one file: `overlay/Scripts/Custom/Bridge/BridgeLink.cs`. It is C# against -- 2.49.1