docs(runicnpc): record the org lead's answers, D221-D228

Standalone use too (D221), placements live on the server (D222), our own
scientist subclass and brain (D223), installer and egg ship it plus a
Gitea release and possibly other download sites (D224), per-profile
kills public and in titles (D225), edge to main (D226), no default caps
but a measured cost warning wherever NPCs are added (D227), and the
names RunicNPC, /rnpc, runicnpc.* (D228). The sections they change are
updated: standalone profiles, the console profile verb, cost warnings,
stage 1's measurements, and publishing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-29 20:38:28 -05:00
parent 97be4fb5ab
commit aec01342d5
2 changed files with 34 additions and 10 deletions

View File

@@ -8,7 +8,7 @@ they can be read together. **Built so far:** §1, walked 2026-09-28 (§1.9); §5
rig walk still to come (§5.8, D209); §3, built and walked on the rigs and the site 2026-09-29, its in-game
rows still to come (§3.4); §4, built 2026-09-29 (§4.1). **§6 was spiked on 2026-09-30, and the org lead
chose neither route: Runic Gateway writes its own NPC plugin, RunicNPC, planned in
[`../../runicnpc/PLAN.md`](../../runicnpc/PLAN.md) (D214–D220), and the rest of this plan waits for it.**
[`../../runicnpc/PLAN.md`](../../runicnpc/PLAN.md) (D214–D228), and the rest of this plan waits for it.**
This is a companion to [`PLAN_FIXES.md`](PLAN_FIXES.md) and [`PLAN.md`](PLAN.md). Where they disagree, this
document is later and wins. Its decisions continue PLAN_FIXES' numbering at **D188**.

View File

@@ -1,6 +1,6 @@
# RunicNPC — the plan
**Status:** plan, written 2026-09-30. No code yet. **Its questions (§11) are open.**
**Status:** plan, written 2026-09-30. No code yet. **Its eight questions (§11) were answered the same day: D221–D228 (§0).**
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
@@ -30,6 +30,14 @@ architectural or design decision is implemented.
| **D218** | **Loot tables are RunicNPC's own for now.** Links to other loot plugins can come later. | AlphaLoot, CustomLoot, Loottable or LootManager as a dependency. |
| **D219** | **Rust's own navmesh is used. A custom navigation mesh is a later stage, built only if a real need appears** (§8, stage 10). | Shipping NpcSpawn-style point meshes from the start. |
| **D220** | **The plugin is built and tested in stages before the Rust plan resumes, and it then becomes a plugin `module-rust` requires.** | Building it alongside the remaining redesigns. |
| **D221** | **RunicNPC works without Runic Gateway too** (Q1). Standalone, its profiles live in its data file and console commands edit them. On a Runic Gateway server, the site's profiles replace that file on every push, and in-game profile edits are refused with "this server's profiles are managed by its website". | Runic Gateway only. |
| **D222** | **Placements live on the server** (Q2). They keep respawning while the site is offline; the site catches up when it returns, and lists and edits them from then on. | The site as the only record. |
| **D223** | **Our NPC is its own subclass of Rust's scientist, with its own brain** (Q3). The brain is kept small and tested on Rust's `staging` branch before each forced wipe. | Rust's scientist plus patches. |
| **D224** | **The installer and the egg ship it from the bundle, pinned and checksummed** (Q6). It is also published as a release on Gitea, and possibly on other sites for anyone to download. | Operators installing it themselves. |
| **D225** | **Per-profile kills are public and usable in titles** (Q4), like the NPC kills column today. | Admins only. |
| **D226** | **`edge` → `main`**, like the other Rust repositories (Q5, D18). | PRs straight to `main`. |
| **D227** | **No default caps.** Instead, a realistic warning of what a number of NPCs costs and its impact on the server, measured in stage 1, shown wherever NPCs are added (Q7). Caps exist only when an admin sets them. | 100 / 50 / 10 per second by default. |
| **D228** | **`RunicNPC.cs`, console name `RunicNPC`, chat command `/rnpc`, permissions `runicnpc.*`** (Q8). | — |
**Borrowing, not copying.** NpcSpawn states no licence at all, so its source grants us nothing and is read only as a
description of *what* can be done in Rust. HumanNPC is MIT on uMod, which is GPL-compatible, but §1.2 rules out its
@@ -132,6 +140,11 @@ Stage 1 proves the swap on both frameworks before anything is built on it.
This split is what HumanNPC got wrong: the *placement* is persistent data, the *NPC* never is. There is exactly one
save path, a flag marks it dirty on every change (including removal), and nothing else is written.
**Standalone or managed (D221).** Without a site, RunicNPC reads its profiles from its own data file and console
commands edit them. With a site, the bridge pushes the site's profiles, which replace the file; the file records that
it is managed, and in-game profile edits are refused with "this server's profiles are managed by its website".
Placements stay the server's either way (D222): the site reads them and edits them through the bridge.
**A profile is a named description of an NPC:**
- **Appearance:** name (or a list to pick from), the kits it wears, body type, and the prefab it starts from.
@@ -183,7 +196,7 @@ Borrowed ideas are marked with their source; everything else is new. Stage numbe
| **Admin placement in game** by chat command (§5), persistent, with respawn delays; the site lists and edits the placements | 3, 4 |
| **Correct accounting**: an NPC is never counted as a player; its name reaches the killfeed, event log and death screen; kills are counted per profile | 2, 4 |
| **Per-profile stats and titles**: "Warden kills", boss kills; the killing blow and every player who did damage | 4 |
| **Caps**: per server, per owner, per profile, a spawn-rate limit, and a performance budget | 2 |
| **Cost warnings, not default caps (D227)**: what N NPCs cost the server, measured in stage 1, shown when an admin places them, on the site's profile and placement pages, and in `doctor`. Caps (per server, owner or profile, and a spawn rate) exist only when an admin sets them | 2, 4 |
| **Event ownership**: run-owned NPCs with the bridge's restart and teardown guarantees | 4 |
| **Waves and phases** without a core change: a phase advances on "n NPCs of profile X died", and on "the boss fell below 50%" | 4, 6 |
| **Zone tethering**: an NPC held inside a ZoneManager zone (from redesign §3) | 5 |
@@ -193,7 +206,7 @@ Borrowed ideas are marked with their source; everything else is new. Stage numbe
| **Quest-giver or vendor**: passive, press E for a site-written message | 6 |
| **The live map and the app**: markers for event NPCs and bosses, "guards left: 3/8" | 8 |
| **Operator-friendly**: shipped in the bundle, no phone-home, no image files, no boot scan; `doctor` reports it; RunicNPC's permissions appear in the site's permission manager like any plugin's | 2, 4 |
| **An API for other plugins**, versioned, documented, and usable without Runic Gateway at all if §11 Q1 says so | 2, 9 |
| **An API for other plugins**, versioned, documented, and usable without Runic Gateway at all (D221) | 2, 9 |
---
@@ -209,7 +222,7 @@ matched by accident. The first draft, fixed in stage 2 and published as `docs/ru
| `RunicNpc_Spawn(Vector3 at, string profile, string owner, JObject overrides)` → `BasePlayer` | Spawns one NPC, or returns null and logs why. `overrides` may change any profile value for this NPC only. |
| `RunicNpc_Despawn(ulong netId)` / `RunicNpc_DespawnOwner(string owner)` → `int` | Removes one, or all of an owner's. |
| `RunicNpc_List(string owner)` → `List<Dictionary<string, object>>` | The owner's live NPCs: id, profile, name, position, health. |
| `RunicNpc_Profiles()` / `RunicNpc_SetProfiles(JObject all)` | Reads, or replaces, the profile set (§11 Q1 decides who may). |
| `RunicNpc_Profiles()` / `RunicNpc_SetProfiles(JObject all)` | Reads, or replaces, the profile set. Replacing marks the server as managed by a site (D221). |
| `RunicNpc_Placements()` / `RunicNpc_SetPlacement(...)` / `RunicNpc_RemovePlacement(string id)` | The persistent placements (§2). |
| `RunicNpc_IsRunicNpc(BaseEntity e)` → `bool`, `RunicNpc_ProfileOf(BaseEntity e)` → `string` | Lets another plugin tell our NPCs apart. |
@@ -236,11 +249,15 @@ permissions reach the site's permission manager through the inventory, like any
| `/rnpc near [radius]` | Lists placements and live NPCs near you | `runicnpc.place` |
| `/rnpc info` | The NPC you are looking at: profile, owner, health, target, state | `runicnpc.place` |
| `/rnpc profiles` | The profiles this server has | `runicnpc.place` |
| `rnpc.profile <create\|set\|delete> ...` (console) | Edits a profile on a standalone server; refused when the site manages them (D221) | `runicnpc.admin` |
| `/rnpc tp <placement>` | Teleports you to a placement | `runicnpc.admin` |
| `/rnpc respawn [placement\|all]` | Respawns a placement's NPC now | `runicnpc.admin` |
| `/rnpc clear <owner>` | Removes every NPC of an owner (e.g. a stuck run) | `runicnpc.admin` |
**Every placement is checked against Rust's navmesh (D219).** On the navmesh, the NPC roams, chases and fights
**Every placement is checked against Rust's navmesh (D219).** Every placement also answers with the
cost warning (D227): how many RunicNPC NPCs the server now has and what stage 1 measured that number to cost.
On the navmesh, the NPC roams, chases and fights
normally. Off it (a roof, inside a base, a pasted structure), a **stationary** profile is placed and a roaming one
is refused with the reason. An event step gets the same answer, as a blocked RaidableBases spot does (D208).
@@ -303,7 +320,7 @@ needs a player in game writes that walk down, and it is done before the stage cl
- `LICENSE.md` (GPL-3.0-or-later), `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md` (with the AI-disclosure rule),
`SECURITY.md`, `README.md`, PR template.
- Branches `main` and `edge` (§11 Q5).
- Branches `main` and `edge` (D226).
- **CI on every PR:** a static reader like Rust-Plugins' `checkPlugin.js`: every hook it declares is known, none
returns a value where one would cancel a game action, and the API version in the source equals the manifest's.
- **Release on `main`:** `runicnpc-<ver>.tar.gz` holding `runicnpc/RunicNPC.cs` and a `manifest.json` (version, API
@@ -324,7 +341,8 @@ A harness plugin (`tools/RunicNpcHarness.cs`) answers, on **both** rigs, what st
2. `enableSaving = false` holds across a hard kill and restart (§1.3 did this for NpcSpawn only).
3. Which ranges and scales hold on the brain without our own states, and which need them.
4. Navmesh coverage: a placement check at every monument of the 6000 map, and on a roof.
5. The cost of spawning 1, 10 and 100, and of 100 idle and 100 fighting, as the server's frame time.
5. The cost of spawning 1, 10 and 100, and of 100 idle and 100 fighting, as the server's frame time. These numbers
are the cost warning's (D227), so they are measured on both rigs and on the 6000 map.
6. The name in the death screen, and what the bridge publishes for a kill by and of our NPC.
**Done when** each has a measured answer written into this plan, and the org lead has picked anything the answers
@@ -334,7 +352,8 @@ leave open.
- The subclass, profiles (read from RunicNPC's own data file for now), kits (random pick), appearance, combat values,
roamer and sentry roles, sleep.
- Owners and lifetimes (§2); placements persisted with one dirty flag; caps and a spawn-rate limit.
- Owners and lifetimes (§2); placements persisted with one dirty flag; standalone profiles and the managed flag
(D221); optional caps, off by default, and the cost warning from stage 1's measurements (D227).
- The API (§4) and its hooks; `docs/runicnpc/API.md`.
**Tested by** the harness asserting each call's result (PASS/FAIL lines read back over the panel), plus restart
@@ -369,7 +388,7 @@ Across four repositories, split into 4a–4c if it grows:
- per-profile stats and titles.
- **Installer and egg:**
- RunicNPC becomes a third artefact in the Rust bundle, pinned and checksummed like the plugin and the sidecar
(§11 Q6);
(D224);
- `doctor` reports it missing or edited;
- the egg installs it.
@@ -415,6 +434,8 @@ in the Android app.
- **The whole acceptance walk on both rigs, with a player.**
- **Operator documentation:** `docs/runicnpc/` gets INSTALL and a command reference; `module-rust`'s OPERATING notes
list it as required.
- **Publishing (D224):** the Gitea release is the source of record. Other download sites (uMod, Codefling) are
decided then, each against its own rules, for example uMod's review guidelines.
- **v1.0.0 released, and `module-rust` then requires it:**
- the bundle lists it;
- the module refuses NPC steps on a server without it, saying why;
@@ -444,6 +465,9 @@ does not wait for a bridge release, or the reverse.
## 11. Questions for the org lead
**All eight were answered on 2026-09-30, as recommended except Q6 (D224 adds Gitea and other download sites)
and Q7 (D227: no default caps, a cost warning instead). They are kept as asked.**
**Q1. Can RunicNPC be used without Runic Gateway?** Picture a server owner who installs only RunicNPC, from uMod.
- **(a, recommended) Yes.** Without a site, profiles live in RunicNPC's data file and console commands edit them.