docs(runicnpc): stage 9b built; INSTALL and COMMANDS; RunicNPC required (D315, D316) #325

Merged
whitlocktech merged 1 commits from docs/runicnpc-stage9-hardening into main 2026-10-07 02:41:30 +00:00
5 changed files with 306 additions and 11 deletions

109
runicnpc/COMMANDS.md Normal file
View File

@@ -0,0 +1,109 @@
# RunicNPC — command reference
Every command RunicNPC answers, with the permission it needs. This page is the reference; how the commands came
to be is in [PLAN.md](PLAN.md) §5 and stage 3, and installing RunicNPC is in [INSTALL.md](INSTALL.md).
## How commands are reached
- **In chat:** `/rnpc <verb> ...`. `/rnpc` alone lists the verbs you are allowed to use.
- **In a console:** `rnpc.<verb> ...`, in a player's F1 console, the server console or RCON. The same code answers
both, with the same permissions.
- **Console-only verbs** (`profile`, `faction`) answer only in a console. In chat they tell you to open F1.
- **The server console has every permission.** It has no position, so `place`, `here` and `path point` take
`at=x,y,z` (and `yaw=`) there. In game those two options are refused.
## Permissions
| Permission | Grants |
|---|---|
| `runicnpc.place` | Placing, removing, renaming and inspecting NPCs, recording routes, and `follow`. |
| `runicnpc.admin` | Everything, `runicnpc.place` included: teleporting, respawning, clearing an owner, and the console-only verbs. |
On a Runic Gateway server, both permissions show in Admin → Rust → Permissions like any plugin's, and the site
grants them there.
## Placements
A **placement** is a spot where a profile's NPCs stand and come back after they die. It is saved on the server
(`data/RunicNPC/placements.json`) and survives restarts. Placements made in game are named after their profile and
a number (`bandit-1`) and can be renamed.
| Command | Does | Permission |
|---|---|---|
| `/rnpc place <profile> [options]` | Places the profile's NPCs where you are looking, and answers with the placement's name and the cost warning. | `runicnpc.place` |
| `/rnpc here <profile> [options]` | The same, where you stand. | `runicnpc.place` |
| `/rnpc remove [placement]` | Removes a placement and its NPCs: the one you name, or the one the NPC you are looking at belongs to. | `runicnpc.place` |
| `/rnpc rename <placement> <new name>` | Renames a placement. Its live NPCs stay where they are. | `runicnpc.place` |
| `/rnpc near [radius]` | Lists the placements and live NPCs around you, with distances and any note (waiting, off the navmesh). | `runicnpc.place` |
| `/rnpc info` | The NPC you are looking at: name, profile, placement or owner, health, state and target. | `runicnpc.place` |
| `/rnpc profiles` | The profiles this server has, and any it refuses with the reason. | `runicnpc.place` |
| `/rnpc follow <placement> <player\|me\|off>` | The placement's live NPCs escort a player. They fight whoever attacks that player, and walk back to their spot if the player dies or leaves. `off` ends it. | `runicnpc.place` |
| `/rnpc tp <placement>` | Teleports you to a placement. | `runicnpc.admin` |
| `/rnpc respawn <placement\|all>` | Respawns a placement's NPCs now, or every placement's. | `runicnpc.admin` |
| `/rnpc clear <owner>` | Removes every NPC an owner has, for example a stuck event run (`run:42`) or another plugin (`plugin:Name`). | `runicnpc.admin` |
**Options for `place` and `here`,** in any order:
| Option | Means | Default |
|---|---|---|
| `count=<n>` | How many NPCs stand there. | 1 |
| `respawn=<seconds>` | How long after a death before the NPC comes back. | 300 |
| `mode=each\|group` | `each`: every NPC comes back on its own timer. `group`: none comes back until all are dead, then all at once. | `each` |
| `move=wander\|monument\|route:<name>` | How the NPCs move: wander around the spot, roam the monument they stand in, or walk a recorded route. | the profile's |
| `radius=<m>` | How far a wanderer strays. | the profile's |
| `tether=<zone>` | A ZoneManager zone the NPCs never leave. Needs ZoneManager. | none |
**Every placement is checked against Rust's navmesh.** A roamer must stand on it. Only a sentry may stand off it.
On a player-built floor the placement is made with a warning: if the floor is destroyed, the NPCs fall back to the
nearest navmesh, and return to their spot once it is rebuilt.
## Routes
A route is a line of points an NPC walks. It is recorded in game where you walk, one point at a time.
| Command | Does | Permission |
|---|---|---|
| `/rnpc path record <name>` | Starts recording a route. | `runicnpc.place` |
| `/rnpc path point` | Adds the spot you stand on. A point the previous one has no walkable path to is refused. | `runicnpc.place` |
| `/rnpc path undo` | Drops the last point. | `runicnpc.place` |
| `/rnpc path save [loop\|back]` | Saves the route: `loop` walks back to the first point and round again, `back` walks it back and forth. | `runicnpc.place` |
| `/rnpc path cancel` | Abandons the recording. | `runicnpc.place` |
| `/rnpc path list` | The saved routes. | `runicnpc.place` |
| `/rnpc path delete <name>` | Deletes a route. A placement that walks it waits until a route of that name exists again. | `runicnpc.place` |
A recording is kept in memory only: a reload of RunicNPC discards it.
## Console only
| Command | Does | Permission |
|---|---|---|
| `rnpc.profile list` | The same as `/rnpc profiles`. | `runicnpc.admin` |
| `rnpc.profile show <name>` | A profile in full, as JSON, and why it is refused if it is. | `runicnpc.admin` |
| `rnpc.profile create <name> [from=<profile>]` | A new profile, empty or copied from another. | `runicnpc.admin` |
| `rnpc.profile set <name> <field> <value>` | Sets one field of a profile. | `runicnpc.admin` |
| `rnpc.profile delete <name>` | Deletes a profile. Placements that use it wait until a profile of that name exists again. | `runicnpc.admin` |
| `rnpc.faction list` | The faction table: which factions fight, ignore or help each other. | `runicnpc.admin` |
| `rnpc.faction set <a> <b> hostile\|neutral\|allied` | Sets one pair, both ways. | `runicnpc.admin` |
| `rnpc.faction clear <a> <b>` | Removes a pair. | `runicnpc.admin` |
| `rnpc.status` | The version and API, the hooks that have fired, the navmesh, the swap's field list, NPC counts by owner, profiles, placements, routes, what fighting costs a think, the caps, and the cost warning. | server console, RCON, or an admin's F1 |
| `rnpc.reload` | Re-reads profiles and routes after a hand edit of their files. | server console, RCON, or an admin's F1 |
| `rnpc.help` | The same list as `/rnpc` alone. | anyone; it lists only what they may use |
**On a Runic Gateway server the website manages the profiles and the faction table** (Admin → Rust → NPC
profiles). There, `rnpc.profile create|set|delete` and `rnpc.faction set|clear` are refused with "managed by its
website", and `show` and `list` still answer. On a server without a website they are how profiles are made.
## Reading `rnpc.status`
```
RunicNPC 1.0.0 api=6 hooks=12 fired=12 silent=0
framework=oxide navmesh=ready swap fields: npc=55/55 brain=32/32 missing=- added=-
npcs=14 awake=9 goingHome=0 asleep=5 owners: placement:bandit-1=3 run:42=11
```
- **`silent`** names a hook that has never fired. After a Rust or framework update, a hook that stays silent is the
first sign it was renamed: neither framework reports a hook that matches nothing.
- **`navmesh=building`** after a Rust update means Rust is still building the map's navmesh (about ten minutes on a
large map). Placements wait for it.
- **`swap fields`**: `missing` or `added` other than `-` means Rust's scientist has changed since this RunicNPC was
released. Update RunicNPC.

132
runicnpc/INSTALL.md Normal file
View File

@@ -0,0 +1,132 @@
# Installing RunicNPC
RunicNPC is Runic Gateway's own NPC plugin for Rust, on Oxide and Carbon. On a Runic Gateway server it is
**required**: every NPC an event places is RunicNPC's, Rust's own scientists included (D310). It also runs on any
Rust server on its own, driven by its `/rnpc` commands and by other plugins through its API.
Every command is in [COMMANDS.md](COMMANDS.md), the API is in [API.md](API.md), and the design of record is
[PLAN.md](PLAN.md).
## What it needs
| | |
|---|---|
| **Oxide 2.0.7726 or Carbon 2.0.259**, or newer | The builds it was tested on. Its release manifest states them, and the installer checks them. |
| **[Kits](https://umod.org/plugins/kits)** | **Required.** A profile equips its NPCs with Kits kits, and RunicNPC declares `// Requires: Kits`, so neither framework loads it without Kits. |
| **[ZoneManager](https://umod.org/plugins/zone-manager)** | Optional. Only a tether (`tether=` on a placement, or an event's "keep inside the zone") uses it. |
**Kits and ZoneManager are reported, never installed.** The installer and `doctor` say when either is missing, and
you install them from uMod. Runic Gateway does not fetch third-party plugins.
**Do not reload Kits while an event runs.** RunicNPC requires Kits, so both frameworks reload RunicNPC with it, and
every NPC it had spawned is gone. Placements come back on their own; an event's NPCs do not (D283).
## On a Runic Gateway server
There is nothing to do by hand. RunicNPC is part of the Rust **bundle**, the exact set of releases CI has checked
together, beside the bridge plugin and the sidecar:
- **The installer** (`install --game rust`, `update --game rust`) places `RunicNPC.cs` beside the bridge, checked
against the bundle's checksum. It refuses a bundle without RunicNPC. `doctor --game rust` fails when RunicNPC is
missing or was never installed, and warns when the file was edited by hand. `uninstall` removes `RunicNPC.cs`
and keeps `data/RunicNPC/`. The installer's own guide is [../rust-link/INSTALL.md](../rust-link/INSTALL.md).
- **The Pterodactyl egg** places it the same way on every install and reinstall.
Once the server has started, **Admin → Rust → Servers** shows the server as complete. A server without RunicNPC, or
with a RunicNPC older than the bridge needs, reads "Incomplete" with the reason, and every Place NPCs step on it is
refused with "needs RunicNPC". **Admin → Rust → NPC profiles** shows each server's RunicNPC version and API.
On such a server the website owns the NPC profiles and the faction table. It pushes them to RunicNPC, and the
console commands that would edit them are refused. Placements stay on the server: they can be made in game with
`/rnpc place` or from the site's map, and both see the same list.
## Without Runic Gateway
1. Download `runicnpc-<version>.tar.gz` and `SHA256SUMS` from the
[releases](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases), and check the tarball:
`sha256sum -c SHA256SUMS`.
2. Install Kits from uMod if the server does not have it.
3. Copy `runicnpc/RunicNPC.cs` from the tarball into `oxide/plugins/` or `carbon/plugins/`. The framework compiles
and loads it at once.
4. In the server console: `rnpc.status`. It should say `navmesh=ready` (a fresh map builds it for several minutes
after the first boot) and `swap fields: ... missing=- added=-`.
On its first load RunicNPC writes four example profiles (a raider, a camp guard, a sniper and the Juggernaut boss)
and their kits into Kits' data file, so a fresh server has something to place at once. Grant `runicnpc.place` or
`runicnpc.admin` to whoever places NPCs, then `/rnpc place raider` where you look.
**Do not create `data/RunicNPC/` yourself.** RunicNPC makes it on first load. A directory made from outside the game,
for example in a panel's file manager, is not writable by the game.
## Its files
| File | What it holds | Who writes it |
|---|---|---|
| `plugins/RunicNPC.cs` | The plugin. | The installer, the egg or you. |
| `config/RunicNPC.json` | Caps (below) and the spawn budget. | You. |
| `data/RunicNPC/profiles.json` | The profiles and the faction table. | The website on a Runic Gateway server (`"managed": true`); otherwise `rnpc.profile` and `rnpc.faction`. |
| `data/RunicNPC/placements.json` | The placements. | `/rnpc`, the API, and the website's map. |
| `data/RunicNPC/routes.json` | The recorded routes. | `/rnpc path`. |
| `data/RunicNPC/state.json` | Its own bookkeeping. | RunicNPC. |
After editing `profiles.json` or `routes.json` by hand, run `rnpc.reload`.
## The cost warning, and caps
**RunicNPC has no caps by default** (D227). Instead, every placement and every event step answers with what the
server's RunicNPC NPCs cost, and `rnpc.status` ends with the same lines:
```
RunicNPC: 100 NPC(s) on this server. Measured on a test server, they add about 3 ms to every server frame while
idle with a player near enough to wake them, and about 0.5 ms once they sleep with nobody near. If all fight at
once they add about 9 ms, and Rust's shared 2 ms AI budget then lets each react only every 3 s. ...
```
What it means:
- **Idle NPCs are cheap, and sleeping ones cheaper.** NPCs that stand, wander or patrol with nobody to fight add a
little to a frame. One with no player within its sleep distance (160 m by default) walks home and sleeps, and
then costs about a sixth of what it did awake.
- **Fighting NPCs hit Rust's AI budget before the frame rate.** All of Rust's human NPCs, its own scientists
included, share `aithinkmanager.framebudgetms` (2 ms a frame). With many fighting at once, each one reacts less
often. The server stays smooth, and the NPCs get slower to react.
- **The figures are measured, not guessed.** Stage 9 measured the released NPC on both frameworks, on a 6000
map, as what 100 add to the median server frame of the same session's empty server. Beside it is Rust's own
scientist, unchanged but for RunicNPC's swap, measured the same day: fighting costs what Rust's own AI costs.
| 100 NPCs, over an empty server | RunicNPC on Oxide | RunicNPC on Carbon | Rust's scientist (Oxide / Carbon) |
|---|---|---|---|
| Idle, a player near enough to keep them awake | +2.7 to +2.9 ms | +1.5 ms | +1.0 / +1.0 ms |
| Idle, asleep with nobody within 160 m | — | +0.5 ms | — |
| Fighting 20 players | +7.2 ms | +9.0 ms | +7.5 / +8.0 ms |
The warning uses the worst of these, rounded up: 3 ms awake, 0.5 ms asleep and 9 ms fighting per 100. The awake
figure on Oxide is higher than it should be and is being worked on
([runicnpc-rust#13](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/issues/13)). Your server's own
figures depend on its CPU, its map and its players.
To cap them anyway, set any of these in `config/RunicNPC.json` and reload RunicNPC (0 means none):
| Key | Caps |
|---|---|
| `caps.total` | RunicNPC's NPCs on the server. |
| `caps.perOwner` | NPCs of one owner: one placement, one event run, or one other plugin. |
| `caps.perProfile` | NPCs of one profile. |
| `caps.spawnsPerSecond` | How fast NPCs are spawned. |
| `spawnBudgetMs` | Milliseconds of a frame the spawn queue may use (8 by default). One NPC takes about 5 ms to spawn, so a large placement is spread over frames instead of stalling one. |
A step or placement over a cap is refused with the cap named.
## After a Rust update
Rust's monthly update can change the scientist RunicNPC is built on. After the update:
1. **Wait for the navmesh.** `rnpc.status` says `navmesh=building` while Rust rebuilds it (about ten minutes on a
large map). Placements wait for it and spawn when it is ready.
2. **Check the swap.** `rnpc.status`'s `swap fields` line should end `missing=- added=-`. Anything else means Rust's
scientist has changed since this RunicNPC was released: update RunicNPC (on a Runic Gateway server,
`update --game rust` or an egg reinstall takes the current bundle).
3. **Check the hooks.** A hook listed as `silent` long after the server has been played on may have been renamed.
Runic Gateway checks every new Rust staging build before it reaches the public branch (PLAN.md stage 9, the
staging drill), so a RunicNPC release that needs fixing is usually out before the update is.

View File

@@ -12,7 +12,8 @@ and **its build's own questions answered 2026-10-01** (D267–D272). **Stage 5 b
site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 4. **Stage 6's design answered
2026-10-05** (D273–D282, §0), **its spike measured 2026-10-05** on both rigs, and **its two questions answered the
same day** (D283, D284, §9). **Its build's own questions were answered the same day too** (D285–D289), and **stage 6 was built and walked on the site 2026-10-05** on both rigs (§9); its API is [API.md](API.md) version 5. **Stage 7's design answered 2026-10-05** (D290–D299, §0). **Stage 9's design answered 2026-10-06**
(D306–D314, §0 and §9).
(D306–D314, §0 and §9). **Phase 9b was built 2026-10-06** (§9), with its two questions answered the same day (D315,
D316).
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
@@ -136,6 +137,8 @@ architectural or design decision is implemented.
| **D312** | **The player walk is one checklist in one session per rig.** Every in-game check deferred so far is gathered with stage 9's own. I prepare the rigs and the walk site, the org lead plays, and I watch the logs and record the results (stage 9). | A checklist per stage; release without a player walk. |
| **D313** | **v1.0.0 is one coordinated cutover.** After the walk passes, `edge` goes to `main` in all five Rust repos: RunicNPC v1.0.0 first, then the bridge, the sidecar, the installer and the bundle that pins them, and the module's requirement last (stage 9). | RunicNPC alone first, the rest later. |
| **D314** | **Core's public phase label is fixed in stage 9**, in its own website PR. `eventPublic.phaseLabel` matches a phase by `key`, which specs use, and still accepts `id`; its test fixture moves to `key`. It has said "Under way" for every phase since Events Phase 14a, UO's events included (stage 9, from docs#323). | A website issue for later. |
| **D315** | **The performance check records what 100 NPCs cost today, and the cost warning says it; the idle overhead is an optimisation issue, not a release blocker.** Stage 1's own bare NPC no longer meets stage 1's bar on today's rigs (fighting +7.5 / +8.0 ms against +5.2), and RunicNPC fights at the same cost; awake idle on Oxide is about 2 ms per 100 over the bare NPC (runicnpc-rust#13). Narrows D306 (stage 9b; the org lead: "Write a warning for it, log it as an optimization bug fix issue on gitea and keep going"). | A same-session bar against the bare NPC; fixing idle before v1.0; keeping D306's absolute bar. |
| **D316** | **On a server without RunicNPC, the Place NPCs picker keeps listing Rust's own scientists, and the step is refused when saved, with the reason.** Core's option sources cannot show a module's reason (a refusal reads only "could not be read"), and Admin → Rust → Servers already says "Incomplete". Narrows 9b's "the picker shows the same reason instead of a list" (stage 9b). | A core change letting an option source answer a reason (MODULE_API minor); an empty list. |
**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
@@ -1615,6 +1618,39 @@ is asked before the next one starts.
harness of stages 1 and 5. It passes at about +1 ms and +5 ms of median frame over that session's empty baseline.
The numbers go into INSTALL and the cost warning.
**9b built (2026-10-06).** Rust 25681086, Oxide 2.0.7801, Carbon 2.0.262 on both rigs.
- **Core's phase label (D314):** website PR. `phaseLabel` finds the phase by `key`, then `id`; the fixture uses
`key`, and a unit test covers a UO-shaped spec, the fallback and key-before-id.
- **RunicNPC is required (D310):** `module-rust` asks every Place NPCs step for RunicNPC first ("Main needs
RunicNPC to place NPCs, Rust's own scientists included: RunicNPC is not loaded on it"), and its floor rises from
API 4 to the bridge's 6, so a server the site calls ready is one the bridge will not refuse. Admin → Rust → Servers
carries each server's `runicNpc` and reads "Incomplete" with the reason. The picker is unchanged (D316). The
bridge refuses one of Rust's own NPC prefabs the same way, before anything spawns, and a profile placement now
refuses an old RunicNPC up front (PROTOCOL §19.16; no message changes shape, protocol stays 13). The installer
refuses a Rust bundle without RunicNPC, `doctor` fails without it, and the bundle CI composes no Rust bundle
without a RunicNPC that answers the bridge (tested against the real releases: it refuses today's v0.1.1 bridge,
which declares no RunicNPC API, and leaves ServUO alone). **The installer had no `edge` any more**, since its
cutover deleted it; it is recreated from `main` for this, so `main`, which releases on every push, gets it at 9e.
The egg follows the bundle and is unchanged.
- **Operator documentation:** [INSTALL.md](INSTALL.md) and [COMMANDS.md](COMMANDS.md). The required note went into
[`../rust-link/INSTALL.md`](../rust-link/INSTALL.md), the Rust operator guide that lists Kits:
`modules/rust/OPERATING.md`, which this section named, is a verbatim mirror of uMod's pages.
- **The update surface:** `runicnpc-rust`'s README lists every Rust type and member the swap and the brain depend
on, where, and whether a change is caught by compiling, by the field list, or only on a running server.
- **The performance check (D306, D315):** `rnt.cost` in the test harness measures RunicNPC's own NPCs, with stage
1's `rnh.cost` beside it in the same session as the reference. 100 NPCs, increase of the median frame:
| | Oxide: bare NPC | Oxide: RunicNPC | Carbon: bare NPC | Carbon: RunicNPC |
|---|---|---|---|---|
| Idle, awake | +0.96 ms | +2.65 / +2.92 ms | +0.97 ms | +1.52 ms |
| Idle, asleep | | | | +0.52 ms |
| Fighting 20 stand-ins | +7.53 ms | +7.19 ms | +7.96 ms | +9.04 ms |
The rigs are slower than in September (an empty Oxide frame 22–30 ms, against 16.4 ms then), and between two
empty windows the median moved by up to 7 ms, so every run ends with a second empty window and is judged against
the lower. The cost warning now says 3 ms per 100 awake, 0.5 ms asleep and 9 ms fighting.
**9c. The staging drill (D307–D309).**
- **The rig:** a new Pterodactyl server, `rust-staging`, Oxide only, on a 2000 map at about 6 GB. It keeps a

View File

@@ -7,13 +7,13 @@ the design of record is [`../modules/rust/PLAN.md`](../modules/rust/PLAN.md) §3
## What this installs
Two components per Rust server, released together as a **bundle** — an exact pair CI has checked
speaks one protocol, never "the latest of each" — and, from RunicNPC's stage 4, a third:
speaks one protocol, never "the latest of each" — and a third, RunicNPC, which is required:
| Component | What it is | Released from |
|---|---|---|
| **The plugin** | `RunicGateway.cs`, one file that runs unchanged on Oxide and Carbon — and beside it, from protocol 13, the optional **ZoneManager helper** `RunicGatewayZones.cs` (PLAN_FIXES D181, D182), installed by default | [Rust-Plugins](https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins/releases) |
| **The sidecar** | `rust-link-sidecar`, which the plugin dials on loopback and the website reaches over HTTP | [Rust-Link](https://gitea.whitlocktech.com/RunicGateway/Rust-Link/releases) |
| **RunicNPC** | `RunicNPC.cs`, Runic Gateway's NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md)), placed beside the bridge **when the bundle carries it**: from RunicNPC's stage 4, the latest RunicNPC release that answers the API the bridge needs (D224). Optional until RunicNPC's stage 9; it needs **Kits**, like the bridge | [runicnpc-rust](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases) |
| **RunicNPC** | `RunicNPC.cs`, Runic Gateway's NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md)), placed beside the bridge: the latest RunicNPC release that answers the API the bridge needs (D224). **Required** since RunicNPC's stage 9 (D310): without it the bridge refuses every NPC an event places, Rust's own scientists included, and Admin → Rust → Servers shows the server as incomplete. It needs **Kits**, like the bridge. Its own guide is [`../runicnpc/INSTALL.md`](../runicnpc/INSTALL.md) | [runicnpc-rust](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust/releases) |
The game server opens no port for the bridge: the plugin is the client and the sidecar the
listener, on `127.0.0.1`. **One sidecar serves one Rust server.** A community running six servers
@@ -32,7 +32,8 @@ There are three ways to set a server up. They produce the same result:
- **Oxide or Carbon is installed, and the server has started once** with it, so its directories
exist. The bridge is a plugin; a vanilla server has nothing to load it.
- **Kits and ZoneManager** from uMod are what the reward and zone features use. The bridge works
without them and names what is missing; nothing here installs them.
without them and names what is missing; nothing here installs them. **RunicNPC needs Kits** and
will not load without it, so without Kits no event can place NPCs.
- **The website has the Rust module**, and you are an administrator on it. Servers are added at
**Admin → Rust → Servers** (`/admin/rust/servers`).
@@ -122,8 +123,8 @@ When you create a server from it:
**The install** downloads the sidecar, its launcher and the plugin from the bundle, checks each
against the bundle's checksum, and only then places them: the sidecar in `rust-link/`, the plugin in
`oxide/plugins/` or `carbon/plugins/`. When the bundle carries RunicNPC, `RunicNPC.cs` goes beside the plugin
(its data directory is left for RunicNPC to make). Any mismatch fails the install with the reason, before
`oxide/plugins/` or `carbon/plugins/`. `RunicNPC.cs` goes beside the plugin (its data directory is left for RunicNPC to make); a bundle
without RunicNPC is refused. Any mismatch fails the install with the reason, before
anything is placed.
**The first boot** prints, in the console, the token the sidecar generated — **once** — and a line
@@ -156,8 +157,8 @@ history, and the token is unchanged.
[`v2/rust/current.json`](https://gitea.whitlocktech.com/RunicGateway/installer/src/branch/bundles/v2/rust/current.json)
on the installer's `bundles` branch. It names the sidecar binary for your platform and the
plugin tarball, each with a `sha256`.
2. **Download and verify** both against those checksums (`sha256sum -c`, or `Get-FileHash`) — and, when
the bundle has an `npc` entry, RunicNPC's tarball too. Copy `runicnpc/RunicNPC.cs` from it into the same
2. **Download and verify** both against those checksums (`sha256sum -c`, or `Get-FileHash`) — and RunicNPC's
tarball, the bundle's `npc` entry, too. Copy `runicnpc/RunicNPC.cs` from it into the same
plugins directory as the bridge. **Do not create `data/RunicNPC/` yourself**: RunicNPC makes it on first
load, and one made from outside the game (a panel's file manager) is not writable by it.
3. **The plugin:** copy `runicgateway-rust-plugin/RunicGateway.cs` from the tarball into
@@ -225,7 +226,7 @@ With the installer:
| | |
|---|---|
| `doctor --game rust [--server-id <id>]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC, when the bundle carried it, the same way (without it the site's NPC profiles and placements and events' profile NPCs are off); that the plugin's config names this server; the required uMod plugins; the service; and `/health` through to **plugin connected**. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on |
| `doctor --game rust [--server-id <id>]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC as a **failure** when it is missing or was never installed (it is required: without it every NPC an event places is refused), and a warning when edited by hand; that the plugin's config names this server; the required uMod plugins; the service; and `/health` through to **plugin connected**. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on |
| `update --game rust` | Moves the sidecar and every server's plugin to the current bundle, and restarts the sidecars. Always all servers together — they share one binary |
| `uninstall --game rust [--server-id <id>] [--purge]` | Removes the service, the plugin file, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's `data/RunicNPC/` (an admin's placements and routes) — it is the website's, and it names the server. `--purge` also removes the sidecar config (the token) and the database. Removing the last server removes the shared binary too |

View File

@@ -2401,8 +2401,9 @@ in-game rows (flags on a player, the messages) wait for the later in-game walk.
RunicNPC is Runic Gateway's own NPC plugin ([`../runicnpc/PLAN.md`](../runicnpc/PLAN.md), [`API.md`](../runicnpc/API.md)).
**The bridge is its only link to the site**: it calls RunicNPC's API for the site's commands and turns
RunicNPC's hooks into frames, and RunicNPC never talks to the sidecar, so "the sidecar is a dumb forwarder"
stays true. RunicNPC is optional until its stage 9. Without it every `npc.*` command answers `npc.error`
**`runicnpc-missing`**, and an event places Rust's own scientists only (D243).
stays true. Without it every `npc.*` command answers `npc.error` **`runicnpc-missing`**. Until its stage 9 an
event then placed Rust's own scientists only (D243); since stage 9 RunicNPC is required, and an event places no
NPCs at all without it (§19.16).
The bridge needs **RunicNPC API 3** (D249), `RunicNpcApiNeeded` in the code and **`runicnpc_api = 3` in
`overlay.toml`**, which the release copies into its manifest and `checkPlugin.js` holds equal to the code.
@@ -2614,3 +2615,19 @@ Stage 7's wire change **joins protocol 13** while it is unreleased (D299); no me
`false` spawns the NPCs with RunicNPC's override `{"loot":{"dropTable":false}}`: they keep what their profile
starts the corpse with, and roll no loot table. Absent or `true`, as before. The site sends it only when an event's
Place NPCs step switches it off.
### 19.16 RunicNPC stage 9: RunicNPC is required (runicnpc PLAN.md stage 9)
**No message changes shape**, and `PROTOCOL_VERSION` stays 13. What changes is which `world.place` the bridge
refuses (D310): **every placement of NPCs needs RunicNPC loaded and answering `RunicNpcApiNeeded` (6)**, a
`profile` placement and one of Rust's own NPC prefabs (`npc.scientist`, `npc.bandit.guard`, …) alike. Crates are
unaffected. The refusal is checked before anything is spawned, with the two reasons that already exist:
| `reason` | Means | Retried? |
|---|---|---|
| `runicnpc-missing` | RunicNPC is not loaded, so this server places no NPCs | no |
| `runicnpc-old` | RunicNPC answers an API older than the bridge needs | no |
The site checks the same thing first, from the hello's `integrations.runicNpc`, and refuses the step before it
is sent; the bridge's check is for a site that missed the hello. `rg.npc` says the same when RunicNPC is not
loaded.