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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -82,6 +82,7 @@ The live NPCs of `owner`; null lists them all. Each has:
|
|||||||
| `rest` | `string` | `Awake`, `GoingHome` or `Asleep` (D235). |
|
| `rest` | `string` | `Awake`, `GoingHome` or `Asleep` (D235). |
|
||||||
| `state` | `string` | Rust's AI state: `Idle`, `Roam`, `Chase`, `Combat` and so on. |
|
| `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. |
|
| `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`
|
### `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>>`
|
### `RunicNpc_Placements()` → `List<Dictionary<string, object>>`
|
||||||
|
|
||||||
Each placement as `{id, placement, alive, waiting, lastError}`. `placement` is the placement's JSON (below),
|
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.
|
`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`
|
### `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
|
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).
|
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`
|
### `RunicNpc_RemovePlacement(string id)` → `bool`
|
||||||
|
|
||||||
Removes a placement and its NPCs.
|
Removes a placement and its NPCs.
|
||||||
@@ -114,7 +123,9 @@ Removes a placement and its NPCs.
|
|||||||
|
|
||||||
### `RunicNpc_SetRoute(string name, JObject route)` → `string`
|
### `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`
|
### `RunicNpc_RemoveRoute(string name)` → `bool`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
decided with it. **Stage 1 measured 2026-09-30** (§9): six answers on both rigs; D231 was decided with it.
|
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
|
**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
|
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,
|
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
|
[`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
|
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.**
|
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
|
### Stage 4 — Runic Gateway integration
|
||||||
|
|
||||||
Across four repositories, split into 4a–4c if it grows:
|
Across four repositories, split into 4a–4c if it grows:
|
||||||
|
|||||||
Reference in New Issue
Block a user