docs(book): the four chapters — Phase 5 slice 2
The book, written out of the tree slice 1 proved. Four chapters in the order the
work happens: the first module in twenty minutes, the website module, the sidecar,
and the game-side plugin.
Shape, settled with the org lead:
* template/README.md stays the REFERENCE — it travels with a copied template and
CI holds it against the tree — and chapter 1 is the narration: what you should
see after each step, the state your module lands in, and the four ways it fails.
The chapter links to the checklist rather than restating it.
* chapters 3 and 4 cite link/ and servuo-plugins/ by FILE AND IDENTIFIER, never by
line. Those repositories move for their own reasons and checkLinks already
forbids commit permalinks, so a line number in this book is wrong the moment
they do. The template stays the only code quoted verbatim.
* one PR: the outline's status table and the link check are only coherent when the
whole set lands.
scripts/checkChapterPaths.js is the anti-rot half a machine can answer: every path
a chapter names in backticks must exist. None of those mentions is a markdown link,
so checkLinks never looked at them, and none is code, so nothing else did either —
renaming one template file would have left four chapters quietly pointing at
nothing. Its anchor list is STATED rather than derived from the tree, for the reason
the template's own build guard states it: a list derived from what exists cannot
fail when what exists changes, and an anchor that stops matching is a check that has
silently stopped checking. So each anchor must exist or the check fails. Eleven
tests, every "must not catch" case a span that really appears in the book.
stripFences moved to scripts/lib/markdown.js and both checks use it — shared code,
not a shared description.
CHAPTER 1 WAS RUN, NOT REASONED ABOUT. The template was copied into a real core on
edge, booted against the dev database, and every claim in "what you should see"
checked: the five log lines, /examplegame/status with its injected
<script type="module" src="/modules/examplegame/entry.js">, the chunk served
no-cache while module.json 404s, /api/v1/public/world/status, the capabilities in
/api/v1/public/modules, and the route in the merged /api/docs.json. Then the three
failures the chapter tells a reader to cause on purpose, because a chapter that
predicts the wrong debugging heuristic is worse than one that predicts none:
* an undeclared prefix -> stage `register`, "declared public/extra but never
registered it", routes 404 and absent from /public/modules;
* a table without the id prefix -> stage `schema`, at LOAD time, before mounting;
* a throwing onBoot -> after mounting, so the same route answers 503 "Module
unavailable" rather than vanishing.
All three came out exactly as written, and the messages in the chapter are that
core's own. Two small corrections fell out of the run: the log sample now shows the
real interleaving of core's three lines with the module's two, and the section on
failure adds that a module disappears from /api/v1/public/modules in every failure
case — a check that needs no login.
MODULE_SYSTEM.md 2.11.1 slice 2. Docs half: docs#146.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
117
book/README.md
117
book/README.md
@@ -1,99 +1,40 @@
|
||||
# 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.
|
||||
Four chapters, in the order the work happens.
|
||||
|
||||
Read [the dry run][dryrun] before any of them.
|
||||
Read [the dry run][dryrun] before any of them — a complete module designed on
|
||||
paper for a second game, and the shortest honest picture of the whole job.
|
||||
|
||||
| # | 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 |
|
||||
| # | Chapter | What it covers |
|
||||
| --- | --- | --- |
|
||||
| 1 | [Your first module in twenty minutes](01-first-module.md) | Copy the template, rename it, build it, install it, see a page. No theory. |
|
||||
| 2 | [The website module](02-website-module.md) | The bulk of the work: `module.json`, `register(ctx, api)`, the schema fragment, the client chunk, packaging, and what a module must never do. |
|
||||
| 3 | [The sidecar](03-sidecar.md) | Why the website never talks to a game server, what "persist before you forward" means, and what a *thin* sidecar is. |
|
||||
| 4 | [The game-side plugin](04-game-plugin.md) | The least code and the highest stakes: never block the game thread. |
|
||||
|
||||
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.
|
||||
Chapters 1 and 2 quote `template/`, which CI builds against a pinned core, so their
|
||||
code is a tree that is proved rather than prose that looks like one. Chapters 3 and
|
||||
4 cite `uo-link` and `servuo-plugins` by file and identifier rather than by line, on
|
||||
purpose: those repositories move for their own reasons and a line number in a book
|
||||
is wrong the moment they do.
|
||||
|
||||
## 1. Your first module in twenty minutes
|
||||
## What is normative, and what is here
|
||||
|
||||
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.
|
||||
Nothing in these chapters is. Where a chapter and one of these disagree, the
|
||||
document is right and the chapter has a bug — [say so][issues]:
|
||||
|
||||
- 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.
|
||||
| Authority | For |
|
||||
| --- | --- |
|
||||
| [`MODULE_API.md`][api] | Everything a module may do. |
|
||||
| [`MODULE_SYSTEM.md`][system] | Why the module system is shaped this way, and how a module is installed and removed. |
|
||||
| [`link/PLAN.md`][linkplan] + [`INTEGRATION.md`][linkint] | The game↔sidecar wire protocol, as one real sidecar implements it. |
|
||||
|
||||
## 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.
|
||||
The chapters teach: the order to do things in, the reasoning, and the mistakes that
|
||||
cost this project time.
|
||||
|
||||
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md
|
||||
[system]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_SYSTEM.md
|
||||
[dryrun]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust-dryrun.md
|
||||
[linkplan]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md
|
||||
[linkint]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/INTEGRATION.md
|
||||
[issues]: https://gitea.whitlocktech.com/RunicGateway/Integration-kit/issues
|
||||
|
||||
Reference in New Issue
Block a user