All checks were successful
PR checks / checks (pull_request) Successful in 2m37s
PLAN.md §16 phase 17, built on the org lead's answers of 2026-10-22, recorded as D75-D79 (count of record 79). - D75: core marketing pages describe what a game module adds in core's words. "Game intelligence" became "What a game module adds"; each game's own list moved to src/data/games/ and renders only on the new /modules/uo/ and /modules/rust/. /modules/ is a card per game. - The page-side capability check is per game (assertGameCoverage), the item phase 13 moved here. - D76: the homepage keeps its game screenshot, labelled. screens.mjs entries may carry `game` (D67); Screenshot.astro and the app grid print the game's name and mark the figure data-game. - D77: /app/ describes the Rust screens (released the same day). - D78: /runicnpc/, the standalone page, is in the header and footer; the narrow header was re-measured so five links keep two rows. - D79: checkTerms.mjs --built reads every built page's visible text after the build; game routes and data-game elements allow their game's words. Proven against ten plants. Still the twelfth check. - Re-read fixes: /architecture/'s "two machines is also the maximum", Discord "guild" on /integrations/, and collection.mjs against what Steam sign-in and the Rust module keep (PLAY_DATA_SAFETY.md regenerated; the Play answers are unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
170 lines
7.8 KiB
Plaintext
170 lines
7.8 KiB
Plaintext
---
|
||
import platform from '../../data/platform.json';
|
||
|
||
/**
|
||
* "Where the game stops and the platform starts" — the third of `/architecture/`'s diagrams
|
||
* (D21).
|
||
*
|
||
* The other two draw runtime shapes. This one draws a code boundary, and it is here because
|
||
* it is the claim the whole project rests on: that a community platform can be built once
|
||
* and pointed at any game. An evaluator has every reason to read that as marketing, so the
|
||
* page draws the seam and then says plainly what does and does not prove it — two modules
|
||
* exist, both built inside this project, and the exit criterion for calling the contract
|
||
* proven by an outsider is written down (§2, §16, and the entries `/modules/` renders from
|
||
* `notBuilt.mjs`). Until phase 13 this said one module and a paper exercise; Module-Rust's
|
||
* release made that false.
|
||
*
|
||
* The Module API version is read from `platform.json` like every other number on this site
|
||
* (§12). It is the one place a version genuinely belongs in this diagram: the seam is
|
||
* literally a version check, and a module whose declared range does not match refuses to
|
||
* load rather than half-loading.
|
||
*/
|
||
---
|
||
|
||
<section class="page section diagram" id="module-seam">
|
||
<div class="diagram__head">
|
||
<p class="eyebrow">Where the game stops</p>
|
||
<h2>A seam, with a version on it</h2>
|
||
<p class="prose">
|
||
The core site does not know what game it serves, what that game calls a group of
|
||
players, or that Ultima Online and Rust exist. Everything that does lives in an installable module on the other side of a
|
||
declared interface — which is what makes "put your game on it" a shape rather than a
|
||
slogan.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="diagram__body">
|
||
<div class="diagram__figure">
|
||
<svg viewBox="0 0 380 500" class="flow" aria-hidden="true" focusable="false">
|
||
<!-- Core: what ships in the image, on every deployment, module or not. -->
|
||
<rect class="host" x="8" y="8" width="364" height="186" rx="14" />
|
||
<text class="host-title" x="28" y="42">Runic Gateway core</text>
|
||
<text class="host-sub" x="28" y="62">game-agnostic; the same image everywhere</text>
|
||
|
||
<rect class="node node--self" x="28" y="80" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="46" y="108">Accounts</text>
|
||
|
||
<rect class="node node--self" x="196" y="80" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="214" y="108">Teams</text>
|
||
|
||
<rect class="node node--self" x="28" y="134" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="46" y="162">Wiki and posts</text>
|
||
|
||
<rect class="node node--self" x="196" y="134" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="214" y="162">Admin and API</text>
|
||
|
||
<!-- The seam. Both boundary lines and the label between them: this is the one
|
||
thing in the picture that is neither core nor module. -->
|
||
<path class="boundary" d="M8 224 H372" />
|
||
<text class="seam-label" x="190" y="252" text-anchor="middle">
|
||
Module API {platform.moduleApi}
|
||
</text>
|
||
<path class="boundary" d="M8 272 H372" />
|
||
|
||
<!-- Registers upward; is asked downward. Two arrows, opposite directions, because
|
||
the traffic across a seam is not one-way and drawing it as one-way is what
|
||
makes people think a module is a plugin that only listens. -->
|
||
<path class="spine" d="M120 300 V206" />
|
||
<path class="arrow" d="M120 198 l-6 10 h12 Z" />
|
||
<text class="seam-arrow" x="136" y="216">registers</text>
|
||
|
||
<path class="spine" d="M260 200 V294" />
|
||
<path class="arrow" d="M260 302 l-6 -10 h12 Z" />
|
||
<text class="seam-arrow" x="244" y="290" text-anchor="end">calls</text>
|
||
|
||
<!-- The module: everything that knows a game exists. -->
|
||
<rect class="host" x="8" y="306" width="364" height="186" rx="14" />
|
||
<text class="host-title" x="28" y="340">Game module</text>
|
||
<text class="host-sub" x="28" y="360">one per deployment; UO or Rust today</text>
|
||
|
||
<rect class="node" x="28" y="378" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="46" y="406">Routes</text>
|
||
|
||
<rect class="node" x="196" y="378" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="214" y="406">Screens</text>
|
||
|
||
<rect class="node" x="28" y="432" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="46" y="460">Its own tables</text>
|
||
|
||
<rect class="node" x="196" y="432" width="156" height="46" rx="10" />
|
||
<text class="node-title" x="214" y="460">Nav rows</text>
|
||
</svg>
|
||
|
||
<p class="diagram__caption">
|
||
A module declares which versions of the interface it speaks. If that does not match
|
||
what the site offers, it refuses to load and the site comes up without it.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="diagram__notes">
|
||
<section>
|
||
<h3>The module brings its own everything</h3>
|
||
<p>
|
||
Not just screens: its routes, its database tables, its navigation rows, its slice of
|
||
the OpenAPI spec and its own prebuilt client bundle. Installing it is a paste in the
|
||
admin panel or a line in your environment — never a build step, because production
|
||
runs an image you pulled, and an operator who has to compile something has been
|
||
handed a maintenance job rather than a feature.
|
||
</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Failure is contained by design</h3>
|
||
<p>
|
||
A module that will not load is marked as failed and the site starts without it.
|
||
Disabling one is a kill switch, not a visibility flag — its routes stop answering
|
||
and its live connections close. Uninstalling keeps the data, and destroying the data
|
||
is a separate, deliberate choice made in its own dialog.
|
||
</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Teams is the shape of the contract</h3>
|
||
<p>
|
||
Core owns the Teams primitive — the roster, the forum, the notifications, the voice
|
||
channel — and does not own the <em>word</em>. A Team cannot be created in core at
|
||
all; it arrives from the module, which is why each module calls them by its own
|
||
game’s word and builds those pages itself. That is the pattern the whole interface is built on: core
|
||
supplies the machinery, the module supplies the meaning.
|
||
</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>What this does not yet prove</h3>
|
||
<p>
|
||
Two modules exist: Ultima Online, and Rust, which this project built second, from
|
||
the Integration Kit. Where the Rust module needed something the contract did not
|
||
yet offer, the gap was closed in core, for every module, rather than in a fork — so
|
||
the seam now has two games on its far side instead of one. What two modules built
|
||
inside this project cannot prove is that somebody outside it can do the same, and
|
||
that is why the Integration Kit still says draft.
|
||
</p>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<style>
|
||
/* The seam label sits between the two boundary rules rather than beside them: it is
|
||
the name of the gap, not an annotation on either side of it. Gold, because it is
|
||
the one contract in the picture. */
|
||
.seam-label {
|
||
fill: var(--gold);
|
||
font-family: var(--sans);
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* Two words, because two arrows crossing a boundary in opposite directions is
|
||
ambiguous without them — and the ambiguity is the exact misreading this diagram
|
||
exists to prevent, that a module is something core talks at. */
|
||
.seam-arrow {
|
||
fill: var(--dim);
|
||
font-family: var(--sans);
|
||
font-size: 11px;
|
||
font-style: italic;
|
||
}
|
||
</style>
|