feat(facts): phase 13 - per-game facts, Rust and RunicNPC checked, main green again
All checks were successful
PR checks / checks (pull_request) Successful in 8m25s

platform.json drops the single protocol/bundle/capability list for a
`games` object (uo, rust) plus a `runicnpc` block. checkFacts.mjs loops
the protocol, plugin-protocol, capability and bundle checks over both
games, reads the schema-2 bundles at v2/<game>/current.json, checks each
module's coreApi, and reads RunicNPC's plugin.toml. 41 facts, up from 19;
clears the two that were red on main (Module API 1.17.0, installer
v0.8.0).

D66: the hero, footer and structured data show the Module API only; each
game's protocol and bundle go on that game's pages, and the docs landing
table splits into shared versions plus one column per game.

The re-read section 1 requires found, and this fixes:
- three hard-coded "1.6.0" Module API versions in the module docs
- module-lifecycle saying modules load alphabetically (the loader now
  refuses a second module, W19), and the install pages never mentioning
  the one-module rule
- "there is one module" in five places, now two modules both built
  inside this project; the Integration Kit keeps its draft banner
- the homepage diagram's ServUO/uo-link sub-labels (D63 wording now)

notBuilt drops `second-module` (resolved by Module-Rust's release) and
rewords `multi-module`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-22 08:09:22 -05:00
parent 2971b84d51
commit 9938e71e07
29 changed files with 384 additions and 154 deletions

View File

@@ -62,8 +62,9 @@ const isExternal = (href: string) => href.startsWith('http');
</span>
</p>
<p class="site-footer__meta">
<span class="chip chip--version">Protocol {platform.protocol}</span>
<span class="chip chip--version">Bundle {platform.bundle.tag}</span>
{/* D66, as on the hero: the shared version only. Each game's protocol and bundle
are on that game's pages. */}
<span class="chip chip--version">Module API {platform.moduleApi}</span>
</p>
</div>
</div>

View File

@@ -53,7 +53,9 @@ const application = {
// client. Not "Windows" — the installer runs there, the platform does not require it.
operatingSystem: 'Linux, Windows, Android',
license: 'https://www.gnu.org/licenses/gpl-3.0.html',
softwareVersion: platform.bundle.tag,
// D66: the Module API is the one version every deployment shares. Each game has its own
// bundle, so no single bundle tag describes the platform any more.
softwareVersion: platform.moduleApi,
publisher: { '@id': url('/#organization') },
// Self-hosted and free, and `offers` is the only way the vocabulary can say so. Omitting
// it reads as "price unknown"; stating zero is simply true.

View File

@@ -8,9 +8,11 @@ import platform from '../../data/platform.json';
* 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 — one module
* exists, the second is a paper exercise, and the exit criterion for calling the contract
* proven is written down (§2, and the entries `/modules/` renders from `notBuilt.mjs`).
* 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
@@ -73,7 +75,7 @@ import platform from '../../data/platform.json';
<!-- 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 today</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>
@@ -130,11 +132,12 @@ import platform from '../../data/platform.json';
<section>
<h3>What this does not yet prove</h3>
<p>
One module exists and it is Ultima Online. A second, for a different game, is a
written dry-run that was deliberately never implemented — it exists to test whether
the contract generalises on paper. Until somebody builds the second one, the seam is
a well-argued design rather than a demonstrated one, and this site says so wherever
it comes up.
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>

View File

@@ -17,7 +17,7 @@ import { capabilityGroups, assertCapabilityCoverage } from '../../data/capabilit
* what exists while quietly omitting the well-known things that do not is the exact failure
* §1 is written to prevent.
*/
assertCapabilityCoverage(platform.moduleUoCapabilities);
assertCapabilityCoverage(platform.games.uo.module.capabilities);
---
<section class="page section caps">
@@ -52,7 +52,7 @@ assertCapabilityCoverage(platform.moduleUoCapabilities);
<p class="caps__foot prose">
Some things people reasonably expect are <strong>deliberately not built</strong> — a Matrix
integration, more than one game module active at once, a second game module. They are
integration, more than one game module active at once, a finished Integration Kit. They are
listed rather than left out, on <a href="/features/">features</a> and{' '}
<a href="/integrations/">integrations</a>.
</p>

View File

@@ -1,12 +1,10 @@
---
import platform from '../../data/platform.json';
/**
* The data path (PLAN.md §13 phase 3), drawn as inline SVG per §11's motif rule — hand-drawn
* geometry, used where it explains something, and no raster anywhere.
*
* ---------------------------------------------------------------------------------------
* THE LABELS ARE GENERIC, WITH UO AS THE CAPTION
* THE LABELS ARE GENERIC; THE CAPTION NAMES THE GAMES
* ---------------------------------------------------------------------------------------
* The org lead settled this before the diagram was drawn. The nodes say "your game server"
* and "sidecar", not "ServUO shard" and "uo-link", because §10's rule is that a reader
@@ -14,10 +12,12 @@ import platform from '../../data/platform.json';
* repositories in order to connect a game server — and because the tagline promises a
* platform, not a UO product.
*
* It does NOT hide what actually ships. The sub-labels and the caption name ServUO and
* uo-link outright, because §1 says the technical truth wins and today there is exactly one
* implementation of this shape. An operator running a shard has to see themselves in the
* picture on the first screen.
* It does NOT hide what actually ships: the caption names the games that ship on this shape,
* because §1 says the technical truth wins and an operator has to see their game in the
* picture on the first screen. Until phase 13 the sub-labels named ServUO and uo-link, which
* was true while there was exactly one implementation. With two, naming one would mislead,
* and D63 (PLAN.md §16) keeps each game's vocabulary on that game's own pages — so the
* caption names the games and the diagram names nothing else.
*
* ---------------------------------------------------------------------------------------
* WHY THE SVG IS aria-hidden
@@ -74,11 +74,11 @@ import platform from '../../data/platform.json';
<rect class="node" x="20" y="16" width="340" height="76" rx="12" />
<text class="node-title" x="42" y="50">Your game server</text>
<text class="node-sub" x="42" y="72">ServUO today · opens no inbound port</text>
<text class="node-sub" x="42" y="72">opens no inbound port</text>
<rect class="node" x="20" y="148" width="340" height="76" rx="12" />
<text class="node-title" x="42" y="182">Sidecar</text>
<text class="node-sub" x="42" y="204">uo-link · the only network-facing part</text>
<text class="node-sub" x="42" y="204">the only network-facing part</text>
<rect class="node node--self" x="20" y="280" width="340" height="76" rx="12" />
<text class="node-title" x="42" y="314">Runic Gateway</text>
@@ -90,8 +90,9 @@ import platform from '../../data/platform.json';
</svg>
<p class="datapath__caption">
Today that game server is a ServUO shard and that sidecar is uo-link. The shape is the
contract; the implementations are what plug into it.
Two games ship on this shape today, Ultima Online and Rust, each with its own plugin
and its own sidecar. The shape is the contract; the implementations are what plug
into it.
</p>
</div>
@@ -109,8 +110,8 @@ import platform from '../../data/platform.json';
<h3>The sidecar</h3>
<p>
A small service beside the game, and the only piece of the bridge anything else can
reach. It speaks a versioned wire protocol — protocol {platform.protocol} today — so
a mismatched pair is refused rather than misread, and it answers only your website's
reach. It speaks a versioned wire protocol — each game pins its own — so a
mismatched pair is refused rather than misread, and it answers only your website's
backend, over an authenticated WebSocket and REST.
</p>
</li>

View File

@@ -55,9 +55,9 @@ import platform from '../../data/platform.json';
</div>
<div class="chips">
<span class="chip chip--version">Protocol {platform.protocol}</span>
{/* D66: only the version every deployment shares. A protocol and a bundle belong
to a game, so they live on that game's pages rather than on the front door. */}
<span class="chip chip--version">Module API {platform.moduleApi}</span>
<span class="chip chip--version">Bundle {platform.bundle.tag}</span>
</div>
</div>