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