docs(runicnpc): stage 6 built and walked; API 5; protocol §19.14

API.md version 5: the profile's boss and use blocks, the passive role
and stand, the boss hooks and OnRunicNpcUsed, role and boss in
RunicNpc_List, RunicNpc_Despawn taking a boss's adds, the example kits
written once, and a Kits reload taking RunicNPC with it (D283).

PROTOCOL.md §19.14: npc.boss.spawned, npc.boss.phase and npc.boss.died,
their classes, and runicnpc_api 5.

PLAN.md stage 6: what was built, the builds it was tested on, the
harness and the site walk on both rigs, and what building it found.
The example kits' permission is kits.runicnpc.

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-05 13:12:16 -05:00
parent 023ba77ceb
commit 6d319c843b
3 changed files with 174 additions and 12 deletions

View File

@@ -11,7 +11,7 @@ design answered 2026-09-30 and 2026-10-01** (D253–D266, §0), **its spike meas
and **its build's own questions answered 2026-10-01** (D267–D272). **Stage 5 built and tested 2026-10-01, walked on the
site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 4. **Stage 6's design answered
2026-10-05** (D273–D282, §0), **its spike measured 2026-10-05** on both rigs, and **its two questions answered the
same day** (D283, D284, §9). **Its build's own questions were answered the same day too** (D285–D289).
same day** (D283, D284, §9). **Its build's own questions were answered the same day too** (D285–D289), and **stage 6 was built and walked on the site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 5.
RunicNPC is Runic Gateway's own NPC plugin for Rust servers, in its own repository,
[`RunicGateway/runicnpc-rust`](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust). It runs on Oxide and
@@ -1236,8 +1236,10 @@ around it, each point snapped to the navmesh.
What this adds to the build (D283, D284):
- **The kits are named `rnpc_raider`, `rnpc_campguard`, `rnpc_sniper` and `rnpc_juggernaut`.**
- **Each carries `RequiredPermission: runicnpc.examplekits`**, which nobody is granted, so no player can claim one
by typing its name (`IsHidden` alone does not stop that). RunicNPC's `GiveKit` call ignores the permission.
- **Each carries `RequiredPermission: kits.runicnpc`**, which nobody is granted, so no player can claim one
by typing its name (`IsHidden` alone does not stop that). RunicNPC's `GiveKit` call ignores the permission. It
was `runicnpc.examplekits` until the build found Oxide warning on every Kits load that a permission Kits
registers must start with `kits.`; the org lead chose Kits' prefix (2026-10-05).
- **The first install runs in this order:** write the kits, set the flag, reload Kits.
- Because of D283, the reload takes RunicNPC down and back once.
- On that second load the flag is already set, so nothing is written twice and nothing loops.
@@ -1259,6 +1261,64 @@ What this adds to the build (D283, D284):
4. **How a passive NPC moves. → D288:** as its profile says.
5. **The example profiles. → D289:** the set in §0.
**Built (2026-10-05), three branches and this one.** RunicNPC API 5 (`runicnpc-rust` `feat/stage-6`), the bridge
(`Rust-Plugins` `feat/runicnpc-stage6`) and the site (`Module-Rust` `feat/runicnpc-stage6`). The API is
[API.md](API.md) version 5; the wire is [`PROTOCOL.md`](../rust-link/PROTOCOL.md) §19.14.
| Piece | What it does |
|---|---|
| RunicNPC API 5 | The boss box (bar, phases, lines and adds; the hooks `OnRunicNpcBossSpawned`, `OnRunicNpcBossPhase`, `OnRunicNpcBossDied` and `OnRunicNpcUsed`); the passive role with `stand` and press E through `OnPlayerInput`; the four example profiles and kits, written once (`data/RunicNPC/state.json`); `role` and `boss` in `RunicNpc_List`. The swap's field list regenerated for Rust 2634.289.1 |
| The bridge | `npc.boss.spawned`, `npc.boss.phase`, `npc.boss.died`; `runicnpc_api = 5`. A profile's `boss` and `use` travel in the push unchanged |
| The site | The profile form's boss box (phases with damage, aim, speed, ranges, a kit swap, adds and a line) and the passive role with its press-E answer; the checks in RunicNPC's words, a phase's kit checked on each server, its adds a site profile that is not a boss; the public triggers `rust.boss.spawned`, `rust.boss.phase`, `rust.boss.killed`; the feed's boss rows (a spawn public, a death behind the presence setting, a phase staff's) |
**The builds it was tested on (checked against the latest, 2026-10-05):** Rust 2634.289.1 (buildid 25681086,
the latest public build), Oxide 2.0.7801 and Carbon 2.0.262 (both the latest releases). This morning's spike ran on
the previous Rust build, before either rig restarted.
**Tested.** The harness's `s6` group (42 checks) passes on both rigs: refusals, passive (never fights, unhurtable
by default, press E once a second, a window, wandering), the boss (three phases, its own copy of the profile, the
death hook's contributors, adds that fight on, `RunicNpc_Despawn` of the dead boss taking its adds) and the
examples. `rnt.run all`: 244/246 on Oxide and 243/246 on Carbon before two checks were fixed (below); each fixed
check and the escort check then passed on both rigs. Module-Rust: 491 server and 67 client tests, and every
`check:*` script.
**Walked, 2026-10-05, through a walk site, signed in to the admin panel in the browser.** No player was on the rigs,
so a probe plugin hurt bosses as a stand-in player.
| Row | Oxide | Carbon |
|---|---|---|
| A boss profile made on the form (two phases: 50% two adds and a line; 25% damage ×1.5, speed ×1.3 and a line), saved and pushed; the rig holds the block as typed | pass | pass (made through the same API) |
| Placed by clicking the live map; `npc.boss.spawned` stored, `rust.boss.spawned` fired | pass | — |
| Phase 1 summoned two adds, owned as the boss is; `rust.boss.phase` fired for each phase | pass | pass (and the 25% kit swap: it then held `rnpc_raider`) |
| The kill: `rust.boss.killed` fired; the adds fought on (D287) | pass | pass |
| The public events route: signed out it shows the spawn only; signed in as an admin, the death too (its killer, 1 damager); never a phase | pass | — |
| An event placing the boss: its phase gate advanced on `rust.boss.killed` where `byEvent`, and the run's teardown removed the boss's adds | pass after a fix (below) | pass |
| A passive profile made on the form: choosing passive showed Press E, hid Boss, unticked "Players can hurt it" and set `stand`; pushed as typed | pass | — |
| Placed from the map; 500 hits from a player left it at 150/150; it stood and took no target | pass | — |
**Not walked:** everything a real client shows, waiting for the in-game walk with a player: the bar on screen and
its redraws, the lines in chat or a popup, a real E press and the window, and the example profiles on a fresh
server's own boot.
**What building it found:**
- **Rust's design picks targets that `IsTarget` refuses.** `HumanNPC.GetBestTarget` ranks every player in
`Senses.Players`, which `IsTarget` does not filter, and fires through `AttackTick`. A passive NPC shot at players
until RunicNPC answered both (IAIAttack, which it already re-implements) with nothing for a passive one. **Stage 5's
ally sparing may have the same hole**: it was not part of this stage and is not changed here.
- **A boss's adds outlived the event that placed it.** Teardown removes what a step placed by net id; the adds
RunicNPC spawned are in no ledger. RunicNPC now keeps each boss's adds by the boss's net id, after it dies too,
and `RunicNpc_Despawn` of that id removes them. Walked again: run 65's cleanup removed both.
- **Oxide warns on a permission Kits registers without its prefix.** The example kits' permission is `kits.runicnpc`
(the org lead's choice), not `runicnpc.examplekits`.
- **The Rust update's navmesh rebuild moved two checks' ground.** `sentry.shoots` now picks a spot with a clear line
(a rock was between the sentry and its stand-in; with a clear line it hit 28 times). `move.monument.roams`: the
military tunnel's own 30 scientists no longer move with no real player near, so ours standing too is the same
behaviour, and the check now says both.
- **The bridge's `npc.health` frame carries no owner** (stage 4's `OnRunicNpcHealth` passes null), so
`rust.npc.health` never says `byEvent`, and a gate on "the event's boss below 50%" cannot be written. Not changed
here; the boss phase trigger does carry it.
### Stage 7 — Loot
Loot tables authored on the site, the corpse's default loot cleared or kept, a crate on death, corpse removal.