# The book Four chapters, in the order the work happens. **None of them are written yet** — this is the outline, landed first so the shape can be argued with before the prose exists. Chapter status is in the table; a chapter that is not there yet is not there yet, rather than a stub that reads like an answer. Read [the dry run][dryrun] before any of them. | # | Chapter | File | Status | | --- | --- | --- | --- | | 1 | Your first module in twenty minutes | `01-first-module.md` | not written | | 2 | The website module | `02-website-module.md` | not written | | 3 | The sidecar | `03-sidecar.md` | not written | | 4 | The game-side plugin | `04-game-plugin.md` | not written | They are named but not linked on purpose: a link to a file that does not exist is the thing this repo's link check is for, and an outline should not be the first thing to fail it. ## 1. Your first module in twenty minutes Copy `template/`, rename it, build it, install it, see a page. No theory. The point is to reach a working module before learning anything, so that everything after it is a change to something that already runs rather than a step toward something that might. - What the pieces of `template/` are, one paragraph each. - `module.json`: the fields you must change, and `coreApi`. - Building the client chunk. Why a module ships **prebuilt** and an operator never builds anything. - Installing it: the admin panel, the `MODULES` environment variable, or a directory on the volume. - Reading the state your module lands in, and the four ways it can fail to load. ## 2. The website module The bulk of the kit. - **`module.json`** — every field, and which are load-bearing at boot. - **The server entry point.** `register(ctx, api)`; what `ctx` hands you and why each member is handed rather than imported; the lazy-accessor pattern that lets a ported file keep a file-scope `require`, and the require-order rule that comes with it. - **The `register*` calls** — routes per tier, notification streams, announce legs, post hooks, extension slots. Worked examples of each, with the distinctions that are easy to get wrong (a leg is one-shot delivery with retry; a post hook is idempotent state that also runs on delete). - **The schema fragment.** Idempotent, replayed every boot, leading-verb allowlist, the table-prefix rule, and why there is no migration runner anywhere in this project. What belongs in `purge.sql` instead. - **The client half.** The prebuilt ESM chunk; `window.__rg`; the shared-dependency rule (core owns React and hands it over — a module that resolves its own gets two Reacts and a broken page); the Vite library build with anchored aliases and `external: []`, and *why* that combination rather than the obvious one. - **Routes, nav and features on the client**, and how a module's nav row becomes an ordinary row an operator can reorder, relabel or hide. - **The UI kit** — seven members, closed on purpose. What to do about the eighth thing you want. - **The OpenAPI fragment**, and how to generate it from your own registrations. - **Packaging and release CI**: the tarball, the install manifest, the checksum, and the version living in `module.json`. - **Boundaries.** What a module must not do, each with the failure it prevents. ## 3. The sidecar Why it exists, why it is **not optional**, and what "thin" means for a game that already speaks a remote-control protocol. - The invariant: your game is never network-reachable; it **dials out**, the sidecar listens, and only the website's backend talks to the sidecar. - **Persist before you forward.** The sidecar owns the durable copy — event history, the latest snapshot of every board, whatever a page must still be able to render when the game or the website is down. A live feed is allowed to be lossy *because* the store is not. - The wire as a **versioned compatibility contract** rather than a build dependency: a version on every response, a mismatch refused rather than mis-parsed, and what a bump obliges you to change in the same commit. - Auth, and why the sidecar is the only exposed part. - `uo-link` as the worked example, and what a *thin* sidecar for an RCON-style game keeps and drops. ## 4. The game-side plugin The chapter with the least code and the highest stakes: a plugin that gets this wrong takes the game down when the sidecar wedges. - **Never block the game thread.** Enqueue and return; a bounded, drop-oldest queue; a dedicated writer thread that drains it. Dropping the oldest event is correct, and stalling the game to avoid it is not. - **Read the world only on the game's own thread**, and hand plain data to the writer. - Reconnect, backoff, and what to send on connect so the sidecar can rebuild its picture without asking. - What to emit at all: the difference between an event stream and a state snapshot, and why both exist. - `servuo-plugins` as the worked example. The constraints are general; the C# is not. [dryrun]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust-dryrun.md