docs(book): the game host already has the files your site wants (chapter 3 §2b) #12

Merged
whitlocktech merged 1 commits from docs/asset-bridge-p9 into main 2026-09-14 22:25:39 +00:00

View File

@@ -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