Compare commits
4 Commits
a72b002f75
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e852e5574d | |||
| ad37cade6e | |||
| 7f746aee3d | |||
| 5dc14fa626 |
@@ -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
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"repo": "https://gitea.whitlocktech.com/RunicGateway/website.git",
|
||||
"branch": "main",
|
||||
"ref": "66bb3b9a3fad01112c06f32d931c9bae56d22de6",
|
||||
"ref": "655fbf3f69a6a1fd650ecbc81afd6cf9c2ad9f66",
|
||||
"why": [
|
||||
"The core this kit is written against, pinned to a commit rather than a branch.",
|
||||
"This one is the engagement cutover, the commit MODULE_API_VERSION 1.9.0 reached",
|
||||
"`main` on, and 1.9.0 is what template/module.json declares. It moved here from",
|
||||
"1.6.0 (the Teams cutover) because engagement expanded the contract the book",
|
||||
"teaches by three registrations and two calls: a module now declares what its",
|
||||
"game can announce and never who is told.",
|
||||
"This one is the EVENT SYSTEM cutover, the commit MODULE_API_VERSION 1.10.0",
|
||||
"reached `main` on (website#199), and 1.10.0 is what template/module.json",
|
||||
"declares. It moved here from 66bb3b9a (1.9.0, the engagement cutover) because",
|
||||
"the event contract expanded the book by a whole chapter: a module now declares",
|
||||
"what its game can DO on request -- event actions, budget dimensions, leases and",
|
||||
"option sources -- where every earlier chapter taught only a read path and a",
|
||||
"thing to announce.",
|
||||
"",
|
||||
"Moving this pin is the moment someone re-reads the chapters: CI asserts the",
|
||||
"version template/module.json declares still equals this core's",
|
||||
@@ -19,29 +21,40 @@
|
||||
"own. Nothing goes red until someone moves the pin. Between cutovers the kit is",
|
||||
"not wrong, it is DATED - and this file is where the date is written down.",
|
||||
"",
|
||||
"The mechanism earned its keep again here. Writing chapter 2's engagement",
|
||||
"section against 1.9.0 found that the seeded body a module ships is the one",
|
||||
"thing registerEngagementSeeds does not validate - it checks that `blocks` is a",
|
||||
"non-empty array and stops - so the template's own example body had a heading",
|
||||
"level of 2 where the block registry takes 'h2', and no block ids at all. It",
|
||||
"would have registered, seeded, and failed the first time an operator opened it.",
|
||||
"Caught by running the template's register() through core's real registry at",
|
||||
"this ref, which is what a re-read is for; both the fix and the gap are now in",
|
||||
"the chapter and beside the code.",
|
||||
"This pin move is a REPAIR as well as a date. Chapter 5 landed (#10) declaring",
|
||||
"^1.10.0 while this file still named a 1.9.0 core, so `main` has been red on",
|
||||
"checkCoreApi since it merged -- deliberately, and stated in that PR, but the",
|
||||
"red belongs to the window and not to the repo. This is the commit that was",
|
||||
"always going to close it, and it could not be written until the events sha",
|
||||
"existed on `main`. Same shape Teams phase 11 used.",
|
||||
"",
|
||||
"That gap is also why this file's own instruction is not enough on its own. The",
|
||||
"The mechanism earned its keep again here, and twice. Writing chapter 5 against",
|
||||
"the event contract found that an idempotency key on a QUESTION makes every",
|
||||
"later read permanently stale -- an at-most-once store answers a repeated key",
|
||||
"with the ORIGINAL reply, so the template's second read of a value returned the",
|
||||
"first read's answer for ever, and the module could not see a change it had just",
|
||||
"made. It also found that a refusal's reason goes in `error`: core's classifier",
|
||||
"reads no other name, so a refusal reported under `detail` reached an author as",
|
||||
"a bare \"refused\". Neither was found by writing prose. Both were found by",
|
||||
"running the template's real declarations through core's real registry and its",
|
||||
"real envelopes through core's real dispatcher.",
|
||||
"",
|
||||
"That is also why this file's own instruction is not enough on its own. The",
|
||||
"template job builds and tests the template against fakes and checks this",
|
||||
"number; it does not load the module into core. A declaration a fake accepts",
|
||||
"and core refuses would ship green, so a pin move is a run against a real core,",
|
||||
"not just an edit here.",
|
||||
"not just an edit here. It was run at THIS ref: core's real registries accepted",
|
||||
"the template's budget, option source, lease and event action, and apply()",
|
||||
"accepted the set.",
|
||||
"",
|
||||
"The branch said `edge` until 2026-08-12, when the module system cut over and",
|
||||
"that branch was deleted (MODULE_SYSTEM.md 2.9). Two later workstreams cut an",
|
||||
"`edge` of their own and this pin skipped both: the kit is written against what",
|
||||
"shipped, never against what is in flight. Nothing in CI reads the branch field",
|
||||
"- it clones the repo and checks out the sha - which is why a wrong label here",
|
||||
"would sit unnoticed. It is for the person deciding whether a newer core is",
|
||||
"worth re-reading the book for.",
|
||||
"`edge` of their own and this pin skipped both; the Event System cut a third,",
|
||||
"and this pin skipped that too until it reached `main`. The kit is written",
|
||||
"against what shipped, never against what is in flight. Nothing in CI reads the",
|
||||
"branch field - it clones the repo and checks out the sha - which is why a wrong",
|
||||
"label here would sit unnoticed. It is for the person deciding whether a newer",
|
||||
"core is worth re-reading the book for.",
|
||||
"",
|
||||
"Same convention as Module-uo's ci/core-ref.json, deliberately - one file, one",
|
||||
"sha, reviewable in a diff."
|
||||
|
||||
Reference in New Issue
Block a user