diff --git a/PLAN.md b/PLAN.md index 6586752..d6b22db 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,8 +1,8 @@ # runicgateway.com — design of record -**Status:** decisions taken by the org lead on 2026-08-19 and recorded in §5. **Phase 0 is -complete**; Phase 1 is the next build step. -**Date:** 2026-08-19 (revision 3) +**Status:** phases 0–12 are built and deployed. **Revision 4 (2026-10-22) plans phases 13–18** — +the second game, RunicNPC, and taking game vocabulary out of the site at large — in §16. +**Date:** 2026-08-19 (revision 3) · 2026-10-22 (revision 4) **Repo:** `RunicGateway/runicgateway.com` **AI-assisted:** yes — drafted by Claude, per the org's AI-usage disclosure policy. @@ -35,6 +35,7 @@ existing one rather than delay the beta. 13. [Build phases](#13-build-phases) 14. [Still needed from the org lead](#14-still-needed-from-the-org-lead) 15. [Planned, not in scope: the demo instance](#15-planned-not-in-scope-the-demo-instance) +16. [A second game: Rust, RunicNPC, and a vocabulary rule](#16-a-second-game-rust-runicnpc-and-a-vocabulary-rule) --- @@ -236,7 +237,7 @@ Taken by the org lead (Colby Whitlock) on 2026-08-19. Recorded so they are not r **Decisions after D13 are recorded where they were taken**, in the section describing the phase that raised them, rather than appended here — a decision is only re-litigated when its reasoning is -somewhere other than the thing it decided. The count of record is **fifty-nine**: +somewhere other than the thing it decided. The count of record is **sixty-five**: | # | Where | What it settled | |---|---|---| @@ -253,6 +254,7 @@ somewhere other than the thing it decided. The count of record is **fifty-nine** | D54–D57 | §6, "How phase 12 delivered it" | A merge deploys (amending D6), the proxy is documented by its requirements rather than by an example, the container trusts the forwarded headers with nothing to configure, and the operator note is its own file while the two policy files are pointers | | D58 | §6, "How phase 12 delivered it" | `node_modules` ships in three layers because Cloudflare refuses a request body over 100 MB, and the workflow counts layers before it pushes | | D59 | §6, "How phase 12 delivered it" | The container publishes on every interface (amending D55), with `SITE_BIND_ADDR` to narrow it | +| D60–D65 | §16, "The six decisions" | Per-game sidebar groups, RunicNPC inside Rust with its own page, the RunicNPC API carried in full with a drift check, game names allowed but game vocabulary confined, a twelfth check that enforces it (amending D53), Rust screenshots last and from the rigs | --- @@ -1700,6 +1702,18 @@ not publish it: Cloudflare rejected the largest layer with `413 Payload Too Larg stayed empty and the deploy never ran. See **D58** — the layer is split, and the workflow now counts layers before it pushes. +**Revision 4 adds six more** (§16), one pull request each, with a pause for the org lead between +them: + +| Phase | Deliverable | +|---|---| +| **13** | Facts: `platform.json` per game, `checkFacts.mjs` reading the per-game bundles, the two values already red on `main`, and the absences the second game resolves | +| **14** | Vocabulary: game-neutral core docs, an **Ultima Online** docs group, redirects for every moved page, and `checkTerms.mjs` (D64) over the docs | +| **15** | The **Rust** docs group | +| **16** | **RunicNPC**: its docs subgroup inside Rust and the full API reference with its drift check (D62) | +| **17** | Marketing: per-game pages, `/runicnpc/`, the homepage, `/features/`, `/modules/`, `/app/`, the architecture drawings, and `checkTerms.mjs` widened to all of `src/` | +| **18** | Rust screenshots from the rigs (D65) | + --- ## 14. Still needed from the org lead @@ -1807,6 +1821,161 @@ line in a bind-mounted file — no rebuild, consistent with §7. --- +## 16. A second game: Rust, RunicNPC, and a vocabulary rule + +Revision 4, 2026-10-22. The org lead's brief, in their words: *update the site for the Rust module, +include RunicNPC documentation, and keep terms like shard or clan — the game-specific terms — in +that module only, not on the site at large.* + +### What changed since the site was built + +All values below were read on 2026-10-22 from `origin/main`, the Gitea API, and the installer's +`bundles` branch. The site was last fact-checked on 2026-09-15. + +| | Now | The site says | +|---|---|---| +| Module API | **1.17.0** (`website` `main:server/src/modules/version.js`) | 1.10.0, so `checkFacts` is **already red on `main`** | +| Installer | **v0.8.0** | v0.2.0, also red | +| `Module-Rust` | **v0.5.0**: id `rust`, `coreApi ^1.17.0`, capabilities `rust servers killfeed leaderboard presence wipes identity map` | nothing | +| Rust bundle | **`v2/rust/current.json`**, bundle **2026.10.22**, protocol **14**: Rust-Link v0.3.0, Rust-Plugins v0.3.0, Oxide ≥ 2.0.7585, Carbon ≥ 2.0.259, requires Kits + ZoneManager, plus RunicNPC v1.0.0 | nothing | +| UO bundle | **`v2/servuo/current.json`**. The root `current.json` is the schema-1 manifest, still at 2026.09.15 | read from the root | +| RunicNPC | **v1.0.0**: an Oxide/Carbon plugin with its own API (`docs/runicnpc/API.md`), `/rnpc` commands, and standalone use without Runic Gateway | nothing | +| Second module | **Exists and is released**. Core now *refuses* a second game module at boot | "a paper dry-run, deliberately unimplemented" | + +Two of the §2 absences are therefore now false. **`second-module` is resolved**, and its own +`resolvedBy` names exactly this event. **`multi-module` needs its wording changed**: it is no longer +"nothing exercises the columns" but "core refuses to boot a second game module". The +**Integration Kit's draft banner stays**: the kit still says it is a draft, and its condition is +someone *outside this project* building a module from it. D8 mirrors the kit and does not +overrule it. + +### The vocabulary problem, measured + +"Shard" appears **227 times in 45 files**. It is not only in the UO material: the whole +*Getting started* path is a ServUO walkthrough (`connect-a-game-server` alone uses the word 14 +times). The homepage diagram's box reads "ServUO today", and the Reference group has a "Shard event +catalog". The product never had this problem: core's client contains no "Shard" label at all, and +"Admin → Shard (uo-link)" is a menu item **module-uo** registers. So the site is less accurate +than the product here, and the fix is a correction rather than a rebrand. + +### The six decisions + +Taken by the org lead on 2026-10-22, each from a concrete example. + +**D60 — per-game sidebar groups.** *Getting started* becomes game-neutral and ends in "Connect your +game server", which sends the reader to their game. Two new groups sit after *Reference*: + +- **Ultima Online** takes everything that is UO today: connecting a ServUO shard, the shard + connection, client files, `Bridge.cfg`, uo-link's `sidecar.toml`, and the shard event catalog. +- **Rust** covers installing the bridge (installer, Pterodactyl egg, by hand), connecting the site, + linking Steam accounts, permissions and groups, mod config from the site, events, zones and + rewards, the live map, and troubleshooting. + +*Administration*, *Modules*, *Architecture* and *Reference* keep only core material, described in +core's own words. Every page that moves gets a redirect from its old URL. The alternative, a +separate sidebar per game under `/docs/uo/` and `/docs/rust/`, was not taken. + +**D61 — RunicNPC lives inside Rust and also has its own page.** Its docs are a subgroup of the Rust +group: overview, install (with and without Runic Gateway), the `/rnpc` commands, managing NPCs from +the site's profile and placement screens, and the API. Marketing gets a RunicNPC card on the Rust +module page and a short standalone `/runicnpc/` page. The plugin works on a Rust server that runs no +Runic Gateway at all, and that server's owner should be able to find it without first learning what +the platform is. + +**D62 — the RunicNPC API is carried in full, with a drift check.** This is a **deliberate exception +to §1**, which links contracts rather than copying them. The site renders every call, every hook it +raises, and every data shape. A drift check, landing as a section of `checkReference.mjs`, reads +`docs` `main:runicnpc/API.md` through the `contents` endpoint (§12) and fails if the two disagree: a call, a hook, or a signature that exists on one side +only, or a signature that differs. It is two-directional like `checkQuickstart.mjs`. +`docs/runicnpc/API.md` stays normative, and the site page says so at the top. The reason for the +exception: the API's readers are plugin authors outside this project, and a page they can search and +read in the site's own docs is the difference between using the plugin and not. + +**D63 — game names yes, game vocabulary no.** Outside the game sections the site may name the games +it supports, as product names. It may not use their vocabulary. Concretely: + +- The homepage diagram's box reads "Game server · opens no inbound port". A **"Supported games: + Ultima Online · Rust"** strip on the homepage, `/features/` and `/modules/` links to each game's + page. +- Words such as *shard, guild, clan, wipe, ServUO, uo-link, Bridge.cfg, cliloc, Oxide, Carbon, + uMod, rust-link* appear only in the Ultima Online and Rust sections, their per-game marketing + pages, and data files that belong to one game. +- Core concepts keep core's names: a **Team** (never a guild or clan), a **game server** (never a + shard), **the sidecar** and **the bridge plugin** (never uo-link or rust-link), **the game + module** (never module-uo). Steam is not on the list: Steam sign-in is a core identity provider + (`website` `server/src/auth/identityHooks.js`), even though Rust is its first user. + +**D64 — a twelfth check enforces D63, amending D53.** D53 declined a twelfth check because nothing +then needed one. This does, for the same reason `checkTokens.mjs` exists: a convention that holds +only while everyone remembers it does not hold. `scripts/checkTerms.mjs` keeps a list of game words, +each tagged with its game. It fails the build, naming the file, line and word, when one appears +outside that game's allowed paths. The allowlist is short and justified line by line. Its first +known entry is Discord's own term: the Discord bot settings really do ask for a *guild ID*, and +that is Discord's word, not a game's. Phase 14 runs the check over the docs; phase 17 widens it to +all of `src/` once the marketing pages have somewhere to move their game content. + +**D65 — Rust screenshots come last, from the rigs.** D4 still holds: real captures only. Every Rust +page is written first, without screenshot slots and never with placeholders. Phase 18 then brings the +rigs up to date (Rust, Oxide and Carbon, as the rig rule requires before any rig work), points the +walk site at them, and captures through `scripts/captureScreens.mjs`, the same pipeline as phase 9 +(D45). Candidates: the server list, the live map, the kill feed, the leaderboard, the permissions +screen and the NPC profile editor. + +### One rule the phases need, recorded so review can object + +**A screenshot belongs to its game.** Phase 9's UO captures carry the word "Shard" in the pixels and +in their alt text. Alt text has to describe the image honestly, so a capture tagged with a game in +`screens.mjs` may use that game's words in its caption and alt. The page showing it must name the +game next to it. `checkTerms.mjs` reads the tag. The homepage's "What it looks like" therefore either +shows a core screen, or shows a game screen labelled with its game. Phase 17 picks one and says which. + +### The phases + +Each phase is one pull request into `main`, with a pause for the org lead before the next. + +**Phase 13 — facts.** `platform.json` grows a `games` object, with `uo` and `rust` each holding +their bundle tag, protocol, component pins, framework minimums, module release and capability list, +and a `runicnpc` entry holding its release. `checkFacts.mjs` reads `v2/servuo/current.json` and +`v2/rust/current.json` instead of the root manifest. It re-reads `Module-Rust` `main:module.json` +the same way it reads module-uo's, and checks the RunicNPC release. Module API and installer go to +their current values, and every page quoting them is re-read (§1). `notBuilt.mjs` drops +`second-module` and rewords `multi-module`. `capabilities.mjs`'s coverage assertion gains its Rust +half, so a capability Module-Rust adds turns the site red the same way a UO one does. **This is the +phase that turns `main` green again.** + +**Phase 14 — vocabulary and the Ultima Online group.** Getting started is rewritten game-neutral, +and its "connect" step becomes a short page that hands off to each game. The UO pages move into the +new group, each with a redirect from its old URL. Every remaining core page is swept to core's +words. `checkSidebar.mjs`'s `plannedSidebar` gains the new group. `checkTerms.mjs` lands, scoped to +`src/content/docs` and `src/config/sidebar.mjs`. `checkReference.mjs`'s entries for `Bridge.cfg`, +`sidecar.toml` and the event catalog follow their pages. + +**Phase 15 — the Rust group.** Journey pages written from `docs/rust-link/INSTALL.md`, +`INTEGRATION.md`, `docs/modules/rust/PLAN.md` and its redesign and fix plans, and `OPERATING.md`. +They teach and link out (§1). The Rust reference pages (rust-link's `sidecar.toml`, the bridge +plugin's config, and the protocol-14 event list) are enumerated and checked against their sources +the way D39 does for UO, extending `checkReference.mjs` rather than adding a check. Every admin +screen described is walked on the walk site first (D36). + +**Phase 16 — RunicNPC.** The subgroup: overview, install, commands, managing NPCs from the site, and +the API reference (D62), with its drift check. That check is the one D62 adds; the count stays at +twelve because it lands as a section of `checkReference.mjs`, which already reads `docs` through +`contents`. + +**Phase 17 — marketing.** `/modules/uo/` and `/modules/rust/` carry each game's own feature list, +written in that game's words. `/runicnpc/` is the standalone page (D61). `/modules/` gains Rust +beside UO. `/features/`'s module-supplied group becomes "what a game module adds", linking to both. +The homepage diagram and caption, `/architecture/`'s two drawings, `/app/` (the Android app has +Rust screens too), `/community/`'s "there is one module" passage and the supported-games strip +(D63) are updated. `collection.mjs` and the Play Data Safety notes are re-read against what the Rust +module collects, Steam ids first, before anything is claimed there. `checkTerms.mjs` widens to all of +`src/`. + +**Phase 18 — screenshots** (D65), plus the emulator pass for `/app/`'s Rust screens. This is the only +phase that needs the rigs. + +--- + [api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md [system]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_SYSTEM.md [int]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/INTEGRATION.md