docs(runicnpc): stage 7 spike, measured on both rigs; D300–D301 #319

Merged
whitlocktech merged 3 commits from docs/runicnpc-stage7-spike into main 2026-10-06 07:17:04 +00:00

View File

@@ -120,6 +120,8 @@ architectural or design decision is implemented.
| **D297** | **The locked crate's hack timer is the profile's**, Rust's own 15 minutes by default (stage 7). | Always Rust's 15 minutes. |
| **D298** | **On a server with no website, a loot table is edited in the data file** (`data/RunicNPC/profiles.json`) and read again with `rnpc.reload`. No new in-game commands (stage 7). | `rnpc.loot` verbs; JSON through `rnpc.profile set`. |
| **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. |
**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
@@ -1356,6 +1358,62 @@ The org lead's answers on 2026-10-05 are D290–D299. **It is one stage, opening
**Tested by** harness kills whose corpses and crates are read back, 100 times each, against the table's chances
and the pool's cap; and a walk on both rigs.
**Measured (2026-10-05, `tools/RunicNpcTest.cs`: `rnt.spike7`).** Both rigs, on Rust 2634.289.1 (buildid
25681086), Oxide 2.0.7801 and Carbon 2.0.262, each checked against the latest first. 13 of 14 checks passed on each
rig; the fourteenth could not be run from outside our NPC (below).
**1. The corpse (D290).**
- **Today's corpse holds Rust's scientist loot, and the kit's clothing.** The main container gets the prefab's loot
slots (for example a salvaged hammer, tech parts, pistol and rifle ammunition and a bandage; on another kill only 12
rifle ammunition). The clothing container holds what the NPC wore (the kit's burlap and boonie). Its belt and main
items (the revolver and its ammunition) are not in the corpse. Rust removes the corpse after **600 s**.
- **Both frameworks raise `OnCorpsePopulate(npc, corpse)` for our NPC**, once, after Rust filled the corpse.
- **A subclass cannot fill its own corpse on both frameworks.** Rust's `HumanNPC.ApplyLoot` is `protected` in
Oxide's assembly and `public` in Carbon's (Carbon publicises Rust's assembly), and an override cannot change a
member's access, so one source file cannot override it for both. `CopyInventoryToCorpse` is a read-only virtual
property, with the same risk. **So the build fills the corpse in `OnCorpsePopulate`:** it keeps or empties Rust's
loot, adds the kit's belt and main items for a kit start, then rolls the table. Emptying a corpse and filling it
there worked on both rigs (`scrap×150` alone).
- The kit start could not be measured from the harness, which cannot switch the property from outside. It is built
and tested in the build.
**2. The crates (D292).** Each of Rust's four crates was spawned at a death spot with Rust's own loot switched off
before it spawned (`initialLootSpawn` false, no refresh), then filled:
| Crate | Prefab | Slots | Rust's loot at spawn | Ours after | Refills | Gone when emptied |
|---|---|---|---|---|---|---|
| Wooden box | `crate_normal_2` | 6 | none | scrap×200 | no | yes |
| Military crate | `crate_normal` | 6 | none | scrap×200 | no | yes |
| Elite crate | `crate_elite` | 12 | none | scrap×200 | no | yes |
| Locked crate | `codelockedhackablecrate` | 24 | none | scrap×200 | no | yes |
A table larger than the crate's slots will not fit; the build says so on the profile.
**3. The locked crate's timer (D297).** Rust's is one server-wide setting, `hackablelockedcrate.requiredhackseconds`
(900 s), and each crate counts its own seconds up to it. **Starting a crate's count where the profile's time
remains gives each crate its own timer:** asked 10 s, it unlocked after 11.5–12.1 s; asked 20 s, after
21.1–22.4 s (Rust ticks once a second). Asked longer than Rust's 900 s (1,800), the count starts below zero
(−898 after 3 s) and still unlocks on time; **what a player's screen shows for a count below zero is not known**
without a client, so the walk checks it. An unhacked locked crate is removed after Rust's own `decaySeconds`
(7,200 s).
**4. Removing the corpse (D294).** `ResetRemovalTime(5)` removed it after 5.1–5.3 s; a corpse killed at once is gone.
**What this sets for the build, with nothing to decide:**
- The corpse is filled in `OnCorpsePopulate`, not by overriding Rust's members.
- A crate spawns with Rust's loot off and no refresh, and is filled with the table.
- The locked crate's timer is its starting count.
- A corpse's time is `ResetRemovalTime`, or it is killed at once.
**Open for the walk:** a locked crate set longer than 15 minutes, as a player's screen shows it.
**The build's own questions, answered by the org lead on 2026-10-05 (D300, D301):**
1. **A "nothing" corpse and the clothes it wore. → D300:** truly empty, clothes too.
2. **How long an unopened crate lasts. → D301:** until it is emptied, as Rust's crates do.
### Stage 8 — On the map and in the app
Event NPCs and bosses on the live map (protocol 11's `map.live`), a live "left: 3/8" on the event page, and the same