Files
runicgateway.com/src/components/architecture/ModuleSeam.astro
wtclaude 56473d4d51
All checks were successful
PR checks / checks (pull_request) Successful in 2m37s
feat(site): phase 17 - per-game module pages, /runicnpc/, and checkTerms over the built site
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
2026-10-22 20:10:51 -05:00

170 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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