docs(modules): R4 the installer grows a game dimension, R5 Teams come from first-party clans

R4 - Rust reaches an operator through the shipped installer behind
--game servuo|rust, not a second binary and not a shared-core refactor. The
shape of the work is set by where the coupling already is: service.rs and
main.rs mention ServUO zero times, while install.rs, doctor.rs, overlay.rs and
tier.rs carry nearly all of it, plus the bundle manifest where OverlayComponent
and ServUoCompat name the game in the schema itself. So the game payload becomes
a variant and ServUoCompat a per-game compat block - a schema change on the
published bundles branch, which is the part to design before touching code. The
Rust payload is much simpler than the UO one (no source tree, no patches, no
patch tier - one .cs into oxide/plugins) and what it gains instead is an Oxide
prerequisite check, which is doctor's shape rather than a new concept.

R5 - Teams come from Rust's FIRST-PARTY clans; the uMod Clans plugin is the
richer optional tier in phase 9. Three distinctions that are easy to collapse
and expensive to get wrong: the seven first-party clan hooks are all "no return
behavior", which is exactly what a read-only bridge wants; Rust's first-party
TEAMS are a different system entirely (twelve mostly-vetoable hooks, the
transient squad rather than the persistent organisation) and are not what core's
Team provider should be fed; and the uMod Clans plugin's API is not in our
mirror at all, since HOOKS.md is the game's 477 hooks and a plugin's API is its
own documentation.

The gap R5 has to design around: the seven hooks carry created, disbanded,
added, kicked and left, but no promote or leader-changed event. So
getTeamLeaders reads leadership off LocalClan at snapshot time, which makes
phase 6 partly snapshot-driven where the dry run predicted fully event-driven.
Recorded as a correction to that document rather than silently.

Also records the start.bat fix on the rig: it now updates D:\rust rather than
C:\rustserver, the original is kept at start.bat.bak, and the appmanifest
already present at the same buildid means the first corrected run is a delta
rather than a 5.9 GB re-download.

Phases are now 0-11; the installer is phase 10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-15 11:32:43 -05:00
parent a5881d5a55
commit 7d589ff5f2

View File

@@ -1,6 +1,6 @@
# `module-rust` — the plan
**Status:** approved in outline 2026-09-15, not started. Three decisions of record settled; one
**Status:** approved in outline 2026-09-15, not started. Five decisions of record settled; one
question open (§3).
The [dry run](../rust-dryrun.md) designed this module on paper and deliberately did not build it.
@@ -112,6 +112,60 @@ Two details from [kit][kit] ch. 5 that this depends on and are easy to get wrong
`budgetMs`, our own `retry: false` is unreachable code. Derive one constant from the other and
assert the inequality in a test. The first module this project shipped had exactly that pairing.
### R4 — Rust reaches an operator through the existing installer, behind `--game`
**Decided 2026-09-15 (org lead).** Not a second binary and not a shared-core refactor: the shipped
[`installer`](../../installer/PLAN.md) grows a game dimension, `--game servuo|rust`, and keeps one
release stream, one `doctor`, one `update`, one `uninstall`.
The shape of the work is set by where the coupling already is. **The reusable half is already
game-agnostic** — `src/service.rs` and `src/main.rs` mention ServUO zero times, and `net.rs`,
`diff.rs`, `paths.rs` and `ui.rs` barely more. **The UO-specific half is concentrated in four files**
`install.rs`, `doctor.rs`, `overlay.rs`, `tier.rs` — plus the bundle manifest, where
`OverlayComponent` and `ServUoCompat` name the game in the schema itself.
So the change is: make the bundle's game payload a **variant** rather than an overlay, and
`ServUoCompat` a per-game compat block. That is a schema change on the published **`bundles`**
branch, and it is the part to design before touching code.
**The Rust payload is much simpler than the UO one**, which is what makes this affordable: no source
tree to overlay, no `patches/`, no opt-in patch tier — a Rust install is a `.cs` file dropped into
`oxide/plugins/`, plus the sidecar and its service, which the installer already knows how to do. What
it gains instead is a **prerequisite check**: is Oxide installed, and is its build current enough.
That is `doctor`'s shape, not a new concept.
The protocol pairing check generalises unchanged. `servuo-plugins/overlay.toml` declares the protocol
the overlay speaks and the installer refuses to pair a disagreeing sidecar; `rust-plugins` needs the
same declaration under whatever name the variant gives it, and the refusal is the same refusal.
### R5 — Teams come from Rust's first-party clans; the uMod Clans plugin is the richer tier
**Decided 2026-09-15 (org lead).** Basic functionality is built on **Rust's own clan system**; the
third-party **uMod Clans plugin** is an optional layer for a much richer experience, and lands in
phase 9 rather than phase 6.
The distinction matters more than the names suggest, so it is worth stating precisely:
- **Rust's first-party clans** are seven hooks in [`HOOKS.md`](HOOKS.md) — `OnClanCreated`,
`OnClanDisbanded`, `OnClanMemberAdded`, `OnClanMemberKicked`, `OnClanMemberLeft`, plus colour and
logo — each handing you a `LocalClan`. **All seven are "no return behavior"**, which is exactly
what a read-only bridge wants: there is nothing to abstain from, so [kit][kit] ch. 4's
return-`null`-from-every-veto rule has no work to do here. They need no plugin, and the rig is
already running them (`D:\rust\server\server1\clans.287.db`).
- **Rust's first-party *Teams* are a different system** — twelve hooks, mostly vetoable — and are the
transient in-game squad, not the persistent organisation. They are **not** what core's Team
provider should be fed. Clans is the right choice and this is why.
- **The uMod Clans plugin's API is not in our mirror.** [`HOOKS.md`](HOOKS.md) is the *game's* 477
hooks; a plugin's own API is its own documentation. It is reached through `[PluginReference]` and
is null when absent, making it an R3-shaped dependency that must refuse with a reason. It is also
not currently staged locally and would need pulling and mirroring.
**The one gap to design around:** the seven first-party hooks carry created, disbanded, added,
kicked and left — but **no promote or leader-changed event**. Core's provider requires
`getTeamLeaders`, so leadership is read off `LocalClan` at snapshot time rather than tracked from
transitions. That makes phase 6 *partly* snapshot-driven where the dry run predicted it would be
fully event-driven — a small correction to that document, recorded here rather than silently.
## 3. Open question
**Which other uMod plugins are in the required base set?** Kits is named. The rest of "a couple" is
@@ -129,10 +183,13 @@ means R2's mechanism can be exercised on day one.
Two traps recorded here because both cost time before they were understood:
- **`D:\rust\start.bat` updates the wrong directory.** It runs
`steamcmd +force_install_dir c:\rustserver\ +app_update 258550` and then launches
`D:\rust\RustDedicated.exe`. The server that boots has never been updated by its own script. That
- **`D:\rust\start.bat` updated the wrong directory — fixed 2026-09-15.** It ran
`steamcmd +force_install_dir c:\rustserver\ +app_update 258550` and then launched
`D:\rust\RustDedicated.exe`. The server that boots had never been updated by its own script, which
is why a second, never-booted install exists at `C:\rustserver` and why `D:\rust` is a wipe behind.
Now reads `+force_install_dir d:\rust\`; the original is kept at `D:\rust\start.bat.bak`.
`D:\rust\steamapps\appmanifest_258550.acf` was already present at the same buildid, so the first
corrected run is a delta to the current wipe rather than a 5.9 GB re-download.
- **`C:\oxide_files` is a 2025-04-23 Oxide and must not be copied anywhere.** Oxide ships a patched
`Assembly-CSharp.dll`; that bundle's is 6,842,880 bytes against the live 9,780,224, so copying it
over a real install is a hard downgrade. `D:\rust` is already correct and needs nothing from it.
@@ -143,22 +200,24 @@ re-checks it.
## 5. The phases
Phases 03 produce a working read-only Rust site. 45 are the permissions product. 78 are events.
9 is the optional tier. Each phase ends with its findings written down, as every workstream here does.
Phases 03 produce a working read-only Rust site. 45 are the permissions product. 6 is Teams. 78
are events. 9 is the optional tier. 10 is how any of it reaches somebody who is not us. Each phase
ends with its findings written down, as every workstream here does.
| # | Phase | Repos | Done when |
|---|---|---|---|
| 0 | **The rig.** Fix `start.bat`, update to the current wipe, confirm the Oxide build still matches, install the base set, prove a grant made at the console is visible to a plugin | docs | A current server boots with the base mods loaded and `oxide.grant` demonstrably gates something |
| 0 | **The rig.** Update to the current wipe (the script is fixed), confirm the Oxide build still matches, install the base set, prove a grant made at the console is visible to a plugin | docs | A current server boots with the base mods loaded and `oxide.grant` demonstrably gates something |
| 1 | **Protocol 1 and three skeletons.** Bounded drop-oldest queue, one writer thread, reconnect with a tagged epoch, dial-out; sidecar listener + SQLite + always-on token auth + version header; module from `template/` | all 3 + docs | One hello line travels game → sidecar → module, and killing the sidecar does not stall the game |
| 2 | **The read path.** First hook wave from [`HOOKS.md`](HOOKS.md); events and snapshots kept distinct at the wire; `wipe_id` everywhere; every board re-emitted on connect | all 3 + docs | A restarted sidecar is fully populated within one connection, with no negotiation |
| 3 | **The first pages.** Server list, per-server status, killfeed, leaderboard; `capabilities`; nav | Module-rust | The site renders the last thing the game said while the game is off |
| 4 | **Identity** (R1) | 3 + docs | A player links an account in-game and the site names their Steam ID |
| 5 | **Site-owned permissions** (R2). Groups and grants authored on the site; full set pushed on connect, deltas after; drift reported | all 3 + docs | A grant made on the website gates a third-party plugin in-game, and survives a wipe |
| 6 | **Teams provider.** Event-driven with a baseline on connect — Rust delivers membership transitions in real time, so no sweep | Module-rust + 2 | Core's reconciler is answered from live transitions, `complete` claimed only per reachable server |
| 6 | **Teams provider** (R5). Rust's first-party clans; membership event-driven, leadership read off `LocalClan` at snapshot | Module-rust + 2 | Core's reconciler is answered from live transitions, `complete` claimed only per reachable server |
| 7 | **Events: one budget, one lease.** [kit][kit] ch. 5's own ordering — the lease before the action | Module-rust + 2 | The leased value is observed changing in the running game and restored, per key |
| 8 | **Events: the Kits reward action** (R3) | all 3 | A retried step grants loot once, and the ledger and the world agree |
| 9 | **Optional mod integrations.** One adapter per plugin, each detecting via `[PluginReference]` and degrading to absent | rust-plugins + docs | A server missing every optional mod still runs the module |
| 10 | **Docs, kit feedback, cutover** | docs + Integration-kit | `docs/` describes what shipped; R2's missing chapter answered either way |
| 9 | **Optional mod integrations**, the uMod **Clans** plugin first (R5). One adapter per plugin, each detecting via `[PluginReference]` and degrading to absent | rust-plugins + docs | A server missing every optional mod still runs the module, Teams included |
| 10 | **The installer** (R4). `--game servuo|rust`, the bundle payload as a variant, an Oxide prerequisite check in `doctor`, the protocol pairing refusal carried over | installer + docs | An operator sets a Rust server up with the released binary and nothing hand-copied |
| 11 | **Docs, kit feedback, cutover** | docs + Integration-kit | `docs/` describes what shipped; R2's missing chapter answered either way |
### Why the lease comes before the reward action