From ad37cade6e695103cf0dd35f668fc9c59d485c4a Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 14 Sep 2026 13:09:57 -0500 Subject: [PATCH] =?UTF-8?q?docs(book):=20the=20game=20host=20already=20has?= =?UTF-8?q?=20the=20files=20your=20site=20wants=20(chapter=203=20=C2=A72b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The integration kit's share of the Asset Bridge, and the whole of it: one section in the sidecar chapter, teaching the pattern rather than re-specifying anything. `docs/link/v8.md` stays normative and is linked out to, as every chapter does. The problem is general even though our instance of it is not. Most games keep content on the host that a website wants to show -- sprites, icons, portraits, localisation tables, map definitions -- and the tempting answer is to make it the operator's problem: export it on a desktop with a third-party tool, upload the result, repeat after every patch. It works once and rots immediately. The four design notes are the ones that cost us real time to learn: content rides request/reply and never events (a sidecar that persists and broadcasts every event would write megabytes of sprite into its store and fan it out to every client); serve one at a time and put "busy" in the protocol so a caller treats it as flow control; two stages, so the common case -- a restart that changed nothing -- costs one small round trip; and version your DERIVATION separately from the protocol, because improving how you read a file changes your bytes while the file's hash stays put. Plus the operational note that surprises people: do not import on boot. Based on `main` rather than `edge` deliberately -- the kit's chapter 5 and the ยง2a it follows are on main only, so this section has nowhere to sit on edge. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4 --- book/03-sidecar.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/book/03-sidecar.md b/book/03-sidecar.md index 5a8e05d..d8db916 100644 --- a/book/03-sidecar.md +++ b/book/03-sidecar.md @@ -135,6 +135,47 @@ clocks. And if the borrowed value lives in the game's own save file, the *hold* must be persisted and the timer re-armed at load; a restart preserves the change and destroys only the thing that would have undone it. +## 2b. The game host already has the files your site wants + +There is a third kind of traffic, and it is worth knowing about before you decide +your sidecar only ever forwards live state. Most games keep **content on the host** +that a website wants to show: sprites, icons, portraits, localisation tables, map +or spawn definitions. It is static, it is large, and it changes only when an +operator patches the game. + +The tempting answer is to make the operator's problem: export it on a desktop with +some third-party tool, upload the result, repeat after every patch. It works once +and rots immediately, because nothing reminds anyone to redo it. + +The better answer costs less than it sounds like: **the game host already has those +files, and you already have a channel to the game host.** Route them over it. + +Four design notes, all learned the expensive way in `uo-link`'s protocol 8 (the +"asset bridge", [`v8.md`][v8]): + +- **This is request/reply, never events.** A sidecar that persists and broadcasts + every event would write megabytes of sprite into its own store and fan it out to + every connected client. Content must ride the same correlated round-trip a query + uses โ€” see [chapter 5](05-events.md) for the shape. +- **Serve one at a time, and say so in the protocol.** Decoding assets costs the + game host real memory. One in-flight request with an explicit "busy" answer is + simpler and safer than a queue, and a caller that treats busy as flow control + rather than failure gets a working import out of it. +- **Two stages: what exists, then what changed.** A cheap call that returns a list + with a hash per item and no content, then a second that fetches only the hashes + that moved. The common case โ€” a restart that changed nothing โ€” must cost one + small round trip, not a re-download of everything. +- **Version your *derivation*, separately from the protocol.** If you improve how + you read a file, the bytes you produce change while the source file's hash does + not. `uo-link` carries an `EXTRACTOR_VERSION` for exactly that, and a consumer + treats a change in it like a changed hash. + +And one operational note, because it is the part that surprises people: **do not +import on boot.** A patch is an event the operator knows about and your website does +not. Re-reading hundreds of megabytes on every restart to discover that nothing +changed pays for the rare case forever; a button an operator presses after they +patch costs nothing and is honest about who knows what. + ## 3. The wire is a versioned contract, not a build dependency Your sidecar and your module ship separately, on different schedules, to hosts you @@ -246,4 +287,5 @@ it wrong takes the game down rather than the website. [api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.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 +[v8]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v8.md [dryrun]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust-dryrun.md -- 2.49.1