docs(runicnpc): stage 8 decisions D302–D305 and its design #321

Merged
whitlocktech merged 1 commits from docs/runicnpc-stage8-decisions into main 2026-10-06 07:17:45 +00:00

View File

@@ -122,6 +122,10 @@ architectural or design decision is implemented.
| **D299** | **Stage 7 is one stage, opening with a spike on both rigs, walked once end to end**, as stages 5 and 6 were (D253, D282) (stage 7). | 7a (the table and corpse) and 7b (the crates), each walked and merged before the next. |
| **D300** | **A corpse that starts with "nothing" (D290) is truly empty:** the clothes the NPC wore come off too, so it lies unclothed and holds nothing until its table is added (stage 7 build). | Keep the worn clothes, as on any Rust corpse. |
| **D301** | **A dropped crate stays until it is emptied**, as Rust's own crates do. The locked crate still removes itself after Rust's own time unhacked (7,200 s) (stage 7 build). | The profile sets how long an unopened crate stays, 30 minutes by default. |
| **D302** | **The live map shows an event's NPCs, a boss's adds among them, and every boss**, a boss placed outside an event too. A placement's ordinary NPCs (guards at Outpost) are not shown (stage 8). | Event NPCs only; every RunicNPC NPC as its own layer. |
| **D303** | **An NPC's marker says its name and its event; a boss is a bigger marker.** No health on the map (stage 8). | A boss's health on its marker; unchanged "Event NPC" dots. |
| **D304** | **While an event runs, its page has a line per Place NPCs step, and a boss as its health:** "Bandits: 3 of 8 left", "The Juggernaut: 62%". A boss's adds are not counted (stage 8). | One total with the adds; bosses only. |
| **D305** | **Core may change as needed, as long as core stays game-agnostic and the UO integration does not break** (stage 8, the org lead, on how the line reaches core's event page and the app). | — |
**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
@@ -1467,6 +1471,52 @@ in the Android app.
**Tested by** a browser walk and an emulator walk.
**The org lead's answers on 2026-10-06 are D302–D305.**
**What is there already.**
- **The map.** `map.live`'s `events` layer lists what a run's steps placed, re-read where each thing stands on every
ask. An event NPC is already there, as `kind: "npc"` with its `prefab` (`profile:bandit` for a RunicNPC profile).
The site draws it as a plain dot whose tooltip is "Event NPC"; the app draws the same.
- **Missing from the map:**
- a boss's adds, which RunicNPC spawns itself, so no step ledgered them;
- a boss placed outside an event;
- any name.
- **The event page is core's** (`website/EVENTS.md` §I). It shows the run's state and its phase's label, and has no
place for anything a module knows.
- **RunicNPC already answers everything the map needs.** `RunicNpc_List` carries each NPC's name, owner (`run:<id>` for
an event's), health and `boss`. The bridge's ledger keeps the step key each NPC was placed by.
**The design (D305).**
- **Core: `MODULE_API` 1.12.0, one optional function on an event action, `live`.**
- While a run is live, core calls `live` for each step whose action declares one, with the step's ledgered refs and
the viewer (signed in or not, staff or not). The answer is `null`, `{ label, left, of }` or `{ label, percent }`.
- Core puts the answers in the public run as `live`, in step order, and renders them as text it does not
interpret: "Bandits: 3 of 8 left", "The Juggernaut: 62%". The app does the same from the same field.
- A call has a deadline and never throws into the page: a module that is slow or down gives no line, not an error.
The answers are cached per run for a few seconds, so a busy page does not reach the game once per reader.
- **This is an addition, so it is a minor bump.** Module-uo declares `^1.10.0` and registers no action with `live`,
so its pages and actions are unchanged. The integration kit's pin (an equality check) goes red on the bump by
design, until the kit is read again and the pin moved.
- **The bridge (protocol 13, still unreleased): `map.live`'s rows for RunicNPC.**
- An event NPC's row gains `name`, `key` (the step that placed it) and `boss: true` for a boss.
- A boss's adds join the `events` layer under their boss's run, with `add: true`.
- A boss placed outside an event joins the **`world` layer** as `kind: "boss"`, with its `name`. It is in the open
world as cargo or a helicopter is, and is shown or hidden with them.
- For the event page, a step's live NPCs and a boss's health come from the same rows. RunicNPC's health is read
there, and the site's map drops it (D303).
- **Module-Rust.**
- The map: a marker's tooltip is its name and its event's title; a boss is bigger (D303).
- `rust.npc.place`'s `live`: the step's NPCs still standing, of the count it placed, labelled with the profile's
name; a boss step answers its health instead (D304).
- Each follows the map's events layer visibility, so a page never tells a viewer what the map would hide.
- **The app.** The map's markers as the site's. The event screen renders the run's `live` lines.
- **Docs.**
- `website/MODULE_API.md` 1.12.0 and `website/EVENTS.md` §I.
- `rust-link/PROTOCOL.md` §17.3.
- The integration kit, read again and its pin moved.
### Stage 9 — Hardening and release
- **Performance to the budget stage 1 measured:** 100 NPCs on a 6000 map without the frame time crossing it.