Compare commits

...

2 Commits

Author SHA1 Message Date
e852e5574d Merge pull request 'docs(book): the game host already has the files your site wants (chapter 3 §2b)' (#12) from docs/asset-bridge-p9 into main
Reviewed-on: #12
2026-09-14 22:25:37 +00:00
ad37cade6e docs(book): the game host already has the files your site wants (chapter 3 §2b)
All checks were successful
PR Checks / prose (pull_request) Successful in 16s
PR Checks / template (pull_request) Successful in 36s
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-14 13:09:57 -05: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