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
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:
@@ -41,6 +41,10 @@ Two consequences of how boots work:
|
||||
Paste the new release's install-manifest URL and press Install. The bundle is verified
|
||||
against its `sha256`, unpacked over the old one, and takes effect at the restart.
|
||||
|
||||
**A site runs one module.** Installing a module with a different id beside the one you have
|
||||
is refused with `409`, and nothing is downloaded. To change game, remove the installed module
|
||||
first.
|
||||
|
||||
An upgrade **deliberately leaves the state alone**: upgrading an enabled module must not
|
||||
silently switch it off, and re-installing a disabled one must not silently switch it on.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ on:
|
||||
| **Base URL (REST)** | `http://<shard host>:8080` — point-in-time queries |
|
||||
| **WebSocket URL (feed)** | `ws://<shard host>:8080/ws` — the live event feed |
|
||||
| **Auth token** | The sidecar's token |
|
||||
| **Protocol** | {platform.protocol} today |
|
||||
| **Protocol** | {platform.games.uo.protocol} today |
|
||||
|
||||
Saving restarts the ingest client, so a change takes effect immediately.
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ The loopback wire protocol between the game plugin and the sidecar is a **versio
|
||||
compatibility contract**, not a build dependency. Nothing compiles the three sides together,
|
||||
so the number is what stops a mismatch from being discovered as corrupted data.
|
||||
|
||||
The current protocol is **{platform.protocol}**.
|
||||
Each game has its own protocol, numbered independently: {platform.games.uo.name} is on
|
||||
**{platform.games.uo.protocol}** and {platform.games.rust.name} on
|
||||
**{platform.games.rust.protocol}**. The two numbers are not comparable — they version two
|
||||
different wires. The examples below are the {platform.games.uo.name} bridge's; the
|
||||
{platform.games.rust.name} bridge declares its number in the same three kinds of place.
|
||||
|
||||
## Three declaration sites
|
||||
|
||||
@@ -18,8 +22,8 @@ The same number is written down in three places, and they must move together.
|
||||
|
||||
| Where | What declares it |
|
||||
|---|---|
|
||||
| `link/sidecar/src/main.rs` | `PROTOCOL_VERSION`, currently {platform.protocol} — what the sidecar speaks |
|
||||
| `servuo-plugins/overlay.toml` | `protocol`, currently {platform.protocol} — what the plugin overlay speaks |
|
||||
| `link/sidecar/src/main.rs` | `PROTOCOL_VERSION`, currently {platform.games.uo.protocol} — what the sidecar speaks |
|
||||
| `servuo-plugins/overlay.toml` | `protocol`, currently {platform.games.uo.protocol} — what the plugin overlay speaks |
|
||||
| The bundle manifest | Copied from `overlay.toml` by CI, so a released pair carries its own claim |
|
||||
|
||||
<Aside type="caution" title="Bump the overlay in the same PR as the emitters">
|
||||
|
||||
@@ -71,7 +71,7 @@ internet; only the sidecar is exposed, and only to your site.
|
||||
<Aside type="note" title="It installs a bundle, not “latest of each”">
|
||||
The three components version independently but must agree on one wire protocol, so what it
|
||||
resolves is a **bundle**: an exact, protocol-checked pair of sidecar and overlay versions
|
||||
({platform.bundle.tag} today — sidecar {platform.bundle.sidecar}, overlay {platform.bundle.overlay}).
|
||||
({platform.games.uo.bundle.tag} today — sidecar {platform.games.uo.bundle.sidecar}, overlay {platform.games.uo.bundle.overlay}).
|
||||
`--bundle <tag>` pins an exact past combination, so a reinstall in six months reproduces
|
||||
today's install rather than tomorrow's.
|
||||
</Aside>
|
||||
@@ -83,7 +83,7 @@ features need edits to stock ServUO sources, and those are opt-in, off unless yo
|
||||
and refused where the target lines are not stock. Skipping the tier costs you vendor-sale
|
||||
events and in-game moderation audit forwarding; everything else works.
|
||||
|
||||
The tier is written and tested against stock ServUO {platform.bundle.servuoMin}. On any
|
||||
The tier is written and tested against stock ServUO {platform.games.uo.bundle.servuoMin}. On any
|
||||
other version it is unsupported and untested, and the prompt makes you answer past a
|
||||
warning.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Watch the log at the restart and you will see exactly what it mounted:
|
||||
[modules] module "uo" started
|
||||
```
|
||||
|
||||
Its capabilities are {platform.moduleUoCapabilities.join(', ')} — the shard console, the
|
||||
Its capabilities are {platform.games.uo.module.capabilities.join(', ')} — the shard console, the
|
||||
map atlas, the player-vendor marketplace, city governors, guilds, houses and IDOCs, champion
|
||||
boards, and the cliloc strings that make item names readable.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ the [`uo` module](/docs/getting-started/install-a-game-module/) and the installe
|
||||
| Requirement | Detail |
|
||||
|---|---|
|
||||
| **A working ServUO install** | It must currently boot and compile scripts cleanly. The installer deploys onto a healthy shard; it does not repair a broken one. |
|
||||
| **ServUO {platform.bundle.servuoMin}** *(patch tier only)* | The base install works on any reasonably current ServUO. The optional patch tier is written and tested against stock {platform.bundle.servuoMin}; on any other version it is unsupported, and skipping it still leaves you with a working bridge. |
|
||||
| **ServUO {platform.games.uo.bundle.servuoMin}** *(patch tier only)* | The base install works on any reasonably current ServUO. The optional patch tier is written and tested against stock {platform.games.uo.bundle.servuoMin}; on any other version it is unsupported, and skipping it still leaves you with a working bridge. |
|
||||
| **`libgdiplus`** *(Linux shard hosts only)* | Only needed for **artwork**. ServUO runs under Mono on Linux, and the library it decodes sprites with is a thin layer over this one — in the decode path, not merely the encode. Without it creature portraits and item pictures stay empty, and names and the spawn atlas are unaffected because neither touches a pixel. `sudo apt-get install libgdiplus`; `doctor` checks for it. **Windows hosts need nothing.** |
|
||||
| **The shard stopped** | `ServUO.exe` locks `Scripts.dll` and rewrites `Saves/` on exit. The installer refuses to deploy under a running shard. |
|
||||
| **Administrator / root** | It writes into system directories and registers a service. |
|
||||
|
||||
@@ -15,19 +15,32 @@ public event stream.
|
||||
|
||||
## What the platform is on today
|
||||
|
||||
Two versions are shared by every deployment, whichever game it runs:
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td>Wire protocol</td><td>{platform.protocol}</td></tr>
|
||||
<tr><td>Module API</td><td>{platform.moduleApi}</td></tr>
|
||||
<tr><td>Current bundle</td><td>{platform.bundle.tag}</td></tr>
|
||||
<tr><td>uo-link sidecar</td><td>{platform.releases.link}</td></tr>
|
||||
<tr><td>Plugin overlay</td><td>{platform.bundle.overlay}</td></tr>
|
||||
<tr><td>Installer</td><td>{platform.releases.installer}</td></tr>
|
||||
<tr><td>ServUO (minimum)</td><td>{platform.bundle.servuoMin}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Every value in that table is read from `src/data/platform.json` and re-checked against its
|
||||
Everything else belongs to a game. Each game has its own wire protocol and its own bundle —
|
||||
the exact, protocol-checked pair of sidecar and game plugin the installer deploys:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th></th><th>{platform.games.uo.name}</th><th>{platform.games.rust.name}</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Game module</td><td>{platform.releases['Module-uo']}</td><td>{platform.releases['Module-Rust']}</td></tr>
|
||||
<tr><td>Wire protocol</td><td>{platform.games.uo.protocol}</td><td>{platform.games.rust.protocol}</td></tr>
|
||||
<tr><td>Current bundle</td><td>{platform.games.uo.bundle.tag}</td><td>{platform.games.rust.bundle.tag}</td></tr>
|
||||
<tr><td>Sidecar</td><td>{platform.games.uo.bundle.sidecar}</td><td>{platform.games.rust.bundle.sidecar}</td></tr>
|
||||
<tr><td>Game plugin</td><td>{platform.games.uo.bundle.overlay}</td><td>{platform.games.rust.bundle.plugin}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Every value in those tables is read from `src/data/platform.json` and re-checked against its
|
||||
source of truth on each build. None of it is typed into prose — including here.
|
||||
|
||||
## An install is two installs
|
||||
@@ -39,8 +52,8 @@ two independent deployments.
|
||||
2. **The shard side** is the installer binary, run on the game server's host. It sets up
|
||||
the plugin overlay and the sidecar, and it never contacts the website.
|
||||
|
||||
They meet at four values pasted into the module's shard screen, and at protocol
|
||||
{platform.protocol}, which both sides check before they will pair.
|
||||
They meet at the values the installer prints, pasted into the game module's connection
|
||||
screen, and at the game's wire protocol, which both sides check before they will pair.
|
||||
|
||||
You can stop after the first one. A site with no game server attached is a complete
|
||||
community website — news, wiki, pages, Teams, forums, accounts, moderation and the event
|
||||
|
||||
@@ -29,6 +29,13 @@ Two artifacts:
|
||||
The manifest URL is the thing an operator pastes. The bundle is downloaded, **verified
|
||||
against the `sha256`**, and unpacked into `modules/<id>/` on the mounted volume.
|
||||
|
||||
<Aside type="caution" title="A site runs one module">
|
||||
An install whose `id` differs from a module already on the volume is refused with `409`
|
||||
before anything is downloaded. Upgrading the installed module is the only install a
|
||||
populated site accepts; to change game, remove the module first. The loader enforces the
|
||||
same rule at boot — see [Module lifecycle](/docs/modules/module-lifecycle/#the-scan).
|
||||
</Aside>
|
||||
|
||||
Nothing is compiled at any point in that sequence.
|
||||
|
||||
## The two surfaces
|
||||
|
||||
@@ -18,10 +18,17 @@ time**. The database is not consulted: what is on the volume determines what mou
|
||||
missing modules directory is not an error** — "no modules installed" is the normal state of
|
||||
bare core, and the loader must not make the mount mandatory to boot.
|
||||
|
||||
Modules load **alphabetically by `id`**, deterministically. There is no dependency
|
||||
resolution between modules, and alphabetical order is the honest way of saying so: any
|
||||
other order would imply a precedence nobody is computing. Do not build a module that needs
|
||||
to load before or after another one.
|
||||
**A site runs one module.** The scan is alphabetical by directory name, and the first
|
||||
module directory it finds holds the site, whether or not that module then loads. Every
|
||||
other module directory is recorded `startup_failed` at the manifest stage **before any of
|
||||
its code is required**: no routes, navigation, tables or registrations. Its reason, in the
|
||||
log and on **Admin → Modules**, names both modules and says to remove one. The installer
|
||||
refuses the same thing earlier — an install whose id differs from the module already on the
|
||||
volume answers `409` before anything is downloaded — so the loader's rule is what catches a
|
||||
directory copied onto the volume by hand.
|
||||
|
||||
There is no dependency resolution between modules, because there is only ever one running.
|
||||
Do not build a module that expects another to load beside it.
|
||||
|
||||
## Validation, in order
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@ your game server ──dials out──▶ your sidecar ──HTTP + WS──
|
||||
Before any of them, the kit points at the [Rust dry
|
||||
run](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/rust-dryrun.md)
|
||||
— a complete module designed on paper for a second game, and the shortest honest picture of
|
||||
the whole job.
|
||||
the whole job. That module has since been built for real, as
|
||||
[`Module-Rust`](https://gitea.whitlocktech.com/RunicGateway/Module-Rust), so the dry run now
|
||||
reads as the plan beside a finished result.
|
||||
|
||||
## The template is built, not just quoted
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ description: The two arguments core hands your module — what you can reach, wh
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
Your server entry point exports one function:
|
||||
|
||||
@@ -14,7 +15,7 @@ module.exports = function register(ctx, api) { /* … */ }
|
||||
`ctx` is what core lends you. `api` is what you register with it. Everything crossing the
|
||||
module boundary goes through one of the two.
|
||||
|
||||
The contract version is **`MODULE_API_VERSION`**, currently **1.6.0**, and your manifest's
|
||||
The contract version is **`MODULE_API_VERSION`**, currently **{platform.moduleApi}**, and your manifest's
|
||||
[`coreApi` range](/docs/modules/the-module-manifest/#coreapi-and-what-a-range-means) is
|
||||
checked against it before your code is required.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ description: Every key in module.json, what the loader does with each, and why a
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import platform from '../../../../data/platform.json';
|
||||
|
||||
`module.json` sits at the root of your bundle. The loader reads it synchronously, before
|
||||
anything else about your module runs.
|
||||
@@ -78,12 +79,14 @@ repository is branching on them.
|
||||
|
||||
## `coreApi` and what a range means
|
||||
|
||||
Core exports a single semver string, currently **1.6.0**. Your range is checked at boot,
|
||||
before your code is required.
|
||||
Core exports a single semver string, currently **{platform.moduleApi}**. Your range is
|
||||
checked at boot, before your code is required.
|
||||
|
||||
A **minor** bump adds members without removing any or changing a signature, so `^1.3.0`
|
||||
keeps resolving against 1.6.0 — which is exactly why `module-uo` still declares `^1.3.0`
|
||||
and runs fine.
|
||||
A **minor** bump adds members without removing any or changing a signature, so an older
|
||||
caret range keeps resolving against a newer core. The {platform.games.uo.name} module
|
||||
declares <code>{platform.games.uo.module.coreApi}</code> and runs on {platform.moduleApi};
|
||||
the {platform.games.rust.name} module declares
|
||||
<code>{platform.games.rust.module.coreApi}</code>.
|
||||
|
||||
Use a caret range against the oldest core you actually support and test against. Pinning
|
||||
exactly buys nothing and strands you on the next additive release.
|
||||
|
||||
Reference in New Issue
Block a user