Merge pull request 'docs(book): a mod is a plugin too, and RCON is not the Rust answer' (#7) from docs/no-rcon-example into main

Reviewed-on: #7
This commit is contained in:
2026-08-19 11:15:28 +00:00
3 changed files with 52 additions and 25 deletions

View File

@@ -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 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 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 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 game must never block on a web request. A game that genuinely delivers events on a
remote-control surface — Rust's RCON over WebSocket, say — needs a *thin* sidecar, surface of its own needs a *thin* sidecar, not none — but check that it delivers
not none. 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 ## Start here

View File

@@ -132,28 +132,46 @@ forwarded, a lossy live feed, an authenticated read API with a version on it.
## "But my game already speaks a remote-control protocol" ## "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 A remote-control channel is built for an operator typing commands: it tells you
[`rust-dryrun.md`][dryrun]: a module designed on paper for a game chosen for how what you asked about, when you ask. What a website needs is what *happened*
little it shares with Ultima Online. Rust ships RCON over WebSocket, so a every kill, every join, every departure, delivered whether or not anyone was
`rust-link` has no protocol to invent and no game-side plugin to write at all. It listening at that moment. Those are different products, and a channel that
keeps: 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 So the honest test is not *does my game expose a protocol* but **does it deliver
process** — where the failure mode is a wedged request handler; events**. If it does, your sidecar keeps that connection, its credentials and its
- a store, so the site is not blank whenever the game is restarting, which for that reconnect loop out of an Express process, keeps a store so the site is not blank
genre is a daily scheduled event; whenever the game restarts, and presents your module one versioned HTTP + WS
- an HTTP + WS API with a version on it, so the module talks to one shape of thing shape. That is what thin means: less code, the same architecture.
regardless of what the game speaks.
It drops the bespoke wire protocol and the plugin. That is what "thin" means: less **Rust is the worked example, and it is not that case.** The dry run in
code, not a different architecture. [`rust-dryrun.md`][dryrun] designs a module for it precisely because it shares so
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.
That document originally concluded the opposite — "no sidecar, the module dials It also pairs **one sidecar to one game server**, on that server's own host,
RCON directly" — and it carries a dated correction saying so, rather than having rather than one sidecar fronting a community's several — because those servers sit
been quietly rewritten. The value of a dry run is the record of what it found, on separate machines, and a shared sidecar would be reached across a network by
including where it was overruled. 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,
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 ## Building yours

View File

@@ -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 book fails by showing an operator a broken web page; this part fails by taking the
game down while people are playing it. game down while people are playing it.
If your game already speaks a remote-control protocol, you may not need any of If your game already **delivers events** on a surface of its own, you may not need
this — see the end of [chapter 3](03-sidecar.md). If it does not, something has to any of this — see the end of [chapter 3](03-sidecar.md), and read the test there
run inside the game and feed your sidecar, and the rules below are what keep that before deciding, because a channel that answers questions is not the same thing.
something from being the reason the server froze. 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 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 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 layer is one file: `overlay/Scripts/Custom/Bridge/BridgeLink.cs`. It is C# against