diff --git a/modules/rust/PLAN_REDESIGNS.md b/modules/rust/PLAN_REDESIGNS.md index 4cf223c..b3bff4f 100644 --- a/modules/rust/PLAN_REDESIGNS.md +++ b/modules/rust/PLAN_REDESIGNS.md @@ -927,7 +927,8 @@ pulls) is research, not a walk, so it still comes first: 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 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 (§14.3), which starts from D361–D369. Then the build, §13 first. + answered before code: **written on 2026-10-14 as §14.3**, from D361–D369, with Q26–Q32 open. Then the + build, in §14.3.11's seven stages, §13 first. **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 @@ -2334,17 +2335,373 @@ not "this wipe". ### 14.3 The integration plan -Written after §14.2, in one docs PR, and answered by the org lead before any code. For §13 and for each of the -six it says: -- what hello reports (`integrations` gains each plugin's `{ loaded, version }`); -- the wire additions, in protocol 14 unless Q16 says otherwise; -- what the site does: feed, map, steps, triggers, titles, columns, configuration; -- any helper, and why, which D351 makes the exception; -- the tests; -- the walk rows, which join the end-of-plan walk (D331). +Written on 2026-10-14 from §14.2.1 and D353–D369, against the code on `edge` (bridge, rust-link and Module-Rust +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 +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. -It also proposes a build order. The working assumption is §13 first (no dependencies), then TruePVE, then Dynamic -PVP, then the other four. +**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 +below is a public hook, a callable method or a console command that §14.2 ran. **The installer and the egg are +not touched:** `requires_plugins` stays Kits, ZoneManager and RunicNPC, and none of the six is shipped (D351). + +#### 14.3.1 Rust's own world events (§13; D349, D353–D356, D366, D367) + +**Hello.** Nothing new: the events need no plugin. Hello gains one fact the steps below need, **`console`**: +`{ plugins: true | false }`, found at load by running one of the bridge's own read-only commands through +`ConsoleSystem.Run`, the route a plugin command takes. On Carbon 2.0.262 it is `false` (D363), and the site +refuses a step that needs another plugin's console command, with the reason and Rust-Plugins#34. When Carbon +fixes it, the next load says `true` and the steps work, with no new code. + +**One new event kind, `world.event`, with a `type`.** It is rare (a few an hour), so it is one frame per event. +Positions are `x`, `z`; **the site computes the grid**, as the map already does, so the grid has one source. A +player is `steamId` and `name`. + +| `type` | Raised from (§14.2.1) | Carries | +|---|---|---| +| `heli.arrived` | `OnEntitySpawned(PatrolHelicopter)` | `x`, `z` | +| `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` | +| `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` | +| `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` | +| `chinook.arrived` | `OnEntitySpawned(CH47HelicopterAIController)`, **except the oil-rig reinforcement**, which is told by the crate hack it answers | `x`, `z` | +| `chinook.dropped` | `OnHelicopterDropCrate`, then the crate's `OnCrateLanded` | `monument` (nearest), `x`, `z` | +| `airdrop.called` | `OnCargoPlaneSignaled` (who = the signal's thrower) or `OnAirdrop` (the schedule: nobody) | who if any, `x`, `z` (the drop point) | +| `airdrop.landed` | `OnSupplyDropLanded`, **once per drop** (it fired twice) | `x`, `z`, the caller remembered from plane to drop | +| `airdrop.looted` | the **first** `OnLootEntity` on a drop | who, `x`, `z` | +| `crate.hack.started` | `CanHackCrate` (who) joined to the `OnCrateHack` that follows for the same crate | who, `where`, `x`, `z` | +| `crate.unlocked` | `OnCrateHackEnd` | `where`, `x`, `z`, the hacker | +| `crate.looted` | the first `OnLootEntity` on an unlocked crate | who, `where` | +| `vendor.arrived` / `vendor.left` | `OnEntitySpawned` / `OnEntityKill` (`TravellingVendor`) | `x`, `z` | +| `satellite.crashed` | the `SatelliteCrashRemains` spawn at impact (no hook of its own) | `x`, `z` | +| `deepsea.opened` / `deepsea.closed` | `OnDeepSeaOpened` / `OnDeepSeaClosed` | **No position:** the Deep Sea is off the map | + +- **`where` for a locked crate** is `oilrig_large`, `oilrig_small`, `cargo`, `ghostship` or `chinook`, each told + positively as §14.2.1 found (the `_oilrig` prefab and nearest rig, parent `CargoShip`, the `_ghostship` prefab, + a crate landed after `OnHelicopterDropCrate`). **Anything else is `other` and raises no frame**, so a + plugin's crate (§14.2.1: Guarded Crate's is an ordinary `HackableLockedCrate`) is never reported as Rust's own. + A crate one of our steps placed is in the world ledger and is skipped as well. +- **"Oil rig scientists called" is not a separate type.** Starting a hack at a rig is what calls them, at once, so + `crate.hack.started` with `where: oilrig_*` says it. +- **Hooks not subscribed:** `OnHelicopterOutOfCrates` (every tick), `OnPatrolHelicopterTakeDamage` (every hit; + D174's hit list already comes from `OnEntityTakeDamage`), `OnDeepSeaTeleport`, the F-15 (D367). +- **Bounded state.** The joins above (plane → thrower, crate → hacker, looted-once sets) are keyed by net id, + dropped at the entity's kill, and swept every ten minutes; nothing is written to disk. + +**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 +`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 +counting `heliKills` and `apcKills` through the same credit, so the feed line and *Rotorbane* never disagree +(Q28). + +**The step that calls one: `world.call`, site → game (D354, D367).** + +```json +{"cmd":"world.call","reqId":"r-40","key":"9:2","event":"airdrop","count":3,"x":-412.0,"z":1180.5} +{"kind":"world.called","type":"reply","reqId":"r-40","key":"9:2","repeat":false,"called":3} +``` + +- `event` is one of the schedule keys §14.2.1 found (`patrol_heli`, `cargo`, `chinook`, `airdrop`, + `travelling_vendor`, `bradley_road`, `f15`) or `satellite`. It runs the game's own `TriggeredEvent.RunEvent` + (`satellite.random` for the satellite), so it works the same on Carbon: **no plugin console command is used.** +- **A position** is offered only where the game has a route to one. §14.2 ran the events where the game puts + them; placing them was not tested. The first build stage starts with a short spike (research, D331) on the + airdrop's drop point, the patrol helicopter's first destination and the Chinook's drop target. An event whose + route does not hold is offered without a position, and the step's form says the game decides where. +- **Kept (`reversible: 'none'`)**: nothing is removed at teardown, because a cargo ship or a drop may already hold + players or their loot (D354). `risk: 'change'`. +- **Bounds:** `count` 1–5 (1 for cargo, the vendor and road Bradleys, which the game runs one at a time), at + most `EventsMaxWorldCalls` per run on the plugin. A new budget, **`rust.world.calls`**, counts each call. + Refusals are `world.error`: `events-disabled`, `bad-event`, `bad-count`, `not-placeable`, `too-many`. +- The sidecar adds `POST /world/call`, a thin forward. + +**The website (Module-Rust).** + +- **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"*, + *"12 road Bradleys are out"*, *"The Deep Sea is open"*. **Public with grid and name, staff adds the Steam id + (D353)**; how that meets the presence rule is Q32. +- **Triggers (D355).** One engagement trigger per type, `rust.world.` (`rust.world.heli.downed`, + `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`, + `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). +- **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). +- **Titles and columns:** *Safecracker* and *Scavenger* (above); the leaderboard gains the two columns. +- **The map** already draws these entities; nothing changes there. + +**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 +their examples; the `world.call` verify and bounds; the two title categories and columns; the ingest of the two +tally fields. + +**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 +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. + +#### 14.3.2 TruePVE in the zone step (D362, D368) + +**Hello.** `integrations.truePve`: `{ loaded, version }`. + +**The wire.** `world.zone` gains an optional **`truePve`**: `{ "exclude": true }` or `{ "ruleset": "pvp-arena" }` +(exactly one). The plugin makes the zone, then calls `AddOrUpdateMapping(zoneId, "exclude" | ruleset)`. + +- **All or nothing.** A `false` from TruePVE (no such ruleset) erases the zone just made and answers + `world.error` `truepve-ruleset` with the name. TruePVE absent answers `truepve-missing`. +- **The unmapping lives with the zone, on the plugin (D362).** Every path that erases one of our zones (the + zone's own deadline, which runs with the site down; teardown; the boot sweep of PROTOCOL.md §15.3) calls + `RemoveMapping` first. When TruePVE is not loaded at that moment, the id is kept in + `data/RunicGateway/truepve.json` and removed at the next `OnPluginLoaded(TruePVE)`. The registry entry + remembers that the zone was mapped, so nothing else of TruePVE's table is ever touched. +- `OnUpdatedMappings` / `OnRemovedMappings` are not subscribed: the call's own return says what happened. + +**The website.** `rust.zone.open` gains **`noPvpProtection`** (D334's three-button boolean, *"No PvP protection in +this zone"*) and **`pvpRuleset`** (a string, *"or this TruePVE ruleset"*). Setting both is refused at verify. Either +one refuses at verify where TruePVE is absent ("TruePVE is not installed on this server"). A name TruePVE does not +know fails the step at perform, with the name, because only the call can tell (D368). + +**Tests.** The two params' verify (both set, absent TruePVE, blank); the wire shape; a `truepve-ruleset` reply +classified as a non-retryable failure. + +**Walk rows.** Map `exclude` and a typed ruleset on both rigs and hurt a stand-in inside; type a wrong name; let +a zone expire on its deadline with the site stopped and see the mapping go; unload TruePVE, end a run, load it +and see the leftover removed. + +#### 14.3.3 Dynamic PVP + +**Hello.** `integrations.dynamicPvp`: `{ loaded, version }`. + +**The wire.** Two events, `pvp.zone.created` `{ zoneId, event, x, z, radius, durationSec }` (−1 for "as long as +the ship") and `pvp.zone.deleted` `{ zoneId }`, from `OnCreatedDynamicPVP` and `OnDeletedDynamicPVP`. The radius +is read from ZoneManager, since the hook does not carry it. `map.live` gains a **`pvp`** layer of the live ones, +kept by the same two hooks and seeded at load from ZoneManager's zones whose ids TruePVE maps for Dynamic PVP. +`OnPlayerEnterPVP` / `ExitPVP` are not subscribed: they would say who stands where, and exit fired twice. + +**The website.** The map draws the `pvp` layer as a red ring with its event's name. The feed's public line is +*"A PvP zone opened around the downed patrol helicopter at G7"*, which names nobody. Its deletion is a staff +line only. No trigger and no step: Dynamic PVP's zones are its own to make. + +**Tests.** The layer's ingest and the viewer filter; the feed lines; the default deny until the kinds are +classified. + +**Walk rows.** With a radius set for the helicopter and Bradley: down each, see the ring and the line, and see +both go when the zone lasts out; cargo's ring for the ship's life; one of our zones beside theirs, neither touched. + +#### 14.3.4 Dangerous Treasures (D365) + +**Hello.** `integrations.dangerousTreasures`: `{ loaded, version }`. + +**The wire.** +- Events **`dt.started`** `{ x, z, key? }`, **`dt.won`** `{ steamId, name, invaders: [steamId…], x, z }`, + **`dt.ended`** `{ x, z }`, from `OnDangerousEventStarted`, `…Won` and `…Ended`. `key` is set when the start + answers one of our steps: the first `OnDangerousEventStarted` within ten seconds of our `dtevent`. +- Command **`dt.start`** `{ key, level }` (0 Easy, 1 Medium, 2 Hard) runs `dtevent ` through + `ConsoleSystem.Run`. An empty reply is success. *"Unable to obtain a valid position"* within the first + fifteen minutes after DT loaded is **`dt-not-ready`**; after that, `dt-no-spot`. `console-unavailable` when hello's + `console.plugins` is false (D363). The key is remembered like a payment's, so a retry never starts a second. +- `map.live`'s `world` gains `kind: "treasure"` for each running event, from the same hooks. +- The tally gains **`treasuresWon`** for the looter of `dt.won`. + +**The website.** +- **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 + 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 + Q27**: core's step retry today is a flat 60 s, three times, so ten minutes needs either a small core addition or + the bridge holding the request. The manual retry is 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"*; + 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). +- The map draws a treasure marker. + +**Tests.** The step's verify (absent, console unavailable), each error's wording, the key join on `dt.started`, +the column and category. + +**Walk rows.** On Oxide: start each difficulty from a run; start one in the first minutes after a restart and +see *"not ready yet"*, the automatic retry and the manual retry; win one and see the line, the column and the title. +On Carbon: the step refused with Rust-Plugins#34 (until D363 clears). + +#### 14.3.5 Guarded Crate, with NpcSpawn (D361, D364) + +**Hello.** `integrations.guardedCrate`: `{ loaded, version, npcSpawn: { loaded, version }, events: […] }`. +`events` are the event names in Guarded Crate's config file (Easy, Medium, Hard and Elite by default), read as +the bridge already reads Kits' and Economics' (PROTOCOL.md §16.3), and absent when the file cannot be read. + +**The wire.** +- Events **`gc.started`** `{ x, z, key? }`, **`gc.ended`** `{ steamId?, name?, x, z }` (no player = no winner), + **`gc.failed`** `{ x, z }`, from the three hooks. `key` as for DT. +- Command **`gc.place`** `{ key, event, x, y, z }` runs `gcrate position x,y,z` (the handler checks + `IsAdmin`, which the server console passes). It refuses with `npcspawn-missing`, `bad-event`, + `console-unavailable` or `gc-missing`. +- `map.live`'s `world` gains `kind: "guarded"` while one runs. +- The tally gains **`guardedWon`** for the player `gc.ended` names (Q31). + +**The website.** +- **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 + way to end one instance, and `gcrate stop all` would end other people's and raise no hook. A new budget + `rust.gc.crates`. +- **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. +- **Feed and triggers.** *"A guarded crate landed at M4"*; *"Alice cleared the guarded crate at M4"*; failed is a + staff line. `rust.gc.started` and `rust.gc.ended` are triggers (§14.3.9). +- **RunicNPC needs nothing:** its guards are `scientists` to factions (§14.2.1). +- **The operator docs** ([OPERATING.md](OPERATING.md)) gain a section on Guarded Crate: install NpcSpawn from + Codefling, its `data/Images/` files, and that **it contacts its author's server at load** (D364). + +**Tests.** Verify (NpcSpawn, a name not in `events`, console unavailable), the null-winner frame, the column. + +**Walk rows.** On Oxide: place one at a monument and at a point from a run, clear it and see the line, 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). + +#### 14.3.6 ZLevels Remastered (D369) + +**Hello.** `integrations.zLevels`: `{ loaded, version }`. + +**The wire.** The tally gains **`levels`**: `{ acquire, crafting, mining, skinning, woodcutting, fishing }`, each a +value read with **`GetLevel` only** (never `api_GetPlayerInfo`, which creates records). It is read when a player +connects and at each flush for the players in it (six calls of about 3 µs each), and sent when one moved. +There is no level-up hook, so a level is as fresh as the last flush. + +**The website.** +- **Stored per server, not per wipe (D369):** a new table `rust_player_levels` (`server_id`, `steam_id`, the six + 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 + picker lists them once ZLevels registers them (§12.2). + +**Tests.** The tally's `levels` ingest as values; the new table's upsert; the columns and categories Q30 decides. + +**Walk rows.** Gain a level and see it after the next flush; lend `vip2` from a run and see the XP rate change; +wipe and see the levels stay. + +#### 14.3.7 Server Rewards (D369) + +It mirrors §11.2.2 (Economics) wherever Server Rewards allows, and the code shares Economics' path where the two +are the same. + +**Hello.** `integrations.serverRewards`: `{ loaded, version }`. Server Rewards has no starting balance, so a new +player is at 0. + +**The wire.** +- **`points.pay`** `{ key, amount, steamIds[], message, delivery }`, answered by `points.paid` with `paid` (before + and after) and `failed`, the key remembered seven days, the message told once, exactly as `economics.pay`. + **Whole numbers only, 1 to 1,000,000.** The bridge refuses anything else itself, because the id overloads + accept a negative amount and invert it (§14.2.1). +- **`points.balances`** `{ steamIds[] }` → `{ balances: [{ steamId, points }] }`, bounded like `economics.balances`. +- **`points.adjust`** `{ key, steamId, op: add | take, amount }` → `{ before, after }`. There is no `set`, because + Server Rewards has no call for it. `take` above the balance is refused with `insufficient`, as Server Rewards + refuses it. +- The tally gains **`points`**, a value, read at connect and from `OnPointsUpdated(ulong, int)`. +- The sidecar adds `POST /points/pay`, `/points/balances`, `/points/adjust`. + +**The website.** +- **The step `rust.points.pay`**, *"Pay RP"*: kept like coins (D335's reasoning), the same recipient modes, a new + budget `rust.points`. +- **Balances** in a new table, **`rust_player_points`** (`server_id`, `steam_id`, `points`), + 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. +- **Staff add and take** from the user's admin page, each an activity row, like Economics. +- **A column and a title** (Q31). + +**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. + +**Walk rows.** Pay three players, one offline, and see the popup on wake; retry the step and see no second +payment; add and take from the admin page, including more than the balance; read the balance on the site and +in the app; wipe and see the points stay. + +#### 14.3.8 The wire, all together + +| Kind | New in protocol 14 | +|---|---| +| 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` | +| Commands | `world.call`; `dt.start`; `gc.place`; `points.pay` / `balances` / `adjust`; `world.zone`'s `truePve` | +| Tally | `cratesHacked`, `dropsLooted`, `treasuresWon`, `guardedWon`; `levels` and `points` (values) | +| `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 | + +`PROTOCOL_VERSION` stays 14 (D332); `overlay.toml` already says 14. + +#### 14.3.9 Settled in this plan, and overrulable + +- **One `world.event` kind with a `type`**, filtered per type (§13.2's shape), rather than twenty-three kinds. +- **The site computes the grid** from `x`, `z`; the Deep Sea's events carry no position. +- **Road Bradleys' spawn is one line with a count**; Launch Site's is its own line. +- **Oil-rig reinforcements are `crate.hack.started` at a rig**, not a type of their own. +- **A crate is Rust's own only when its location is told positively**; anything else raises no frame. +- **A hack counts for *Safecracker* when it starts**, for the player `CanHackCrate` named, once per crate. +- **Dangerous Treasures' and Guarded Crate's starts and wins are triggers too**, as world events are (D355). + Dynamic PVP's zones are feed and map only. +- **A run's DT or guarded crate is told from the plugin's own** by the first start within ten seconds of our + command, which is the only join the hooks allow. +- **The DT and GC steps refuse on a server whose `console.plugins` is false**, rather than being hidden. +- **Server Rewards' wire is `points.*`, beside `economics.*`**, not one verb with a currency field: protocol 14 + is unreleased, but Economics' verbs are built and specified, and two names keep each plugin's refusals its own. + +#### 14.3.10 Questions for the org lead + +- **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** + of a run already going (`{ on: 'rust.world.bradley.destroyed' }`) and can send a notice, but **core cannot start a + run from a trigger**: a run starts by its schedule or by hand. **Proposed:** add it in core as a definition's + *"start when"* (a trigger, conditions and a cooldown, so Bradley dying twice in a minute starts one run), on + the website's own plan, in its own PR with the Module-uo proof, as §2 was. Or leave it out for now, and an + event that needs it opens with a phase that waits for the trigger. +- **Q27 — DT's ten-minute retry (D365).** Core retries a failed step every 60 s, three times, so a step cannot + say "try me again in ten minutes". For example, a *Treasure Hunt* at 20:00 after a 19:55 restart fails at + 20:00, 20:01 and 20:02, then pauses the run for staff. **Proposed:** a small core addition, `retryAfterMs` on a + step's failure (bounded to 30 min, counted as one attempt), a MODULE_API minor bump. The other way is the bridge + holding the request and starting DT itself ten minutes later, which means a step that "succeeded" before + anything started. +- **Q28 — Who downed the helicopter.** D174 credits the last player to hurt it within 60 s of the crash. The test + found the game names the player at the **crippling hit**, and a crippled helicopter flew 34.7 s on Carbon before + crashing. One that glides 70 s would credit nobody today. **Proposed:** credit the player named at the crippling + hit, falling back to D174's rule, for the feed line and *Rotorbane* alike (amending D174). +- **Q29 — A guarded crate outlives its run.** There is no way to end one guarded crate: `gcrate stop all` ends + every one on the server, and raises no hook. For example, a run cancelled five minutes after placing one + leaves it, with its eight guards, for up to 30 minutes, until it is cleared or times out. **Proposed:** accept + that. The step says so, and the run log notes the crate left standing. +- **Q30 — ZLevels on the leaderboard.** Six skills. For example: **(a)** one *Level* column, the total of the six, + and one title *Grandmaster* for the highest total; **(b)** six columns (*Woodcutting*, *Mining* …) and six + titles; **(c)** one *Level* column whose skill the viewer picks, with six title categories an admin may use or + not. **Proposed:** (c). +- **Q31 — Titles for the plugins' counts.** For example: *Treasure Hunter* for most DT treasures won, *Warden* (?) + for most guarded crates cleared, *Hoarder* (?) for the most RP. **Proposed:** all three as title categories + with those default names, renamable (D175), and a leaderboard column for each on servers that report the + plugin. RP ranks only above 0. +- **Q32 — A public line that names a player, and the presence rule.** The rule of 2026-09-22 says nothing tells who + is online by default, and the public feed already hides `npc.boss.died` (it names the killer) below the + operator's presence audience. D353 says the public line names the player. For example, with presence left at + staff, does a signed-out visitor see *"Patrol helicopter downed by Alice at G7"*, *"Patrol helicopter downed at + G7"*, or nothing? **Proposed:** the middle one. The event is public, and the name shows only to the presence + audience, so a line with nobody in it (the cargo docking, the Deep Sea) is always public. + +#### 14.3.11 Order of building + +Each stage is built on `edge`, compiled on both rigs, and PR'd with its PROTOCOL.md section; nothing is walked +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 + and carries the console probe the later stages use. +2. **TruePVE** in the zone step. +3. **Dynamic PVP** (it needs TruePVE). +4. **Dangerous Treasures**, after Q27's core change if that is the answer. +5. **Guarded Crate**. +6. **ZLevels**. +7. **Server Rewards**. + +Q26's core work, if wanted, is a website PR of its own and can be built alongside any stage. ### 14.4 Questions for the org lead