diff --git a/modules/rust/PLAN.md b/modules/rust/PLAN.md index 87f597b..c07e82c 100644 --- a/modules/rust/PLAN.md +++ b/modules/rust/PLAN.md @@ -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 0–3 produce a working read-only Rust site. 4–5 are the permissions product. 7–8 are events. -9 is the optional tier. Each phase ends with its findings written down, as every workstream here does. +Phases 0–3 produce a working read-only Rust site. 4–5 are the permissions product. 6 is Teams. 7–8 +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