docs(rust): stage 1 built — world events on protocol 14 (PROTOCOL §20.5, D378, D379)

- rust-link/PROTOCOL.md §20.5: `world.event` and its 24 types (the type in
  `event`, not the envelope's `type`), the credit block, the tally's two
  counts, `world.call`, hello's `console`, the sidecar forward and the site.
- modules/rust/PLAN_REDESIGNS.md: D378 (the Chinook is called with no
  position) and D379 (`chinook.downed` counts toward Rotorbane), both the org
  lead's on 2026-10-15; §14.3.14, the stage-1 record: the placement spike, the
  seven changes the build found (among them a correction to §14.2.1: Carbon
  refuses plugin console commands only inside a console command, so the
  bridge's own path works), what was settled in the code, the tests and the
  walk rows it adds; pointers from §14.3.1 and §14.3.8.

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 00:39:01 -05:00
parent 0ada1e4f26
commit adf197e5f8
2 changed files with 229 additions and 2 deletions

View File

@@ -928,7 +928,8 @@ pulls) is research, not a walk, so it still comes first:
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, and answered as D370–D376. 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 nine stages (two of them core), §13 first. 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.
**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
@@ -1090,6 +1091,8 @@ The org lead answered §14.3's Q26–Q32 on 2026-10-14:
| **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 | | **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 | | **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 |
| **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 | | **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 |
## 11. First-class optional plugins (D199–D202) ## 11. First-class optional plugins (D199–D202)
@@ -2361,6 +2364,11 @@ not touched:** `requires_plugins` stays Kits, ZoneManager and RunicNPC, and none
#### 14.3.1 Rust's own world events (§13; D349, D353–D356, D366, D367) #### 14.3.1 Rust's own world events (§13; D349, D353–D356, D366, D367)
> **Built as stage 1 (§14.3.14), with seven changes the build found.** The one that matters to a reader of the
> examples below: **a world event's type is in `event`, not `type`**, which is the envelope's. There are
> **24 types** with `chinook.downed` (D379); the Chinook takes no position (D378); `airdrop.called` comes
> from the plane's spawn. [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20.5 is the wire as built.
**Hello.** Nothing new: the events need no plugin. Hello gains one fact the steps below need, **`console`**: **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 `{ 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 `ConsoleSystem.Run`, the route a plugin command takes. On Carbon 2.0.262 it is `false` (D363), and the site
@@ -2673,7 +2681,7 @@ in the app; wipe and see the points stay.
| Kind | New in protocol 14 | | Kind | New in protocol 14 |
|---|---| |---|---|
| 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` (24 types with `chinook.downed`, D379; the type in `event`, §14.3.14); `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`; `points` (a value). **`heliKills` and `apcKills` leave the tally**: the site counts them from `world.event`'s `credit` (D372) | | 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 |
@@ -2814,6 +2822,79 @@ ten minutes"*.
**Tests (website).** The delay honoured; clamped at both ends; counted as an attempt; no retry for a run paused **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. or cancelled in the wait; `MODULE_API.md` and the classify rules updated.
#### 14.3.14 Stage 1, built: Rust's own world events (2026-10-15)
Built on `edge` in Rust-Plugins, Rust-Link and Module-Rust, specified in
[PROTOCOL.md](../../rust-link/PROTOCOL.md) §20.5, on Rust 25902425, Oxide 2.0.7830 and Carbon 2.0.262 (the
latest of each that day). Walked at the end with everything else (D331).
**The placement spike (research, D331).** A throwaway plugin on both rigs called each event at a chosen point by
the routes the game itself has, and measured where it went:
| Event | Route | Result |
|---|---|---|
| Airdrop | `CreateEntity(cargo_plane)`, `InitDropPosition(point)`, then `Spawn()` | Released **0–1 m** above the point on both rigs; lands there. The plane crosses a 6000 map in about 480 s and drops at half way |
| Patrol helicopter | `PatrolHelicopterAI.SetInitialDestination(point)` **before** `Spawn()`, so `ServerInit` skips its random start | Came within **10–54 m** of the point after 65–165 s, then patrolled as usual |
| Chinook | It drops only at the map's `CH47DropZone`s (seven, all at monuments). Its brain's interest point: it orbited the zone 110 s and left without dropping (a drop needs it five minutes old). The Airfield terminal's statics, set 5 s after spawn: dropped 5 m from the zone. Set before spawn: it dropped at its offshore spawn and vanished | **No position (D378)**: the working route is the real terminal's shared state |
| Cargo leaving | `cargoship.egress` | **`OnCargoShipEgress` fires** on both frameworks (a natural one was seen on Oxide); the kill follows after `egress_duration_minutes`. So `cargo.left` is the egress |
**What changed from §14.3.1 as planned, each found by the build:**
1. **The type is `event`, not `type`.** §14.3.1's example wrote `"type":"heli.downed"`, but `type` is the
envelope's and the sidecar files on it: the rig's sidecar dropped every world event as untyped, the same
collision protocol 13's `world.expired` had (§19). Every frame says `"type":"event","event":"heli.downed"`.
2. **`airdrop.called` comes from the cargo plane's spawn.** The console's `supply.call` raised neither
`OnAirdrop` nor `OnCargoPlaneSignaled`, so a line from those hooks would miss it. They now only say who and
where; the drop point is otherwise read from the plane.
3. **A Bradley's `where` is told by the game's spawners**, not by `RoadSpawned`, which defaults to true (a
plugin's Bradley would read as a road one): Launch Site's when `BradleySpawner.singleton` holds it, road when
the road event's list does. **A Bradley neither holds is `other`**; its spawn says nothing, but its death is
sent and counts toward Tankbreaker, as it did under D174.
4. **`chinook.downed` (D379).** D174 counted Chinook kills toward Rotorbane; they keep counting, by the rule.
5. **The Chinook's drop is joined at the crate's landing.** The crate a Chinook drops has no net id when
`OnCrateDropped` fires (it is made just before its spawn), so the drop is joined to the crate that lands
under it within 40 m and 120 s.
6. **`world.call` runs one of the schedule's events at random, as `EventSchedule.Trigger` does, but not
`Trigger`**, which would also reset the schedule's clock: calling an airdrop must not delay the next natural
one. The schedule is found whether or not it is enabled.
7. **Carbon's console, corrected.** §14.2.1 found Carbon 2.0.262's `ConsoleSystem.Run` answers "not found" to
every plugin command. Stage 1 found that holds only for a call **inside a console command**, or in a
`NextTick` a console command queued, which is how §14.2's spike ran every test (`rgs.cmd …`). From a timer,
and from a `NextTick` queued by a background thread — **the bridge's own path for every website command** —
Carbon finds plugin commands, `c.version` included. So the console probe (D363) runs from a timer, and reads
**true on both rigs**. It also suggests D180's Carbon reload works from the website after all, and that
Rust-Plugins#34 describes the spike's context rather than the bridge's; the end walk confirms it.
**Settled in the code, and overrulable:**
- **A position never leaves the module.** The feed sends a grid; `x` and `z` reach staff only, because the map
has its own audience for where things are (D111).
- **`EventsMaxWorldCalls`** defaults to its ceiling, 10 per run per server.
- **The credit rule is on the server's settings** (Admin → Rust → the server, *World events*), and an unknown
stored word reads as the default, the killing blow.
- **`damage` is the damage each hit reported**, summed, and a credit block names at most 32 damagers.
- **The feed gains a *World events* filter**, and the two leaderboard columns sort.
- **A trigger's `player`, for a downed one, is the credited names as a phrase** (*"Alice, Bob and 2 others"*),
by the server's rule; `credited` is how many.
- **A step's answer** says how many the game started (`called`), and a note when it started fewer than asked.
**Tests.** Bridge: `checkPlugin.js` (76 hooks, every one void and listed) and a new check that the console probe
names a command the plugin declares, 18 tests. Compiled and loaded on both rigs; `heli.arrived`,
`cargo.arrived` and `airdrop.called` read back from each rig's sidecar store, and hello's
`console.plugins` on both. Sidecar: 71 tests, `fmt` and `clippy` clean. Module-Rust: 585 server tests (22 new
in `test/worldEvents.test.js`: the catalogue against the protocol's 24 types, D372's four rules against one
block and against one with nobody, the line and the count agreeing under every rule, the per-viewer reading,
the grid off the map, ingest, the triggers and their variables, the step's bounds and refusals), 84 client
tests, every check, and a boot against the pinned core (43 triggers, 12 actions, 10 budgets, 20 option sources;
the 89 routes unchanged).
**Walk rows added to §14.3.1's:** the Travelling Vendor (the spike's schedule run raised no spawn on the rig);
the road Bradleys' line (twelve were already out, so none came); the console probe after a `c.reload` typed in
Carbon's console; and a configuration save's reload from the website on Carbon (D180), which settles item 7.
**Not changed:** the Android app. It reads the API leniently, so it shows neither world events nor the two
columns until it learns them.
### 14.4 Questions for the org lead ### 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 **Answered on 2026-10-14:** Q17 as proposed, out (D358). **Q18 against the proposal: the end walk waits for

View File

@@ -2873,3 +2873,149 @@ through the `rust.backpack.size` step (*Bigger backpack*, `risk: 'change'`, `rev
`rust.backpacks`): one `rust_perm_run_grants` row per recipient with `backpacks.size.N` as its permission and no `rust.backpacks`): one `rust_perm_run_grants` row per recipient with `backpacks.size.N` as its permission and no
kit, pushed by the next `perm.sync` and revoked when the run's revert deletes the rows. Its message, a §20.2 kit, pushed by the next `perm.sync` and revoked when the run's revert deletes the rows. Its message, a §20.2
notice, defaults to a warning about the take-back. notice, defaults to a warning about the take-back.
### 20.5 Rust's own world events — `world.event`, `world.call`, the console probe (§14.3.1, D349, D353–D356, D363, D366, D367, D372, D376, D378, D379)
The patrol helicopter, Bradley, the cargo ship, the Chinook, airdrops, locked crates, the Travelling Vendor,
the satellite and the Deep Sea, **with no plugin** (D349). The design is
[`PLAN_REDESIGNS.md`](../modules/rust/PLAN_REDESIGNS.md) §14.3.1; what the test phase and stage 1's spike found
is §14.2.1 and §14.3.14 there.
**`world.event` — one event kind, told apart by `event`.** It is rare (a few an hour), so it is one frame per
world event. **The type is in `event`, never in `type`:** `type` is the envelope's (§8.1), and the sidecar files
a frame on it. The plan's first example put the world event's type there, and the sidecar dropped every one as
untyped on the rig — protocol 13's `world.expired` lesson again (§19).
```json
{"kind":"world.event","type":"event","t":1792041350288,"serverId":"main","wipeId":"w-20261005T165054Z",
"event":"heli.arrived","x":3900.0,"z":-1714.5}
```
Positions are `x` and `z`, rounded to 0.1 m. **The site works out the grid** from them and the map's own
geometry (§17), so the grid has one source. A player is `steamId` and `name`, only where the table says.
**Nothing is said until the world is loaded, nor while the server is quitting**: a booting server spawns the
cargo ship and the vendor it saved, and that is not either of them arriving.
| `event` | Raised from | Carries |
|---|---|---|
| `heli.arrived` | `OnEntitySpawned(PatrolHelicopter)` | `x`, `z` |
| `heli.left` | `OnHelicopterRetire` | `x`, `z` (last seen) |
| `heli.downed` | `OnEntityDeath(PatrolHelicopter)` at the crash | `credit`, `names`, `x`, `z` |
| `bradley.spawned` | `OnBradleyApcInitialize`, next tick. Road Bradleys spawned within a second are one frame | `where`: `launch_site` \| `road`; `count` |
| `bradley.destroyed` | `OnEntityDeath(BradleyAPC)` (D366) | `where`: `launch_site` \| `road` \| `other`; `credit`, `names`, `x`, `z` |
| `cargo.arrived` | `OnEntitySpawned(CargoShip)` | `x`, `z` |
| `cargo.docking` / `cargo.docked` / `cargo.undocked` | `OnCargoShipHarborApproach` / `…Arrived` / `…Leave` | `x`, `z`, `harbour`, `harbourName` |
| `cargo.left` | `OnCargoShipEgress`: it turns for the edge with its radiation on. The kill follows when it is gone | `x`, `z` |
| `chinook.arrived` | `OnEntitySpawned(CH47HelicopterAIController)`, a second later; **not** an oil rig's reinforcement (it has a landing target, or comes within 10 s of a rig's hack) | `x`, `z` |
| `chinook.dropped` | `OnHelicopterDropCrate`, joined to the `OnCrateLanded` of the crate under it (within 40 m and 120 s) | `monument`, `monumentName`, `x`, `z` |
| `chinook.downed` (D379) | `OnEntityDeath(CH47Helicopter)` | `credit`, `names`, `x`, `z` |
| `airdrop.called` | **a cargo plane's spawn**, next tick, once per plane | `steamId`/`name` of a supply signal's thrower; `x`, `z` (the drop point) |
| `airdrop.landed` | `OnSupplyDropLanded`, **once per drop** | `steamId`/`name` of the caller, `x`, `z` |
| `airdrop.looted` | the **first** `OnLootEntity` on a drop | `steamId`/`name`, `x`, `z` |
| `crate.hack.started` | `CanHackCrate` (who), joined to the `OnCrateHack` that follows within 2 s | `where`, `steamId`/`name`, `x`, `z` |
| `crate.unlocked` | `OnCrateHackEnd` | `where`, `steamId`/`name` (the hacker), `x`, `z` |
| `crate.looted` | the first `OnLootEntity` on an unlocked crate | `where`, `steamId`/`name`, `x`, `z` |
| `vendor.arrived` / `vendor.left` | `OnEntitySpawned` / `OnEntityKill(TravellingVendor)` | `x`, `z` |
| `satellite.crashed` | the `SatelliteCrashRemains` spawn at the impact (no hook of its own) | `x`, `z` |
| `deepsea.opened` / `deepsea.closed` | `OnDeepSeaOpened` / `OnDeepSeaClosed` | nothing: the Deep Sea is off the map |
- **`airdrop.called` comes from the plane's spawn**, because the console's `supply.call` raised neither
`OnAirdrop` nor `OnCargoPlaneSignaled` (stage 1). Those two only say who and where: a signal's thrower and its
position, the schedule's drop point. Otherwise the drop point is the plane's own, read from it.
- **`where` for a locked crate** is `oilrig_large`, `oilrig_small` (the `_oilrig` prefab and the nearest
`oilrig_1` / `oilrig_2`), `cargo` (a `CargoShip` parent), `ghostship` (the `_ghostship` prefab) or `chinook`
(a crate seen landing under a Chinook's drop). **Anything else is `other` and says nothing** — Guarded Crate's
crate is an ordinary locked crate — and a crate one of our own steps placed (§15's ledger) is skipped.
- **`where` for a Bradley** is `launch_site` when `BradleySpawner.singleton.spawned` is it, `road` when the road
event's own list holds it, and `other` otherwise. `RoadSpawned` cannot say: it defaults to true, so a plugin's
Bradley would read as a road one. An `other` Bradley's spawn is not news, but its death is sent: it counted
toward Tankbreaker under D174 and still does.
- **`harbour` and `monument`** are the nearest monument's kind (the world verbs' vocabulary, §15) and
`harbourName` / `monumentName` the game's English name for it; absent where the map has none.
- **"Oil rig scientists called" is not a type**: starting a hack on a rig calls them at once, so
`crate.hack.started` with `where: oilrig_*` says it. **Not subscribed:** `OnHelicopterOutOfCrates` (every
tick), `OnPatrolHelicopterTakeDamage` (every hit), `OnDeepSeaTeleport`, the F-15 (D367).
- **The joins are memory only** — plane to thrower, drop to caller, crate to hacker, looted once, a Chinook's
drop — keyed by the entity, dropped when it goes and swept every ten minutes. Nothing is written to disk, so a
reload forgets a crate's hacker and that line goes without a name.
**The credit block (D372, D379).** For a patrol helicopter, a Chinook and a Bradley the plugin keeps a damage
table from `OnEntityTakeDamage` (the hook D174's last-hitter already used, after the same type test, for those
classes only) and sends every candidate. **It credits nobody itself.**
```json
{"kind":"world.event","type":"event","event":"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: for the patrol helicopter, the player the first `OnPatrolHelicopterKill` names (the
crippling hit, seconds before a crash that names nobody); for a Bradley or a Chinook, its death's
`InitiatorPlayer`. `last` is the last player to hurt it, with no window. `most` is the most damage. Each is
absent when nobody qualifies, and a death nobody touched carries `"damagers": []`.
- `damage` is the damage as each hit reported it, summed; at most 32 `damagers`, by damage.
- **The site applies the server's rule** (`creditRule`, below) to the feed line and to the count alike.
**The tally.** `player.tally` gains **`cratesHacked`** (a hack counts for its starter at `crate.hack.started`,
once per crate, from any location but `other`) and **`dropsLooted`** (a drop counts for its first looter),
both sums (D356). **`apcKills` and `heliKills` are no longer sent**: the site counts them from `world.event`'s
credit block. A protocol-13 plugin still sends them, and the site still adds them.
**`world.call` — run one of the game's own world events now, for a run (D354, D367).**
```json
{"cmd":"world.call","reqId":"r-40","runId":"9","key":"9:2","event":"airdrop","count":3,"x":-412.0,"z":1180.5}
{"kind":"world.called","type":"reply","reqId":"r-40","key":"9:2","event":"airdrop","repeat":false,"called":3}
```
- `event` is a schedule's key — `patrol_heli`, `cargo`, `chinook`, `airdrop`, `travelling_vendor`,
`bradley_road`, `f15` — or `satellite`. The plugin runs **one of that schedule's `TriggeredEvent`s at random,
as `EventSchedule.Trigger` does, but not `Trigger` itself**, which would also reset the schedule's own clock.
The schedule is found whether or not it is enabled. The satellite is the game's own `satellite.random`. **No
plugin console command is used**, so it works the same on Carbon.
- **A position**, as `x`/`z` or `monument`/`offsetX`/`offsetZ` (§15's location), **only for `airdrop` and
`patrol_heli`**, which stage 1's spike placed: the airdrop by `InitDropPosition` before it spawns (it releases
0–1 m above the point and lands there), the patrol helicopter by `SetInitialDestination` before it spawns (it
reaches the point within about 50 m, then patrols as usual). **The Chinook takes none (D378)**: it drops only
at the map's drop zones, and the one route to a chosen zone borrows the Airfield call terminal's shared state.
The rest go where the game puts them. A position for any of them is `not-placeable`.
- **Kept.** Nothing a call starts is removed at teardown. The plugin remembers the call only — its key, its run,
what it called — in `data/RunicGateway/world-calls.json` for seven days, so a retried key answers
`repeat: true` with the first answer and calls nothing.
- **Bounds:** `count` 1–5, and 1 for `cargo`, `travelling_vendor` and `bradley_road`; at most
**`EventsMaxWorldCalls`** (config, default and ceiling 10) world events per run, counted over every call the
run made; behind `EventsEnabled`. A count the game partly ran answers `called` below `count`.
- Refusals are `world.error`: `not-ready`, `events-disabled`, `malformed` (no key or run, or a key over 128
characters), `bad-event`, `bad-count`, `not-placeable`, `too-many`, §15's location refusals, and `refused`
(the game had nothing to run).
- `world.monuments` gains `maxWorldCalls`.
**Hello and status gain `console`: `{ "plugins": true | false }` (D363).** Whether this framework runs a plugin's
console command from code, found at every load by running the bridge's own `rg.probe` through
`ConsoleSystem.Run`. The steps that drive another plugin through its console command refuse while it is false.
**The probe runs from a timer**, because stage 1 found Carbon 2.0.262 refuses ("not found") every plugin command
run from *inside* a console command, and from a `NextTick` that a console command queued — which is how §14.2's
spike ran every test — while it finds them from a timer and from the `NextTick` the link's reader queues, which
is every website command's path. On both rigs it reads `true`. Hello is sent again when the answer changes.
**The sidecar** adds `POST /world/call`, a thin forward that keeps nothing.
**The website (Module-Rust).**
- **The feed** serves `world.event` (a public kind), read per viewer by `worldEvents.shapeFor`: an `event` this
build does not know is staff's alone; the event and its **`grid`** are public; a player's **`name`** (and the
names in `credited`) only for the operator's presence audience (D376); a **Steam id and a position only for
staff** (D353). A downed one carries `credited` (`[{ name, steamId? }]`), `creditedCount` and `creditRule`.
- **`rust_servers.credit_rule`**: `blow` (the default: the killing blow, else the last hit), `last`, `most` or
`all` (everyone who hurt it, one each), set on the server's settings. The same rule writes the line and adds
`heli_kills` (patrol helicopters and Chinooks, *Rotorbane*) and `apc_kills` (*Tankbreaker*). A change applies
from then on; counts already written are not recounted.
- **Triggers** `rust.world.<event>`, one per type (D355), ceiling `everyone`, cooldown subject the server and
the event (the crate types add `where`), with `grid`, `player`, `where`, `harbour`, `monument`, `count` and,
for a downed one, `credited`. No Steam id.
- **The step `rust.world.call`**, *Call a world event* (`risk: 'change'`, `reversible: 'none'`, budget
`rust.world.calls`).
- **The tally's two counts** in `rust_player_wipe_stats.crates_hacked` and `.drops_looted`; title categories
`crateshacked` (*Safecracker*) and `dropslooted` (*Scavenger*); leaderboard columns *Crates hacked* and *Drops
looted*, sortable (`sort=cratesHacked`, `sort=dropsLooted`).