From 953044b806a2d689f50db0f5741a753cb51dcda1 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Wed, 30 Sep 2026 03:19:14 -0500 Subject: [PATCH] docs(runicnpc): stage 3 as built, and its in-game walk - PLAN.md stage 3: what was built (runicnpc-rust#5), the results on both rigs (134/134, reload and restart 5/5), what building it found (Rust leaves an NPC in the air when its floor goes; a route leg can be on the navmesh and still unwalkable; a shore floor not covered; stage 2's JSON leak), the console-only at= option for the org lead's call, and the in-game walk checklist. - API.md: SetPlacement's navmesh check and D239 fallback, the placements' note, List's regrounded, and SetRoute's unchecked legs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- runicnpc/API.md | 17 ++++++++++++--- runicnpc/PLAN.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/runicnpc/API.md b/runicnpc/API.md index 2c2f17d..c4609a5 100644 --- a/runicnpc/API.md +++ b/runicnpc/API.md @@ -82,6 +82,7 @@ The live NPCs of `owner`; null lists them all. Each has: | `rest` | `string` | `Awake`, `GoingHome` or `Asleep` (D235). | | `state` | `string` | Rust's AI state: `Idle`, `Roam`, `Chase`, `Combat` and so on. | | `leashes` | `int` | How many times it has given up a chase at its chase range. | +| `regrounded` | `int` | How many times its ground went from under it and it was put on the nearest navmesh (D239). | ### `RunicNpc_Profiles()` → `JObject` @@ -96,14 +97,22 @@ profile has gone waits, and spawns again when the profile returns (D237). ### `RunicNpc_Placements()` → `List>` -Each placement as `{id, placement, alive, waiting, lastError}`. `placement` is the placement's JSON (below), -`alive` is how many of its NPCs are alive, and `waiting` says why it cannot spawn right now, or is null. +Each placement as `{id, placement, alive, waiting, lastError, note}`. `placement` is the placement's JSON (below), +`alive` is how many of its NPCs are alive, and `waiting` says why it cannot spawn right now, or is null. `note` is +set while its NPCs stand somewhere other than its spot because the ground there went (D239, below), or is null. ### `RunicNpc_SetPlacement(string id, JObject placement)` → `string` Adds or replaces a persistent placement (D222). Null on success, or the reason. Replacing one despawns its NPCs and spawns them again from the new values. Every set logs the cost warning (D227). +A roamer's spot must be on Rust's navmesh, within 2 m up or down, when it is set; otherwise the answer says how far +off it is, as `rnpc place` does (D219). The check is skipped while the map's navmesh is still being built, and +for a profile that does not exist yet. **If the ground under a placement goes later** (a player-built floor is +destroyed, D239), a live NPC is put on the nearest navmesh within 2 s, and the placement respawns its NPCs on the +nearest navmesh until its spot is walkable again; `note` says so meanwhile. Rust itself leaves such an NPC standing +in the air. + ### `RunicNpc_RemovePlacement(string id)` → `bool` Removes a placement and its NPCs. @@ -114,7 +123,9 @@ Removes a placement and its NPCs. ### `RunicNpc_SetRoute(string name, JObject route)` → `string` -Adds or replaces a route (D234). Every point must be on Rust's navmesh. Null on success, or the reason. +Adds or replaces a route (D234). Every point must be on Rust's navmesh. Null on success, or the reason. Unlike +`rnpc path` (D240), it does not check that an NPC can walk from each point to the next: a leg Rust cannot path +leaves the NPC standing at the last point it reached, so a caller that builds routes should check its own legs. ### `RunicNpc_RemoveRoute(string name)` → `bool` diff --git a/runicnpc/PLAN.md b/runicnpc/PLAN.md index d9b394d..1685e74 100644 --- a/runicnpc/PLAN.md +++ b/runicnpc/PLAN.md @@ -5,7 +5,7 @@ decided with it. **Stage 1 measured 2026-09-30** (§9): six answers on both rigs; D231 was decided with it. **Stage 2's design answered 2026-09-30:** D232–D238 (§0), which reshape stage 2 (§9). **Stage 2 built and tested 2026-09-30** on both rigs (§9); its API is [API.md](API.md). **Stage 3's design answered 2026-09-30:** D239–D242 -(§0). +(§0). **Stage 3 built and tested 2026-09-30** on both rigs (§9); its in-game walk waits for a player. 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 @@ -618,6 +618,60 @@ route file a recording writes), then a written in-game walk: place, remove, rena route and place a patrol on it, place on a player-built floor and destroy it, restart. **This is the first stage that needs a player on a rig.** +**Built (2026-09-30, runicnpc-rust#5, API still 2).** Every verb of §5 is `/rnpc ` in chat and `rnpc.` +in a console, one dispatcher behind both. `runicnpc.admin` includes `runicnpc.place`, and the server console has +both. `/rnpc` alone lists the verbs the caller may use. `rnpc.profile` answers only in a console (F1 included): in +chat it points there. **One addition for the org lead's call:** the server console has no position, so there +`place`, `here` and `path point` take `at=x,y,z` (and `yaw=`), which is also how the harness drives them. In game +these options are refused. + +| Group | What it proves | Oxide | Carbon | +|---|---|---|---| +| cmd | D242's options and defaults; D241's names (the next number, the freed number taken again, rename keeps the live NPC and moves its owner); 11 refusals, none leaving a placement behind; a sentry 40 m up; D240's recording (undo, too few points, a point in the air, `loop\|back`); a patrol walking the recorded route 18 m in 12 s; a deleted route leaves its placement waiting; respawn, clear, remove; the chat path as a stand-in player (nothing without the permission, `here`, `remove`); standalone `rnpc.profile` create, set, show and delete, refused while managed | 57/57 | 57/57 | +| floor | D239's warning; a roamer standing on a player-built floor; the floor destroyed under it; the respawn falling back to the ground and back onto the rebuilt floor; a route leg to a floor with no stairs refused | 7/7 | 7/7 | +| all | stage 2's groups and the two above in one `rnt.run all` | 134/134 | 134/134 | +| RunicNPC reloads / the server restarts | stage 2's checks | 5/5 · 5/5 | 5/5 · 5/5 | + +What building it found: + +- **Rust leaves an NPC standing in the air when the floor under it is destroyed.** Measured for 15 s; it kept its + state and never fell. RunicNPC's brain now checks every 2 s, and a roamer more than 2 m above the nearest + navmesh is put on it (as Rust's own navigator warps) and wanders from there until it respawns. It was on the + ground 2 s after its floor went, on both rigs. A placement whose spot is off the mesh when its NPCs respawn + spawns them on the nearest navmesh, and returns to its spot once the floor is rebuilt. This is how D239's "falls + back to the nearest navmesh" is implemented. +- **A route can be on the navmesh and still not walkable.** The first recorded test route climbed a hill a player + can walk. Its first point was on an island of navmesh on a rock top, and the patrol never left it. `path point` + now asks Rust for a path from the previous point and refuses the point if there is none; `path save loop` + refuses a route whose last point cannot reach its first. `RunicNpc_SetRoute` does not check legs (API.md). +- **A floor over the shore was not covered by the navmesh in 10 s,** where a floor over dry ground a few metres + away was covered in 0.1 s (stage 2's figure). The command answers with the reason in both cases, so nothing + depends on it. It is noted here in case stage 10 meets it again. +- **Stage 2 wrote two computed properties into its JSON** (`V` in every position, `IsSentry` in every profile), + in the files and in the API's answers. Both are ignored now. +- **Rust's console argument type has changed** from `string[]` to `StringView[]` in the current build. The + console verbs read each argument through `GetString`, which both have. + +**The in-game walk is still to do** (it needs a player). Its checklist, on either rig, with `runicnpc.admin` +granted and one standalone profile made from the console (`rnpc.profile create bandit`, then `set bandit kits +`): + +1. `/rnpc` lists the verbs; without the permission it says which is missing. +2. `/rnpc place bandit` on the ground you look at → "Placed bandit-1"; it spawns facing you, and the cost + warning is shown. `/rnpc here bandit count=3 mode=group` → bandit-2, three NPCs around you. +3. Look at one: `/rnpc info` shows its name, profile, placement, health, state and target. Shoot it: the target + line names you. +4. `/rnpc near` lists bandit-1 and bandit-2 with distances. `/rnpc rename bandit-2 camp`; `/rnpc tp camp`. +5. Kill one of `camp`: nothing returns until all three are dead (`group`); then `/rnpc respawn camp` brings all + back at once. +6. `/rnpc path record gate`, walk and `/rnpc path point` four times (try one on a rock or a roof: refused), + `/rnpc path undo`, one more point, `/rnpc path save loop`. `/rnpc place bandit move=route:gate` → the NPC walks + the points in order. +7. Build a foundation and a floor 2 high with stairs, stand on the floor: `/rnpc here bandit` → the warning. + Destroy the floor: the NPC is on the ground within 2 s. `/rnpc near` shows the note. +8. Look at an NPC: `/rnpc remove` removes its placement. `server.save`, restart: every other placement is back, + and no plain "Scientist" stands at a spot. + ### Stage 4 — Runic Gateway integration Across four repositories, split into 4a–4c if it grows: -- 2.49.1