feat: stage 2, the NPC and its API (API 2) #4

Merged
whitlocktech merged 1 commits from feat/stage-2-npc-api into edge 2026-09-30 06:52:39 +00:00
Member

What

This is stage 2 of docs/runicnpc/PLAN.md §9, built on the org lead's D232–D238 (docs#301). The API is documented in docs/runicnpc/API.md (the docs PR opened with this one).

  • The NPC: Rust's scientist with its ScientistNPC and ScientistBrain swapped for RunicNpcPlayer and RunicNpcBrain.
    • The swap copies a fixed field list (D232), generated by tools/fieldlist from the Carbon rig's unmodified assembly: 64 NPC fields and 32 brain fields.
    • The swap includes stage 1's AwakeFromInstantiate step, its AttackerInfo override, and the sense values set before the brain starts.
  • Profiles are read from data/RunicNPC/profiles.json in the approved shape (D238). A profile whose kit is missing is refused.
    • RunicNpc_SetProfiles marks the server managed (D221), and rnpc.reload re-reads the file after a hand edit.
  • Movement (D233, D234): a roamer can wander (our own), follow Rust's monument paths, or walk a route:<name>, and a placement can override which. A sentry holds its spot.
  • Sleep (D235): with no player within 160 m, the NPC walks home, then stops thinking. A player in range wakes it.
  • Placements (D222, D236, D237):
    • Saved behind one dirty flag, with a count, a respawn delay and each or group respawn.
    • A placement whose profile or route is missing waits.
    • Spawning waits for the navmesh and is spread over frames within spawnBudgetMs.
  • Owners (§2): run: and plugin: owners for API callers; a plugin's NPCs are removed when that plugin unloads.
  • Caps are off by default, and the cost warning is built from stage 1's table (D227).
  • The API is all of §4, plus routes and RunicNpc_CostWarning, with its four hooks. api moves to 2 in plugin.toml.

Found while building it

  • Outside a monument, Rust's scientists never chase. Rust's chase state errors without an AI zone's move points, just as its roam does. wander and route roamers therefore get our own chase: it closes to ¾ of the attack range and stops at the chase range from home. If the target stays out of reach there for 5 s, the NPC gives up and walks home.
  • Rust's navmesh covers a new player-built floor within 0.1–0.3 s. Stage 1's "never on the mesh" came from a same-frame reading. This is recorded in the docs PR and left open for the org lead.
  • Rust's navmesh sampler reaches further down than across, so a roamer's spot must now be within 2 m, up or down, of the navmesh.
  • A brain can think before it has started (it has no navigator yet).
  • A hit with no body part reports every part at once ((HitArea)(-1)).
  • The manifest's entity list leaves out the NPC prefabs, so a profile's prefab is looked up in its prefab list instead.

Checks

  • node scripts/checkPlugin.js and its 24 tests pass. The check now treats an override (RunicNpcPlayer.OnDied) as never a hook, with a test for it.
  • The plugin and the harness compile locally against the Carbon rig's Rust assemblies and Oxide's plugin assemblies. That compile is stricter than either framework, which both allow internals.
  • tools/RunicNpcTest.cs passed every group on both rigs (Oxide 2.0.7726, Carbon 2.0.259, the same 6000 map):
Group Oxide Carbon
api 24/24 24/24
hooks 8/8 8/8
move 8/8 8/8
sentry 7/7 7/7
sleep 7/7 7/7
place 14/14 14/14
a plugin's NPCs removed when it unloads pass pass
after a RunicNPC reload (rnt.after) 5/5 5/5
after a server restart (rnt.after) 5/5 5/5
  • On both rigs, rnpc.status reads swap fields: npc=64/64 brain=32/32 missing=- added=-.

Checklist

  • AI-assisted: written with Claude Code (Claude Opus 5.5); the commit carries the trailer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

## What This is stage 2 of `docs/runicnpc/PLAN.md` §9, built on the org lead's D232–D238 (docs#301). The API is documented in `docs/runicnpc/API.md` (the docs PR opened with this one). - **The NPC:** Rust's scientist with its `ScientistNPC` and `ScientistBrain` swapped for `RunicNpcPlayer` and `RunicNpcBrain`. - The swap copies a **fixed field list** (D232), generated by `tools/fieldlist` from the Carbon rig's unmodified assembly: 64 NPC fields and 32 brain fields. - The swap includes stage 1's `AwakeFromInstantiate` step, its `AttackerInfo` override, and the sense values set before the brain starts. - **Profiles** are read from `data/RunicNPC/profiles.json` in the approved shape (D238). A profile whose kit is missing is refused. - `RunicNpc_SetProfiles` marks the server managed (D221), and `rnpc.reload` re-reads the file after a hand edit. - **Movement** (D233, D234): a roamer can `wander` (our own), follow Rust's `monument` paths, or walk a `route:<name>`, and a placement can override which. A sentry holds its spot. - **Sleep** (D235): with no player within 160 m, the NPC walks home, then stops thinking. A player in range wakes it. - **Placements** (D222, D236, D237): - Saved behind one dirty flag, with a count, a respawn delay and `each` or `group` respawn. - A placement whose profile or route is missing waits. - Spawning waits for the navmesh and is spread over frames within `spawnBudgetMs`. - **Owners** (§2): `run:` and `plugin:` owners for API callers; a plugin's NPCs are removed when that plugin unloads. - **Caps** are off by default, and the **cost warning** is built from stage 1's table (D227). - **The API** is all of §4, plus routes and `RunicNpc_CostWarning`, with its four hooks. `api` moves to 2 in `plugin.toml`. ## Found while building it - **Outside a monument, Rust's scientists never chase.** Rust's chase state errors without an AI zone's move points, just as its roam does. `wander` and `route` roamers therefore get our own chase: it closes to ¾ of the attack range and stops at the chase range from home. If the target stays out of reach there for 5 s, the NPC gives up and walks home. - **Rust's navmesh covers a new player-built floor within 0.1–0.3 s.** Stage 1's "never on the mesh" came from a same-frame reading. This is recorded in the docs PR and left open for the org lead. - Rust's navmesh sampler reaches further down than across, so a roamer's spot must now be within 2 m, up or down, of the navmesh. - A brain can think before it has started (it has no navigator yet). - A hit with no body part reports every part at once (`(HitArea)(-1)`). - The manifest's entity list leaves out the NPC prefabs, so a profile's prefab is looked up in its prefab list instead. ## Checks - `node scripts/checkPlugin.js` and its 24 tests pass. The check now treats an `override` (`RunicNpcPlayer.OnDied`) as never a hook, with a test for it. - The plugin and the harness compile locally against the Carbon rig's Rust assemblies and Oxide's plugin assemblies. That compile is stricter than either framework, which both allow internals. - **`tools/RunicNpcTest.cs` passed every group on both rigs** (Oxide 2.0.7726, Carbon 2.0.259, the same 6000 map): | Group | Oxide | Carbon | |---|---|---| | api | 24/24 | 24/24 | | hooks | 8/8 | 8/8 | | move | 8/8 | 8/8 | | sentry | 7/7 | 7/7 | | sleep | 7/7 | 7/7 | | place | 14/14 | 14/14 | | a plugin's NPCs removed when it unloads | pass | pass | | after a RunicNPC reload (`rnt.after`) | 5/5 | 5/5 | | after a server restart (`rnt.after`) | 5/5 | 5/5 | - On both rigs, `rnpc.status` reads `swap fields: npc=64/64 brain=32/32 missing=- added=-`. ## Checklist - [x] AI-assisted: written with Claude Code (Claude Opus 5.5); the commit carries the trailer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-09-30 06:31:34 +00:00
feat: stage 2, the NPC and its API (API 2)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m38s
f8084955e8
RunicNPC now spawns its own NPC: Rust's scientist with its ScientistNPC and
ScientistBrain swapped for ours, copying a fixed field list generated from
Rust's unmodified assembly (D232, tools/fieldlist). Profiles live in
data/RunicNPC/profiles.json in the approved shape (D238); placements persist
with each/group respawn (D236) and wait for a missing profile or route
(D237); roamers wander, follow Rust's monument paths, or walk a route (D233,
D234), with our own chase where Rust's needs an AI zone; sentries hold their
spot; NPCs walk home and sleep past 160 m of any player (D235). Spawns wait
for the navmesh and are spread over frames; caps are off by default and the
cost warning is shown instead (D227). The whole PLAN.md section 4 API and its
hooks are in, documented in docs/runicnpc/API.md.

tools/RunicNpcTest.cs is the stage 2 harness; every group passed on both
rigs, and after a plugin reload and a server restart. checkPlugin no longer
counts an override (RunicNpcPlayer.OnDied) as a hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
whitlocktech merged commit 15120ef80d into edge 2026-09-30 06:52:39 +00:00
whitlocktech deleted branch feat/stage-2-npc-api 2026-09-30 06:52:40 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/runicnpc-rust#4
No description provided.