docs(runicnpc): the automated wipe-day cutover, planned (D332) #411
@@ -155,6 +155,7 @@ architectural or design decision is implemented.
|
||||
| **D329** | **The Kits reload the first load asks for is owed until Kits has the example kits.** The reload ran on a one-second timer, and a timer dies with its plugin. In the 9d session RunicNPC was loaded again inside that second on Carbon, the reload was lost, and since `examplesWritten` was already set the examples stayed refused until Kits was reloaded by hand. Now `state.json` holds `kitsReloadOwed`: every load, and Kits coming back, clears it once Kits knows every kit owed, and otherwise asks for the reload again, at most three times, then warns with the command to run. Oxide never showed it, because loading RunicNPC recompiles and reloads Kits there (`// Requires: Kits`). The org lead chose the fix over recording it (2026-10-09). | Record it as a hot-load artifact, with the remedy in INSTALL.md. |
|
||||
| **D330** | **RunicNPC's stage 9 release is v1.0.0, cut by a `feat!:` commit.** The release engine takes its bump from the non-merge commit subjects since the last tag. Every stage had landed as `feat:`, which would have cut v0.2.0. runicnpc-rust#18 (`feat!:`, with a `BREAKING CHANGE:` footer) changed only the README, and made the cutover cut v1.0.0. The API stays 6 (2026-10-09). | Accept v0.2.0 and change the plan's wording. |
|
||||
| **D331** | **A kit's extra is found by its item definition when it has no held entity.** Rust's November 2026 build adds the server variable `ItemModEntity.playerOnlyEntityBeltOnly` (on by default): an item Rust marks player-only, the medical syringe among them, has a held entity only while it is in the belt. A kit puts its syringes in the main inventory, so D260's heal, which looked for an item whose held entity is a `MedicalTool`, found none and never healed (runicnpc-rust#20). Every extra (heal, melee, flamethrower, grenade, rocket) now reads the held entity when there is one and the item definition's entity prefab when there is not; holding the item moves it to the belt, where Rust makes its entity. A full belt means no heal, not an error. The org lead chose all five extras over heal alone (2026-11-02). | Fix the heal alone. |
|
||||
| **D332** | **On a forced-wipe weekend a workflow merges `edge` into `main` on its own, once five gates pass** (9c, the automated cutover): Rust's public build changed on wipe day; Oxide's public release has caught up; a Carbon boot on public Rust says `missing=- added=-`; the drill's live half passes on public Rust with `edge`'s code; PR checks pass. It runs hourly from Thursday 17:00 UTC to Sunday 23:00 UTC, then leaves it to the org lead. The issues it closes are the `Closes #N` lines of the PRs merged into `edge` since the last release; it merges with a token locked to `runicnpc-rust` (`RUNICNPC_TOKEN`). RunicNPC only. The org lead chose it (2026-11-02): gate 1 is "public changed", since the daily drill tests `main` and so cannot vouch for `edge`; the schedule reaches `main` once by a `ci:` PR. | Approve the cutover by hand each wipe (D226 as it was); gate on "the staging drill passed this build"; give up Friday 23:00 UTC. |
|
||||
|
||||
**Borrowing, not copying.** NpcSpawn states no licence at all, so its source grants us nothing and is read only as a
|
||||
description of *what* can be done in Rust. HumanNPC is MIT on uMod, which is GPL-compatible, but §1.2 rules out its
|
||||
@@ -1804,6 +1805,51 @@ patched against patched, so only Rust's own changes show. Hurt, OnAttacked, Heal
|
||||
Oxide's 31 October staging build boots on it): stage 5 47 pass, 0 fail, and the whole harness 266 pass, 0 fail,
|
||||
`place` included, so cf3997b's spots off the navmesh have now run live too.
|
||||
|
||||
**9c, the automated cutover (D332, planned 2026-11-02).** The org lead approves no merge on a wipe day. A workflow
|
||||
in `runicnpc-rust`, `.gitea/workflows/cutover.yml`, merges `edge` into `main` once Rust's update is live and the
|
||||
plugin is proven on it; `release.yml` then cuts the release as on every push to `main`. The other four Rust repos
|
||||
keep their cutover by hand (D313); a case for them is a plan of its own.
|
||||
|
||||
- **When.** Hourly, from 17:00 UTC on the forced-wipe Thursday (the first Thursday, D318) until Sunday 23:00 UTC.
|
||||
Nothing is dated in the workflow: a new `wipeWeekend(now)` helper beside `nextWipe` says whether this hour is
|
||||
in that span (`windowDays` cannot: on the Thursday itself `nextWipe` already answers next month). It shares the
|
||||
drill's concurrency group, `staging-drill`, so it never runs while the daily drill has `rust-staging`. Nothing
|
||||
to merge (`edge` already in `main`) ends it at once.
|
||||
- **Five gates, cheapest first; it merges only when all pass:**
|
||||
1. **Rust's public build changed on wipe day.** The drill's static half run with `BRANCH=public`: the public
|
||||
depot manifest is dated the wipe day or later. Until then it waits, quietly.
|
||||
2. **Oxide has caught up.** The same static run reports Oxide's public release `0` members behind Rust's public
|
||||
assemblies (D317), and RunicNPC from `edge` compiles against them.
|
||||
3. **A Carbon boot on public Rust.** `rust-carbon` is restarted (its egg updates Rust and Carbon), loads `edge`'s
|
||||
`RunicNPC.cs`, and `rnpc.status` must say `framework=carbon`, `navmesh=ready` and `missing=- added=-`
|
||||
(D232: only Carbon's unmodified assembly makes `added` meaningful). The rig is left running.
|
||||
4. **The drill again, on live Rust.** Facepunch sometimes changes or holds a build between staging and live at
|
||||
the last minute, so a staging pass does not count: the drill's live half runs on `rust-staging` with public
|
||||
Rust, Oxide's public build, and `edge`'s plugin and harness, and every harness check must pass with no
|
||||
RunicNPC error line in Oxide's log. The drill's `branch: public` mode runs it; its report gains an outcome
|
||||
the workflow reads (today a public run ends before it writes one).
|
||||
5. **PR checks** on the cutover PR (`plugin-checks`), on its head commit.
|
||||
- **Each passed gate is kept** for that public manifest, beside the drill's own state in `drill.json` on the
|
||||
`drill` branch (`cutover: {wipe, manifest, gates, pr, merged}`), so an hourly retry never re-runs a gate that
|
||||
passed, above all the hour-long gate 4. A new public manifest that weekend starts the gates again.
|
||||
- **The PR.** It opens one `edge` → `main` PR (or reuses the open one) titled for the wipe and the build. Its body
|
||||
lists every `Closes #N` line found in the bodies of PRs merged into `edge` since the last `v*` tag, so the issues
|
||||
they fixed close when it merges into `main`, the default branch; a merge into `edge` closes nothing. No issue
|
||||
number is written anywhere in the workflow.
|
||||
- **The merge.** `POST …/pulls/{n}/merge` with `RUNICNPC_TOKEN`, a personal access token the org lead made
|
||||
for this repo and locked to it, used in that one step only; a push made with the runner's own token would not start
|
||||
`release.yml`. `REGISTRY_TOKEN` and `PTERODACTYL_DRILL_KEY` serve every other step, as in the drill. After the
|
||||
merge it waits for the release and comments its version on each issue that closed.
|
||||
- **When a gate blocks.** One comment a day on the cutover PR naming the gate and why (opened once gate 1 has
|
||||
passed, so a quiet Thursday morning writes nothing). At Sunday 23:00 UTC it stops with a last comment, and the
|
||||
org lead merges by hand as before.
|
||||
- **Trying it.** `workflow_dispatch` with `dry_run` (every gate runs and reports; no PR, no merge, no comment) and
|
||||
`force` (outside the wipe weekend). The gate decisions and the `Closes` collection are pure functions with tests
|
||||
in `tools/drill/`, which pr-checks already runs.
|
||||
- **First run.** Gitea fires a schedule only from the default branch, so the workflow reaches `main` once by a
|
||||
`ci:` PR holding only it and its tests, merged by the org lead this week; `release.yml` cuts nothing for `ci:`,
|
||||
and the field list stays on `edge`. The same commits go to `edge`. From then on no wipe needs an approval.
|
||||
|
||||
**9d. The player walk (D312).** Both rigs are updated first (Rust, Oxide, Carbon) and their builds stated. One
|
||||
checklist, published as a page, for one session per rig. I prepare the rigs, the walk site and a linked test
|
||||
account; the org lead plays, and I watch the logs and record each row. It gathers every in-game check deferred so
|
||||
|
||||
Reference in New Issue
Block a user