feat(drill): the staging drill, the week before each forced wipe (stage 9c, D307-D309, D317-D319)

A Gitea job that checks each new Rust staging build in the seven days before the
monthly forced wipe:

- tools/drill/static.sh: staging's managed assemblies (DepotDownloader) and
  Oxide's staging build; whether Oxide has caught up (fieldlist --oxide-behind),
  the swap's field list compared, and the plugin compiled.
- tools/drill/drill.js: window (D318), gate (one check per depot manifest),
  live (rust-staging runs rnt.run all; rust-carbon is stopped and restarted when
  both 6000-map rigs run, D309) and report (one issue per build, state on the
  drill branch; a build waits up to three days for Oxide, D317).
- tools/drill/rig-start.sh: the rig's startup; installs the branch and Oxide's
  build for it, and refuses to start a mismatched pair.
- RunicNpcTest 0.7.2: stage 5's five fights look for a field with room for their
  280 m line (D319), and the spawn check names what did not spawn.

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-07 01:29:45 -05:00
parent 9b316c6266
commit c25f73c525
9 changed files with 1058 additions and 8 deletions

View File

@@ -92,7 +92,8 @@ The API version moves when a call or a raised hook changes shape, not on every r
| `plugin/RunicNPC.cs` | The plugin. The only file a server gets. |
| `plugin.toml` | Its declarations: API version, framework floors, required plugins. The release copies them into the manifest. |
| `scripts/checkPlugin.js` | The static checks run on every pull request and again before a release (see its header). |
| `tools/` | Developer scaffolding for the test rigs, never shipped: the panel scripts (see `tools/rigs.example.json`); `RunicNpcHarness.cs`, the stage 1 and 5 measurement plugin; `RunicNpcTest.cs`, the test harness (`rnt.run all`, then `rnt.after` after a reload or restart) and stage 9's performance check (`rnt.cost`, below); and `fieldlist/` with `managed.js`, which regenerate the swap's field list (below). |
| `tools/` | Developer scaffolding for the test rigs, never shipped: the panel scripts (see `tools/rigs.example.json`); `RunicNpcHarness.cs`, the stage 1 and 5 measurement plugin; `RunicNpcTest.cs`, the test harness (`rnt.run all`, then `rnt.after` after a reload or restart) and stage 9's performance check (`rnt.cost`, below); and `fieldlist/` with `managed.js`, which regenerate the swap's field list (below). `drill/` is the staging drill's (below). |
| `.gitea/workflows/staging-drill.yml` | The staging drill, the week before each forced wipe. |
## After a Rust update: the swap's field list
@@ -112,7 +113,7 @@ fields Rust itself serialises.
## The update surface: what a Rust update can break
Everything RunicNPC takes from Rust's own classes for the swap and the brain, in one place. A Rust update
that changes any of it breaks RunicNPC; the staging drill (PLAN.md stage 9, D308) checks exactly this list
that changes any of it breaks RunicNPC; the staging drill (PLAN.md stage 9c, below) checks exactly this list
against every new staging build. **Caught by** says how: *compile* means the plugin no longer compiles
against the new assembly; *field list* means `tools/fieldlist`'s regenerated list differs from the
plugin's (and `rnpc.status` says `missing=` or `added=` at run time); *run time* means only a running server
@@ -138,6 +139,39 @@ The **hooks** RunicNPC listens to are listed by `rnpc.status`, which names any t
that stays silent after an update has probably been renamed (neither framework reports a hook that matches
nothing).
## The staging drill
Facepunch puts each Rust build on Steam's `staging` branch days before it ships, and Rust changes on the
monthly forced wipe, the first Thursday. `.gitea/workflows/staging-drill.yml` works in the seven days
before each forced wipe, which leaves a week to fix what it finds, and checks every new staging build in
that week (PLAN.md stage 9c, D307–D309, D317, D318). It is scheduled daily and stops at once outside
the week. A run by hand always goes ahead.
- **Static, about a minute, no server** (`tools/drill/static.sh`): Rust's managed assemblies
from the staging branch and Oxide's staging build. It asks whether Oxide has caught up with Rust
(`tools/fieldlist --oxide-behind`), regenerates the swap's field list and compares it, and compiles the
plugin against them.
- **Live, once per build** (`tools/drill/drill.js live`): the `rust-staging` server (Oxide, a 3500 map)
installs the same pair at start (`tools/drill/rig-start.sh`), loads RunicNPC, Kits and
`tools/RunicNpcTest.cs`, and runs `rnt.run all`. When both 6000-map rigs are running, `rust-carbon` is
stopped for it and started again.
- **Report:** anything that fails or differs opens one issue for that build, labelled `staging-drill`.
The last build checked is kept on the `drill` branch.
A build is the manifest of Rust's Linux depot, as Steam names it. Oxide's build for a branch carries its
own patched `Assembly-CSharp.dll`, and while it is older than Rust's, an Oxide server on that pair dies
at boot. So a build waits, opening nothing, until Oxide catches up, for up to three days. After that it is
reported as "Oxide has not caught up". To rehearse it on a pair known to match, run the workflow by hand
with `branch: public`: it reports in the job summary only.
By hand, from Git Bash or Linux:
```bash
BRANCH=public DEPOTDOWNLOADER=/path/to/DepotDownloader bash tools/drill/static.sh # → drill-work/static.json
PANEL_KEY_FILE=/path/to/key node tools/drill/drill.js live --branch public # → drill-work/live.json
node tools/drill/drill.js report --state /tmp/drill.json # public: prints only
```
## Performance: `rnt.cost`
Stage 9's check (D306): 100 of RunicNPC's NPCs, idle and fighting 20 stand-in players, against the same