docs(runicnpc): stage 3 as built, and its in-game walk #304

Merged
whitlocktech merged 1 commits from docs/runicnpc-stage3 into main 2026-09-30 08:20:53 +00:00
2 changed files with 69 additions and 4 deletions

View File

@@ -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<Dictionary<string, object>>`
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`

View File

@@ -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 <verb>` in chat and `rnpc.<verb>`
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
<kit>`):
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: