docs(rust): stage 6 built — Guarded Crate (PROTOCOL §20.9, PLAN_REDESIGNS §14.3.19, D380)

- rust-link/PROTOCOL.md §20.9: hello's guardedCrate with npcSpawn and events,
  gc.started/ended/failed, gc.place and gc.error's reasons, guardedWon, map.live's
  guarded rows and how they are kept, POST /gc/place, and the website side.
  §17.3's world row gains guarded.
- modules/rust/PLAN_REDESIGNS.md: D380 (the kept crate's run-log note at
  placement), §14.3.19 the stage record, pointers from §9 and §14.3.5.
- modules/rust/OPERATING.md: Runic Gateway's own section on Guarded Crate and
  NpcSpawn (install, images, what it contacts at load), after the uMod mirror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-15 12:26:53 -05:00
parent 966ba41deb
commit a305e98e05
3 changed files with 248 additions and 2 deletions

View File

@@ -5,6 +5,8 @@ configure and permission plugins. This is the surface our own deployment story h
of, the way [`installer/INSTALL.md`](../../installer/INSTALL.md) sits on top of ServUO.
> **Mirrored verbatim from uMod on 2026-09-15** — 6 pages. uMod is upstream and normative.
> The last section, [Guarded Crate and NpcSpawn](#guarded-crate-and-npcspawn), is Runic Gateway's own and
> is not part of the mirror.
---
@@ -16,6 +18,7 @@ of, the way [`installer/INSTALL.md`](../../installer/INSTALL.md) sits on top of
- [Plugin configuration](#plugin-configuration-plugins-configuration) — Editing a plugin's config file
- [Data Files](#data-files-plugins-data-files) — Where plugin data lives on disk
- [Permissions](#permissions-plugins-permissions) — Granting permissions and managing groups in-game
- [Guarded Crate and NpcSpawn](#guarded-crate-and-npcspawn) — Runic Gateway's note on an optional plugin that needs a Codefling extension
---
@@ -369,3 +372,41 @@ That's the basics to permissions for Oxide. Permissions give you a fantastic way
---
<sub>Mirrored from uMod, 2026-09-15.</sub>
---
<a id="guarded-crate-and-npcspawn"></a>
## Guarded Crate and NpcSpawn
<sub>Runic Gateway's own section, not mirrored. Design: [PLAN_REDESIGNS.md](PLAN_REDESIGNS.md) §14.3.5 (D361,
D364); wire: [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20.9.</sub>
Bazz3l's **Guarded Crate** (uMod) is one of the optional plugins Runic Gateway reports and drives: a guarded
crate landing and being cleared reach the site's feed, map and *Warden* title, and an event can place one. It
**needs NpcSpawn** for its guards, and NpcSpawn is not on uMod. Runic Gateway never ships or modifies either
(D351, D361); an operator who wants guarded crates installs both.
**Installing NpcSpawn.**
1. Download NpcSpawn from Codefling (<https://codefling.com/extensions/npc-spawn>), a free download.
2. Put `NpcSpawn.cs` in the plugins directory (`oxide/plugins`, or `carbon/plugins` on Carbon).
3. Put the archive's `data/Images/` files in the data directory's `Images` folder (`oxide/data/Images`, or
`carbon/data/Images`). **Without them NpcSpawn unloads itself.**
4. Leave `data/NpcSpawn/` for NpcSpawn to create. On Carbon, a folder made by a panel's file manager may not be
writable by the game, and NpcSpawn then fails until that folder is deleted and left to it.
5. Install Guarded Crate from uMod as any other plugin.
**What NpcSpawn does at load: it contacts its author's server.** It calls a version server at a bare IP
address (seen as `37.153.157.216:5000` in the §14.2 test) and downloads clothing images. An operator whose
server may not reach the internet, or who does not want it to, should know this before installing it.
**Without NpcSpawn,** Guarded Crate unloads itself the next time its own schedule starts an event, and Runic
Gateway's *Place a guarded crate* step is refused with *"Guarded Crate is loaded but NpcSpawn is missing: its
events cannot start"*. The bridge refuses that placement itself, because Guarded Crate's `position` command does
not check and would drop a crate with no guards. Admin → Rust servers shows NpcSpawn beside Guarded Crate.
**Its events** are rows in Guarded Crate's **data** file, `data/GuardedCrate.json` (Easy, Medium, Hard and Elite
as it ships), not its config. The site offers whatever names that file holds. A crate placed by an event is
**kept when the event ends** (D373): Guarded Crate has no way to end one event, and `gcrate stop all` ends every
one on the server.

View File

@@ -930,7 +930,7 @@ pulls) is research, not a walk, so it still comes first:
answered before code: **written on 2026-10-14 as §14.3**, from D361–D369, and answered as D370–D376. Then the
build, in §14.3.11's nine stages (two of them core), §13 first. **Stage 1, §13, was built on 2026-10-15
(§14.3.14)**, with D378 and D379. **Stage 2, core's `retryAfterMs` (MODULE_API 1.15.0), was built on
2026-10-15 (§14.3.15).** **Stage 3, TruePVE in the zone step, was built on 2026-10-15 (§14.3.16)**, and **stage 4, Dynamic PVP, the same day (§14.3.17)**, and **stage 5, Dangerous Treasures, the same day (§14.3.18).**
2026-10-15 (§14.3.15).** **Stage 3, TruePVE in the zone step, was built on 2026-10-15 (§14.3.16)**, and **stage 4, Dynamic PVP, the same day (§14.3.17)**, and **stage 5, Dangerous Treasures, the same day (§14.3.18)**, and **stage 6, Guarded Crate, the same day (§14.3.19).**
**Then one walk, on Oxide and Carbon, of everything still owed (D331):** §2.7; §11's rows; §12.9; §13's and
§14's rows; the in-game rows
@@ -1094,6 +1094,7 @@ The org lead answered §14.3's Q26–Q32 on 2026-10-14:
| **D377** | **The leaderboard changes only for something new and substantial.** A new title may add its column (*Safecracker*, *Scavenger*, *Treasure Hunter*, *Warden*, *Hoarder* do), but a small plugin's own numbers, such as ZLevels' levels, never become columns (org lead, 2026-10-14, confirming D374 beside D375). | Adding a column for every number a plugin reports. | 14.3.1, 14.3.6 |
| **D378** | **The Chinook is called with no position.** It drops only at the map's own drop zones (seven on the rigs' map, all at monuments), and the one route that sends it to a chosen zone borrows the Airfield call terminal's shared state, so a player at the real terminal and a step calling at once would race for one Chinook (stage 1's spike, org lead 2026-10-15). The airdrop and the patrol helicopter take a position. | A position snapped to the nearest drop zone through the terminal's state, refused while a real Airfield call is active. | 14.3.1, 14.3.14 |
| **D379** | **`chinook.downed` is a 24th world event**, with the credit block and the operator's rule, and counts toward *Rotorbane*: D174 counted Chinook kills there, and the plan's first draft would have dropped them (org lead, 2026-10-15). | Counting Chinook kills through the old tally, with no line; patrol helicopters only. | 14.3.1, 14.3.14 |
| **D380** | **A guarded crate left standing is noted in the run log when it is placed, not when the run ends:** the *Place a guarded crate* step's success line says the crate is kept when the run ends and how it ends (org lead, 2026-10-15). Core gives a module no hook at a run's end for a step that is kept, so D373's note at the end would need new core work. | A run-end hook in core now (a MODULE_API minor, with the Module-uo proof), or filed for later. | 14.3.5, 14.3.19 |
## 11. First-class optional plugins (D199–D202)
@@ -2627,6 +2628,10 @@ the bridge already reads Kits' and Economics' (PROTOCOL.md §16.3), and absent w
column and the trigger; let one time out; cancel the run and see the crate stay; unload NpcSpawn and see the
step refused with the reason. On Carbon: refused (D363).
> **Built on 2026-10-15** (§14.3.19), specified in PROTOCOL.md §20.9. The events are in Guarded Crate's data
> file, not its config; the location is a world call's (monument or point, no grid); the run-log note is written
> at placement (D380); and the Carbon row now expects the step to work. §14.3.19 says how.
#### 14.3.6 ZLevels Remastered (D369, D374)
**Hello.** `integrations.zLevels`: `{ loaded, version }`.
@@ -3098,6 +3103,81 @@ actions, 11 budgets, 21 option sources; the 89 routes unchanged.
hotload the bridge while a chest stands and see it still on the map (the seed); and start one from a run and
read its `dt.started` row's `key` from the staff events API (the web client has no staff feed view).
#### 14.3.19 Stage 6, built: Guarded Crate, with NpcSpawn (2026-10-15)
Built in Rust-Plugins, Rust-Link and Module-Rust, specified in [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20.9
and §17.3, with the operator's section in [OPERATING.md](OPERATING.md). `PROTOCOL_VERSION` stays 14. Builds:
Rust 25902425 (changeset 167781), Oxide 2.0.7830, Carbon 2.0.262 production (the latest of each that day); the
code read is Guarded Crate 2.0.17, still the newest on uMod, and NpcSpawn 3.4.8. **D380** answers the one point of
§14.3.5 that could not be built as written. No MODULE_API change: `coreApi` stays `^1.15.0`.
**What §14.3.5 had wrong or unsaid, found in Guarded Crate's source and on the rigs:**
1. **The events are in its data file, not its config.** `data/GuardedCrate.json`'s `CrateEventEntries`, each
name under the key `event display name)` (the stray bracket is Guarded Crate's). Its console command is a
config value, read from the config.
2. **`gcrate position` does not check NpcSpawn** (only `start` does). Without NpcSpawn a placed crate lands with
no guards and nothing stops a hack, so `npcspawn-missing` is the bridge's own refusal, not a courtesy.
3. **The key's join is exact**, as Dangerous Treasures' is (§14.3.18): the start hook fires inside the call that
creates the event. §14.3.9's "first start within ten seconds" is not needed for either.
4. **Its unload and `stop all` raise no hook**, and the ended hook does not say which event ended. The bridge
reads Guarded Crate's private list of running events by reflection (it has no API that lists them) to seed
the map after a hotload, to drop events `stop all` ended, and to tell which event a clearing ended.
5. **Its cargo plane was an airdrop.** Stage 1 announces every cargo plane's spawn as `airdrop.called`, so each
guarded crate also said *"An airdrop was called"*; found on the rig, fixed here with Guarded Crate's own
`API_IsGuardedCrateCargoPlane`. §14.3.1's walk row *"a Guarded Crate crate raising no Rust frame"* now holds
for the plane as well as the crate.
6. **The run-log note is written at placement (D380):** core has no run-end hook for a kept step.
7. **The location is a world call's**: a monument with offsets, or a point. §14.3.5 said "monument, grid or
point", but no step takes a grid; the zone and call steps take these two.
8. **The Carbon row is out of date**, as §14.3.18 found for DT: the console probe is true on the Carbon rig, and
`gcrate` ran there. The step is expected to work on Carbon.
**Settled in the code, and overrulable:**
1. **A clearing counts only for a player.** A last guard killed by another NPC (RunicNPC's, say) or by nothing
is `gc.ended` with no winner: no *Warden* count, no trigger.
2. **`rust.gc.ended` fires only for a crate a player cleared**; an ending with nobody, and a timeout, raise no
trigger. Both are still feed lines (*"A guarded crate ended with nobody clearing it"* public, the timeout
staff's).
3. **Whether anybody cleared it (`won`) is public**; the clearer's name is the presence audience's, the Steam
id, the position and a step's key staff's.
4. **The event param is a name, not `server/name`.** The location already names the server; the list offers
every server's names once each, with the servers that have them, and Guarded Crate's four defaults where no
server has said. Verify refuses a name the chosen server's report does not list.
5. **`gc-too-close` is retried** on core's ordinary delay (the other crate may be cleared); every other plugin
refusal is permanent.
6. **A height is not sent**: the crate falls to the ground and its guards stand on it.
7. **The column is *Crates cleared***, shown where the server reports Guarded Crate or a row counts one, as
Treasures is.
8. **Where Guarded Crate's list cannot be read** (its shape changed), the hooks alone keep the set, and the log
says so once.
**Tests.** Bridge: `checkPlugin.js` (84 hooks, every one void and listed), 18 tests. Sidecar: 73 tests (one new,
the stamp), `fmt` and `clippy` clean. Module-Rust server: `test/guardedCrate.test.js`, 16 new (the public read
the grid, never the position or the key; the clearer's name for the presence audience and the Steam id for
staff; an ending with nobody is not a win; the feed reads a stored clearing per viewer; the report read
leniently; the step kept, priced and without a revert; the event list from every server and the defaults; the
dry run's refusals for Guarded Crate absent, NpcSpawn missing, the console unavailable and an unknown event,
ignoring case, and a server that has not said left to the plugin; the key, event and location on the wire and
the kept note; retries and permanent refusals; a repeat; the tally's count; the column's sort; the triggers and
their variables; a crate nobody cleared and a timeout firing nothing; an old crate telling nobody), and the
catalogue, title and registration tests extended: 643/643, imports, bundle, swagger and engagement checks.
Client: three new in `feed.test.js`, 95/95, the build and externals. Booted against the pinned core: 47 triggers,
14 actions, 12 budgets, 22 option sources; the 89 routes unchanged.
**On the rigs**, both: the bridge compiled and loaded; with Guarded Crate 2.0.17 and NpcSpawn 3.4.8 loaded,
`rg.world` read the four events from the data file and Guarded Crate's list as readable; `gcrate start` and
`gcrate position` (in the fully quoted form `ConsoleSystem.Run` sends) each counted a running crate and said
`gc.started`, with no `airdrop.called` after the fix; a hotload of the bridge found the running crate again
(*"1 guarded crate(s) after load"*); and `gcrate stop all` took it off. Both rigs were then cleaned of the two
plugins and their files, and put back on the protocol-13 bridge. The clearing, the timeout, the step through
the sidecar and the site are §14.3.5's walk rows, at the end walk (D331, D359).
**Walk rows** are §14.3.5's, with the Carbon row expecting the step to work, and three more: a Guarded Crate
placement raising no airdrop line; `gcrate stop all` taking a crate off the map; and a crate's guards killed by
a RunicNPC NPC, which clears it for nobody.
### 14.4 Questions for the org lead
**Answered on 2026-10-14:** Q17 as proposed, out (D358). **Q18 against the proposal: the end walk waits for