docs(rust): record the answers to Q26-Q32 (D370-D376)

D370 a trigger can start a run: core's `starts` (trigger, conditions,
cooldown, one run at a time), written as §14.3.12. D371 a step's
retryAfterMs, written as §14.3.13, used by DT's warm-up. D372 the operator
picks the helicopter/Bradley credit rule (killing blow, last hit, most
damage, everyone); the plugin sends every candidate and the site counts,
amending D174. D373 a guarded crate outlives its run. D374 no leaderboard
change for ZLevels, so its levels wire and table are dropped. D375
Treasure Hunter, Warden, Hoarder. D376 the public line names a player
only for the presence audience. Build order is now nine stages.

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-14 21:26:25 -05:00
parent d99c8e15b5
commit 2163bf7aeb

View File

@@ -927,8 +927,8 @@ pulls) is research, not a walk, so it still comes first:
8. **Permissions as event rewards** (§12), built on 2026-10-12. 8. **Permissions as event rewards** (§12), built on 2026-10-12.
9. **Rust's own world events and six more optional plugins** (§13, §14). First comes one test phase covering 9. **Rust's own world events and six more optional plugins** (§13, §14). First comes one test phase covering
all of them on both rigs (§14.2, D352); **it ran on 2026-10-14 (§14.2.1)**. Then one integration plan, all of them on both rigs (§14.2, D352); **it ran on 2026-10-14 (§14.2.1)**. Then one integration plan,
answered before code: **written on 2026-10-14 as §14.3**, from D361–D369, with Q26–Q32 open. Then the 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 seven stages, §13 first. build, in §14.3.11's nine stages (two of them core), §13 first.
**Then one walk, on Oxide and Carbon, of everything still owed (D331):** §2.7; §11's rows; §12.9; §13's and **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 §14's rows; the in-game rows
@@ -1078,6 +1078,18 @@ The org lead answered the phase's Q19–Q25 on 2026-10-14:
| **D368** | **TruePVE in the zone step (Q24):** a *"no PvP protection in this zone"* checkbox (`exclude`), plus an optional ruleset name typed by staff, which the bridge validates by `AddOrUpdateMapping`'s own `false`. | Checkbox only; reading TruePVE's config file. | 14.2.1, 3 | | **D368** | **TruePVE in the zone step (Q24):** a *"no PvP protection in this zone"* checkbox (`exclude`), plus an optional ruleset name typed by staff, which the bridge validates by `AddOrUpdateMapping`'s own `false`. | Checkbox only; reading TruePVE's config file. | 14.2.1, 3 |
| **D369** | **Wipes mirror the plugin (Q25):** the site shows what ZLevels and Server Rewards hold and does nothing at a wipe; ZLevels columns are *current level*, not *this wipe*. | Per-wipe snapshots of our own; a site-side wipe reset. | 14.2.1 | | **D369** | **Wipes mirror the plugin (Q25):** the site shows what ZLevels and Server Rewards hold and does nothing at a wipe; ZLevels columns are *current level*, not *this wipe*. | Per-wipe snapshots of our own; a site-side wipe reset. | 14.2.1 |
The org lead answered §14.3's Q26–Q32 on 2026-10-14:
| # | Decision | Rejected | § |
|---|---|---|---|
| **D370** | **A trigger can start a run (Q26).** Core gains a definition's `starts` (a trigger, conditions and a cooldown), one run at a time per definition, `event.*` triggers excluded; core work on the website's own plan, its own PR with the Module-uo proof. | Only phase gates; an event that waits for the trigger in its first phase. | 14.3.12 |
| **D371** | **A failed step may name its retry delay (Q27):** `retryAfterMs`, 1–30 min, counted as one attempt, a MODULE_API minor bump. DT's warm-up retries ten minutes later through it. | The bridge holding the request and starting DT itself later. | 14.3.4, 14.3.13 |
| **D372** | **The operator chooses who is credited with a helicopter or a Bradley (Q28):** killing blow (the default; for the helicopter, the crippling hit, else the last hit), last hit, most damage, or everyone who hurt it. The plugin sends every candidate; the site applies the server's rule to the feed line and to *Rotorbane* / *Tankbreaker* alike. **This amends D174.** | The crippling hit as the one fixed rule; D174's 60 s last-hitter. | 14.3.1 |
| **D373** | **A guarded crate outlives its run (Q29):** nothing is removed at teardown; the step says so and the run log notes a crate left standing. | Ending every guarded crate on the server with `gcrate stop all`. | 14.3.5 |
| **D374** | **The leaderboard is not changed for ZLevels (Q30):** no column and no title, because most of its skills already line up with counts of ours. ZLevels is reported at hello, and its XP boosts are lent as permissions (§12). | One total column; six columns; one column with a skill picker. | 14.3.6 |
| **D375** | **Three new titles (Q31):** *Treasure Hunter* (DT treasures won), *Warden* (guarded crates cleared), *Hoarder* (the most RP, above 0), renamable, each with a column on servers that report the plugin. | No titles for the plugins' counts. | 14.3.4, 14.3.5, 14.3.7 |
| **D376** | **A public line names a player only for the presence audience (Q32):** the event and its grid are public (*"Patrol helicopter downed at G8"*), and the name shows only to the audience the operator chose for presence. | The name always public (D353 as written); the whole line hidden below the presence audience. | 14.3.1 |
## 11. First-class optional plugins (D199–D202) ## 11. First-class optional plugins (D199–D202)
The org lead named four plugins the module supports as first-class integrations. Kits already is one. The other The org lead named four plugins the module supports as first-class integrations. Kits already is one. The other
@@ -2339,7 +2351,7 @@ Written on 2026-10-14 from §14.2.1 and D353–D369, against the code on `edge`
at protocol 14 with §2, §11.2, §11.3 and §12 in), and **answered by the org lead before any code**. For §13 and at protocol 14 with §2, §11.2, §11.3 and §12 in), and **answered by the org lead before any code**. For §13 and
each of the six it says what hello reports, what goes on the wire, what the site does, which helper (none), the each of the six it says what hello reports, what goes on the wire, what the site does, which helper (none), the
tests and the walk rows. §14.3.9 gathers the points the plan settles itself, so they can be overruled, and tests and the walk rows. §14.3.9 gathers the points the plan settles itself, so they can be overruled, and
§14.3.10 asks Q26–Q32. §14.3.10 asks Q26–Q32, answered as D370–D376; §14.3.12 and §14.3.13 are the two pieces of core work they add.
**Everything here is protocol 14 (D357)**, specified in [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20 as each **Everything here is protocol 14 (D357)**, specified in [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20 as each
piece lands, and released with the rest after the end walk. **No helper plugin is needed (D351):** every route piece lands, and released with the rest after the end walk. **No helper plugin is needed (D351):** every route
@@ -2362,9 +2374,9 @@ player is `steamId` and `name`.
|---|---|---| |---|---|---|
| `heli.arrived` | `OnEntitySpawned(PatrolHelicopter)` | `x`, `z` | | `heli.arrived` | `OnEntitySpawned(PatrolHelicopter)` | `x`, `z` |
| `heli.left` | `OnHelicopterRetire` | `x`, `z` (last seen) | | `heli.left` | `OnHelicopterRetire` | `x`, `z` (last seen) |
| `heli.downed` | the first `OnPatrolHelicopterKill` that names a player, else D174's last-hitter at the crash (Q28) | who, `x`, `z` | | `heli.downed` | the crash's `OnEntityDeath`, with the first `OnPatrolHelicopterKill`'s player kept as the killing blow | every credit candidate (D372, below), `x`, `z` |
| `bradley.spawned` | `OnBradleyApcInitialize`. **Road Bradleys come twelve at once, so their spawns in one second are one frame with `count`** | `where: launch_site \| road`, `count` | | `bradley.spawned` | `OnBradleyApcInitialize`. **Road Bradleys come twelve at once, so their spawns in one second are one frame with `count`** | `where: launch_site \| road`, `count` |
| `bradley.destroyed` | `OnEntityDeath(BradleyAPC)` (D366) | who, `where`, `x`, `z` | | `bradley.destroyed` | `OnEntityDeath(BradleyAPC)` (D366) | every credit candidate (D372), `where`, `x`, `z` |
| `cargo.arrived` / `cargo.docking` / `cargo.docked` / `cargo.undocked` | `OnEntitySpawned(CargoShip)`, `OnCargoShipHarborApproach`, `…HarborArrived`, `…HarborLeave` | `x`, `z`; the three harbour types add `harbour` (the nearest `harbor_*`) | | `cargo.arrived` / `cargo.docking` / `cargo.docked` / `cargo.undocked` | `OnEntitySpawned(CargoShip)`, `OnCargoShipHarborApproach`, `…HarborArrived`, `…HarborLeave` | `x`, `z`; the three harbour types add `harbour` (the nearest `harbor_*`) |
| `cargo.left` | `OnCargoShipEgress`, which did not fire within §14.2's window; else the ship's `OnEntityKill`. Stage 1 confirms which | `x`, `z` | | `cargo.left` | `OnCargoShipEgress`, which did not fire within §14.2's window; else the ship's `OnEntityKill`. Stage 1 confirms which | `x`, `z` |
| `chinook.arrived` | `OnEntitySpawned(CH47HelicopterAIController)`, **except the oil-rig reinforcement**, which is told by the crate hack it answers | `x`, `z` | | `chinook.arrived` | `OnEntitySpawned(CH47HelicopterAIController)`, **except the oil-rig reinforcement**, which is told by the crate hack it answers | `x`, `z` |
@@ -2394,9 +2406,37 @@ player is `steamId` and `name`.
**The tally** gains `cratesHacked` and `dropsLooted` (D356): a hack counts for its starter at `crate.hack.started`, **The tally** gains `cratesHacked` and `dropsLooted` (D356): a hack counts for its starter at `crate.hack.started`,
once per crate, from any location except `other`; a drop counts for its first looter. New columns once per crate, from any location except `other`; a drop counts for its first looter. New columns
`crates_hacked`, `drops_looted` in `rust_player_wipe_stats`; title categories **`crateshacked` (*Safecracker*)** and `crates_hacked`, `drops_looted` in `rust_player_wipe_stats`; title categories **`crateshacked` (*Safecracker*)** and
**`dropslooted` (*Scavenger*)**, `sum` sources, renamable (D175). `heli.downed` and `bradley.destroyed` keep **`dropslooted` (*Scavenger*)**, `sum` sources, renamable (D175).
counting `heliKills` and `apcKills` through the same credit, so the feed line and *Rotorbane* never disagree
(Q28). **Who gets the credit for a helicopter or a Bradley is the operator's choice (D372).** The plugin no longer
decides. For each patrol helicopter and Bradley it keeps a small damage table from `OnEntityTakeDamage`, the
hook that already feeds D174's last-hitter, after the type test it already does, and for those two classes
only. `heli.downed` and `bradley.destroyed` carry every candidate:
```json
{"kind":"world.event","type":"heli.downed","x":812.0,"z":-340.5,
"credit":{"blow":"76561190000000001","last":"76561190000000002","most":"76561190000000002",
"damagers":[{"steamId":"76561190000000002","damage":6120.0},{"steamId":"76561190000000001","damage":3880.0}]},
"names":{"76561190000000001":"Alice","76561190000000002":"Bob"}}
```
- `blow` is the killing blow's player: for the helicopter, the player the first `OnPatrolHelicopterKill` names at
the crippling hit; for a Bradley, its death's `InitiatorPlayer`. `last` is the last player to hurt it, with no
60 s window. `most` is the player who dealt the most damage. Each is absent when nobody qualifies.
- **The site applies the server's rule**, a setting beside the server's other Rust settings:
| Rule | Credits | For example (Bob did 61 %, Alice the crippling hit) |
|---|---|---|
| **Killing blow** (default) | `blow`, else `last` | Alice |
| **Last hit** | `last` | whoever hit it last: Alice or Bob |
| **Most damage** | `most` | Bob |
| **Everyone who hurt it** | every `damagers` entry, one each | Alice and Bob, one each |
- **The same rule writes the feed line and the count**, so the line and *Rotorbane* / *Tankbreaker* never
disagree. Under protocol 14 the site counts `heli_kills` and `apc_kills` from these frames, and the tally's
`heliKills` / `apcKills` are no longer sent. Under *Everyone*, the line reads *"Patrol helicopter downed by
Alice, Bob and 2 others at G7"*.
- Changing the rule applies from then on; past counts are not recounted.
**The step that calls one: `world.call`, site → game (D354, D367).** **The step that calls one: `world.call`, site → game (D354, D367).**
@@ -2423,13 +2463,15 @@ counting `heliKills` and `apcKills` through the same credit, so the feed line an
- **The feed.** `world.event` joins the catalogue, filtered **per `type`**. Each line is written from the type, e.g. - **The feed.** `world.event` joins the catalogue, filtered **per `type`**. Each line is written from the type, e.g.
*"Patrol helicopter downed by Alice at G7"*, *"Bob started hacking the locked crate on Large Oil Rig"*, *"Patrol helicopter downed by Alice at G7"*, *"Bob started hacking the locked crate on Large Oil Rig"*,
*"12 road Bradleys are out"*, *"The Deep Sea is open"*. **Public with grid and name, staff adds the Steam id *"12 road Bradleys are out"*, *"The Deep Sea is open"*. **The event and its grid are public; the player's name
(D353)**; how that meets the presence rule is Q32. shows only to the operator's presence audience (D376)**, so with presence left at staff a signed-out visitor
reads *"Patrol helicopter downed at G8"*. Staff see the name and the Steam id (D353). A line with nobody in it
(the cargo docking, the Deep Sea) is the same for everyone.
- **Triggers (D355).** One engagement trigger per type, `rust.world.<type>` (`rust.world.heli.downed`, - **Triggers (D355).** One engagement trigger per type, `rust.world.<type>` (`rust.world.heli.downed`,
`rust.world.crate.hack.started` …), `kind: 'event'`, ceiling `everyone`, cooldown subject the server and type `rust.world.crate.hack.started` …), `kind: 'event'`, ceiling `everyone`, cooldown subject the server and type
(the crate types add `where`), with typed variables (`server`, `grid`, `player`, `where`, `harbour`, (the crate types add `where`), with typed variables (`server`, `grid`, `player`, `where`, `harbour`,
`monument`, `count`). Because a phase gate's `{ on: … }` reads the same catalogue, **"advance when Bradley is `monument`, `count`). Because a phase gate's `{ on: … }` reads the same catalogue, **"advance when Bradley is
destroyed" works with no new code**. Starting a run from a trigger is not something core does today (Q26). destroyed" works with no new code**. **Starting a run from one is new core work (D370, §14.3.12).**
- **The step `rust.world.call`**, *"Call a world event"*: server, event, count, and the location params of the - **The step `rust.world.call`**, *"Call a world event"*: server, event, count, and the location params of the
place verbs where the event allows a position (above). place verbs where the event allows a position (above).
- **Titles and columns:** *Safecracker* and *Scavenger* (above); the leaderboard gains the two columns. - **Titles and columns:** *Safecracker* and *Scavenger* (above); the leaderboard gains the two columns.
@@ -2438,10 +2480,12 @@ counting `heliKills` and `apcKills` through the same credit, so the feed line an
**Tests.** Bridge: `checkPlugin.js` (hook names, the console probe). Module-Rust: the catalogue holds every type **Tests.** Bridge: `checkPlugin.js` (hook names, the console probe). Module-Rust: the catalogue holds every type
against PROTOCOL.md's table (default deny for an unknown type); each feed line; the trigger declarations and against PROTOCOL.md's table (default deny for an unknown type); each feed line; the trigger declarations and
their examples; the `world.call` verify and bounds; the two title categories and columns; the ingest of the two their examples; the `world.call` verify and bounds; the two title categories and columns; the ingest of the two
tally fields. tally fields; each of D372's four rules against one `credit` block (one with no `blow`, one with nobody at all),
for the line and the count alike; a presence-gated line with and without the name (D376).
**Walk rows (D331).** On both rigs, with timers shortened as §14.2 did: force each type and read its feed line, **Walk rows (D331).** On both rigs, with timers shortened as §14.2 did: force each type and read its feed line,
staff line and trigger; down a helicopter with a crash long after the hit; hack a crate at each location and staff line and trigger, signed out and as staff; down a helicopter with a crash long after the hit, and a
Bradley with two players, under each of the four credit rules; hack a crate at each location and
see *Safecracker* count; loot a drop first and see *Scavenger*; call an airdrop at a position and a patrol see *Safecracker* count; loot a drop first and see *Scavenger*; call an airdrop at a position and a patrol
helicopter from a run, then cancel the run and see both stay; a Guarded Crate crate raising no Rust frame. helicopter from a run, then cancel the run and see both stay; a Guarded Crate crate raising no Rust frame.
@@ -2512,13 +2556,14 @@ both go when the zone lasts out; cargo's ring for the ship's life; one of our zo
- **The step `rust.dt.start`**, *"Start a Dangerous Treasures event"*: server and difficulty; DT picks the spot. - **The step `rust.dt.start`**, *"Start a Dangerous Treasures event"*: server and difficulty; DT picks the spot.
`reversible: 'none'` (DT despawns its own), `risk: 'change'`, a new budget `rust.dt.events`. It refuses at `reversible: 'none'` (DT despawns its own), `risk: 'change'`, a new budget `rust.dt.events`. It refuses at
verify where DT is absent or `console.plugins` is false, naming Rust-Plugins#34 for the second. verify where DT is absent or `console.plugins` is false, naming Rust-Plugins#34 for the second.
- **D365.** `dt-not-ready` is reported as *"Dangerous Treasures is not ready yet"*. **The retry after ten minutes is - **D365.** `dt-not-ready` is reported as *"Dangerous Treasures is not ready yet"*, and the step answers
Q27**: core's step retry today is a flat 60 s, three times, so ten minutes needs either a small core addition or `{ ok: false, retry: true, retryAfterMs: 600000 }`: **core's new per-failure retry delay (D371, §14.3.13)**, so
the bridge holding the request. The manual retry is core's own: the step's `on_failure` pauses the run, and the run tries once more ten minutes later. `dt-no-spot` retries on core's ordinary 60 s. The manual retry is
staff press **Retry** on the paused step ([EVENTS.md](../../website/EVENTS.md) §I), which re-sends the same key. core's own: the step's `on_failure` pauses the run, and staff press **Retry** on the paused step
([EVENTS.md](../../website/EVENTS.md) §I), which re-sends the same key.
- **Feed and triggers.** *"A Dangerous Treasures event started at K12"*; *"Alice won the treasure at K12"*; - **Feed and triggers.** *"A Dangerous Treasures event started at K12"*; *"Alice won the treasure at K12"*;
ended is staff-only. `rust.dt.started` and `rust.dt.won` are triggers (§14.3.9). ended is staff-only. `rust.dt.started` and `rust.dt.won` are triggers (§14.3.9).
- **A column and a title**, `treasureswon`, default *Treasure Hunter* (Q31). - **A column and a title**, `treasureswon`, default *Treasure Hunter* (D375).
- The map draws a treasure marker. - The map draws a treasure marker.
**Tests.** The step's verify (absent, console unavailable), each error's wording, the key join on `dt.started`, **Tests.** The step's verify (absent, console unavailable), each error's wording, the key join on `dt.started`,
@@ -2541,13 +2586,15 @@ the bridge already reads Kits' and Economics' (PROTOCOL.md §16.3), and absent w
`IsAdmin`, which the server console passes). It refuses with `npcspawn-missing`, `bad-event`, `IsAdmin`, which the server console passes). It refuses with `npcspawn-missing`, `bad-event`,
`console-unavailable` or `gc-missing`. `console-unavailable` or `gc-missing`.
- `map.live`'s `world` gains `kind: "guarded"` while one runs. - `map.live`'s `world` gains `kind: "guarded"` while one runs.
- The tally gains **`guardedWon`** for the player `gc.ended` names (Q31). - The tally gains **`guardedWon`** for the player `gc.ended` names: a column and a title, `guardedwon`, default
*Warden* (D375).
**The website.** **The website.**
- **The step `rust.gc.place`**, *"Place a guarded crate"*: server, event (the names from hello), and the place - **The step `rust.gc.place`**, *"Place a guarded crate"*: server, event (the names from hello), and the place
verbs' location params (monument, grid or point). **Kept: nothing is removed at teardown (Q29).** There is no verbs' location params (monument, grid or point). **Kept: nothing is removed at teardown (D373).** There is no
way to end one instance, and `gcrate stop all` would end other people's and raise no hook. A new budget way to end one instance, and `gcrate stop all` would end other people's and raise no hook. The step's
`rust.gc.crates`. description says so, and when a run ends with one still standing, the run log notes *"a guarded crate at M4
was left standing; it ends when cleared or after its own time"*. A new budget `rust.gc.crates`.
- **D364:** it refuses at verify where NpcSpawn is absent: *"Guarded Crate is loaded but NpcSpawn is missing: its - **D364:** it refuses at verify where NpcSpawn is absent: *"Guarded Crate is loaded but NpcSpawn is missing: its
events cannot start."* The servers page shows NpcSpawn beside Guarded Crate. events cannot start."* The servers page shows NpcSpawn beside Guarded Crate.
- **Feed and triggers.** *"A guarded crate landed at M4"*; *"Alice cleared the guarded crate at M4"*; failed is a - **Feed and triggers.** *"A guarded crate landed at M4"*; *"Alice cleared the guarded crate at M4"*; failed is a
@@ -2562,27 +2609,25 @@ 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 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). step refused with the reason. On Carbon: refused (D363).
#### 14.3.6 ZLevels Remastered (D369) #### 14.3.6 ZLevels Remastered (D369, D374)
**Hello.** `integrations.zLevels`: `{ loaded, version }`. **Hello.** `integrations.zLevels`: `{ loaded, version }`.
**The wire.** The tally gains **`levels`**: `{ acquire, crafting, mining, skinning, woodcutting, fishing }`, each a **The leaderboard does not change (D374).** It gains no ZLevels column and no ZLevels title. Most of its six
value read with **`GetLevel` only** (never `api_GetPlayerInfo`, which creates records). It is read when a player skills already line up with counts of ours: woodcutting with *Timberwright*, mining with *Prospector*, crafting
connects and at each flush for the players in it (six calls of about 3 µs each), and sent when one moved. with *Seamster* and *Armorer*, acquire (plants) with *Greenhand*, and skinning sits beside the animal kills
There is no level-up hook, so a level is as fresh as the last flush. (*Beastbane*). Fishing has no count of ours, and stays that way. So **no levels go on the wire
and nothing is stored**: the `levels` tally field and the `rust_player_levels` table this plan first proposed are
dropped. D369 still holds in what remains: nothing is done at a wipe.
**The website.** **What the integration is:**
- **Stored per server, not per wipe (D369):** a new table `rust_player_levels` (`server_id`, `steam_id`, the six - **Hello reports it**, and the servers page shows it beside the other optional plugins.
levels, `updated_at`), a value written, never summed, and nothing done at a wipe.
- **The leaderboard and titles** are Q30. Whatever the answer, the column is "current level", on servers that
report ZLevels, and shown for every wipe view because it does not belong to one.
- **XP boosts need no code** (§14.2.1): `rust.perm.lend` already lends `zlevelsremastered.vip1`–`vip3`, and its - **XP boosts need no code** (§14.2.1): `rust.perm.lend` already lends `zlevelsremastered.vip1`–`vip3`, and its
picker lists them once ZLevels registers them (§12.2). picker lists them once ZLevels registers them (§12.2). A *Double XP weekend* is a lend step.
**Tests.** The tally's `levels` ingest as values; the new table's upsert; the columns and categories Q30 decides. **Tests.** The hello field and the servers page.
**Walk rows.** Gain a level and see it after the next flush; lend `vip2` from a run and see the XP rate change; **Walk rows.** Lend `vip2` from a run and see the XP rate change at once, and back at the run's end.
wipe and see the levels stay.
#### 14.3.7 Server Rewards (D369) #### 14.3.7 Server Rewards (D369)
@@ -2611,7 +2656,9 @@ player is at 0.
per server and not per wipe (D369). The player's Rust page, the user's admin page and the app (R10) show it beside per server and not per wipe (D369). The player's Rust page, the user's admin page and the app (R10) show it beside
the coins. the coins.
- **Staff add and take** from the user's admin page, each an activity row, like Economics. - **Staff add and take** from the user's admin page, each an activity row, like Economics.
- **A column and a title** (Q31). - **A column and a title**, `points`, default *Hoarder*, a value like the richest (D375): ranked only above 0, on
servers that report Server Rewards, and shown for every wipe view because the points do not belong to one
(D369).
**Tests.** The same set as Economics': the amount bounds (0, negative, fractional), a repeat key, `insufficient`, **Tests.** The same set as Economics': the amount bounds (0, negative, fractional), a repeat key, `insufficient`,
the value ingest, the admin route and its activity row, the app's screen. the value ingest, the admin route and its activity row, the app's screen.
@@ -2627,7 +2674,7 @@ in the app; wipe and see the points stay.
| Hello | `console.plugins`; `integrations.truePve`, `dynamicPvp`, `dangerousTreasures`, `guardedCrate` (with `npcSpawn`, `events`), `zLevels`, `serverRewards` | | Hello | `console.plugins`; `integrations.truePve`, `dynamicPvp`, `dangerousTreasures`, `guardedCrate` (with `npcSpawn`, `events`), `zLevels`, `serverRewards` |
| Events | `world.event` (23 types); `pvp.zone.created` / `deleted`; `dt.started` / `won` / `ended`; `gc.started` / `ended` / `failed` | | Events | `world.event` (23 types); `pvp.zone.created` / `deleted`; `dt.started` / `won` / `ended`; `gc.started` / `ended` / `failed` |
| Commands | `world.call`; `dt.start`; `gc.place`; `points.pay` / `balances` / `adjust`; `world.zone`'s `truePve` | | Commands | `world.call`; `dt.start`; `gc.place`; `points.pay` / `balances` / `adjust`; `world.zone`'s `truePve` |
| Tally | `cratesHacked`, `dropsLooted`, `treasuresWon`, `guardedWon`; `levels` and `points` (values) | | Tally | `cratesHacked`, `dropsLooted`, `treasuresWon`, `guardedWon`; `points` (a value). **`heliKills` and `apcKills` leave the tally**: the site counts them from `world.event`'s `credit` (D372) |
| `map.live` | `world` kinds `treasure` and `guarded`; the `pvp` layer | | `map.live` | `world` kinds `treasure` and `guarded`; the `pvp` layer |
| Sidecar | `POST /world/call`, `/dt/start`, `/gc/place`, `/points/pay`, `/points/balances`, `/points/adjust`, thin forwards that keep nothing | | Sidecar | `POST /world/call`, `/dt/start`, `/gc/place`, `/points/pay`, `/points/balances`, `/points/adjust`, thin forwards that keep nothing |
@@ -2651,6 +2698,10 @@ in the app; wipe and see the points stay.
#### 14.3.10 Questions for the org lead #### 14.3.10 Questions for the org lead
**All seven were answered on 2026-10-14 (D370–D376, §10).** Q26, Q27, Q29, Q31 and Q32 as proposed (Q26 is
written as §14.3.12, Q27 as §14.3.13). **Q28 differs:** the operator chooses the credit rule, from four options
(D372). **Q30 differs:** the leaderboard is not changed for ZLevels at all (D374).
- **Q26 — Can a world event start a run?** D355 makes every world event a trigger, and §13.4's example was - **Q26 — Can a world event start a run?** D355 makes every world event a trigger, and §13.4's example was
*"when anyone destroys Bradley, start the Launch Site Lockdown event"*. Today a trigger can **advance a phase** *"when anyone destroys Bradley, start the Launch Site Lockdown event"*. Today a trigger can **advance a phase**
of a run already going (`{ on: 'rust.world.bradley.destroyed' }`) and can send a notice, but **core cannot start a of a run already going (`{ on: 'rust.world.bradley.destroyed' }`) and can send a notice, but **core cannot start a
@@ -2693,15 +2744,74 @@ Each stage is built on `edge`, compiled on both rigs, and PR'd with its PROTOCOL
until the end (D331, D359). The org lead is asked before each stage starts. until the end (D331, D359). The org lead is asked before each stage starts.
1. **§13, the world events**, starting with the placement spike for `world.call`. It has no plugin dependency 1. **§13, the world events**, starting with the placement spike for `world.call`. It has no plugin dependency
and carries the console probe the later stages use. and carries the console probe the later stages use, and D372's credit rule.
2. **TruePVE** in the zone step. 2. **Core: a step's retry delay** (§14.3.13, D371), a website PR with the Module-uo proof. It is small and comes
3. **Dynamic PVP** (it needs TruePVE). before stage 5, which needs it.
4. **Dangerous Treasures**, after Q27's core change if that is the answer. 3. **TruePVE** in the zone step.
5. **Guarded Crate**. 4. **Dynamic PVP** (it needs TruePVE).
6. **ZLevels**. 5. **Dangerous Treasures**, on stage 2's `retryAfterMs`.
7. **Server Rewards**. 6. **Guarded Crate**.
7. **ZLevels** (hello only, D374).
8. **Server Rewards**.
9. **Core: starting a run from a trigger** (§14.3.12, D370), a website PR of its own with the Module-uo proof,
as §2 was. It can be built alongside any stage after 1; it is last here only because nothing else waits on it.
Module-Rust needs no code for it, because the world events are already triggers.
Q26's core work, if wanted, is a website PR of its own and can be built alongside any stage. #### 14.3.12 Core: starting a run from a trigger (D370)
**What it gives.** *"When anyone destroys Bradley on Main, start Launch Site Lockdown"*; *"when the Deep Sea opens,
start Sea Raiders"*; on UO, *"when a champion's boss appears, start Champion's Call"*. Today a trigger can only
advance a phase of a run already going, or send a notice (§14.3.1). This is core work on the website's own plan
([EVENTS.md](../../website/EVENTS.md) gains it when it is built). It is written here because this plan asked for it,
and no module contract changes: the triggers it starts from are the ones modules already register.
**The design, to be settled again in EVENTS.md at build time:**
- **A definition's version gains `starts`**: a list of `{ on: <triggerId>, where: <conditions>, cooldown }`,
beside its schedule (a definition may have either, or both). `on` and `where` are a phase gate's own
(EVENTS.md §E), the same catalogue, operators and form, so *"`where: { server: 'main', where: 'launch_site' }`"* is
already a thing the editor can draw.
- **The emit path starts it.** When a trigger fires, core finds the `ready` definitions whose published
version starts on it and whose conditions match, and creates a run of that version **now**, with
`scheduled_for` = the firing's time, so the existing `UNIQUE (definition_id, scope, scheduled_for)` keeps one
firing from starting two runs. `started_by` is `trigger:<triggerId>`, and the firing's variables are stored on
the run, so its page says *"Started because Bradley was destroyed at G8"*, and the audit says who and what.
- **One run at a time, and a cooldown.** A firing while a run of that definition is going, or within its
`cooldown` (default 30 minutes, 0 to 7 days) of the last trigger-started run, starts nothing. It is counted,
and the definition's page shows *"2 firings ignored while running"*. This is what stops twelve road Bradleys dying
in a minute from starting twelve runs.
- **No loops.** Core's own `event.*` triggers cannot start a run, so a run starting can never start another.
A run whose own steps cause its start trigger (an event that calls a patrol helicopter and starts on one
being downed) is held by one run at a time and the cooldown.
- **Everything else is a run like any other:** caps and budgets, verify before publish, the same roles that may
schedule one (admin and editor), the same announcements and cleanup. A definition that starts from a trigger
whose module is not loaded is dormant, as a phase gate on one is.
**Tests (website).** A matching firing starts one run; a second firing during it, and one in the cooldown, start
nothing and are counted; two instances emitting the same firing make one run; an `event.*` trigger is refused in
`starts`; a dormant trigger; the run's origin on its page and in the audit. **Module-uo proof:** one definition
starting on `uo.champ.boss_up`.
**Walk rows.** On a Rust rig: *"start when Bradley is destroyed"* with a short cooldown. Destroy two in a minute and
see one run and one ignored firing; destroy one after the cooldown and see a second run.
#### 14.3.13 Core: a step's retry delay (D371)
**What it gives.** A failed step may say when to try again: `{ ok: false, retry: true, retryAfterMs }`. Today every
retry waits the flat `EVENT_STEP_RETRY_MS` (60 s). DT's warm-up (D365) is the first user: *"not ready yet; try in
ten minutes"*.
- **Bounded** 60 000 to 1 800 000 ms (one to thirty minutes). Outside that it is clamped, because this is the
module's hint, not an author's value (D95 is about the second). Absent, the flat delay as today.
- **It counts as one attempt** of `EVENT_STEP_MAX_ATTEMPTS`, and the run goes `degraded` on it as on any retry
(EVENTS.md §L). The run console shows *"retrying at 20:10"*.
- **A run that is paused or cancelled while it waits** does not retry; the existing staff Retry on a paused step
still works at any time.
- **MODULE_API minor bump** (additions only). [`STEAM_SIGNIN.md`](../../website/STEAM_SIGNIN.md) has claimed
1.15.0; whichever lands first takes it, the other the next minor. Module-uo needs no change. The Integration Kit's
`coreApi` equality check turns red on the bump, as it is meant to, and its pin moves when the chapters are re-read.
**Tests (website).** The delay honoured; clamped at both ends; counted as an attempt; no retry for a run paused
or cancelled in the wait; `MODULE_API.md` and the classify rules updated.
### 14.4 Questions for the org lead ### 14.4 Questions for the org lead