From adf197e5f8b68d9f9742e301adc519eb015e3624 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Thu, 15 Oct 2026 00:39:01 -0500 Subject: [PATCH] =?UTF-8?q?docs(rust):=20stage=201=20built=20=E2=80=94=20w?= =?UTF-8?q?orld=20events=20on=20protocol=2014=20(PROTOCOL=20=C2=A720.5,=20?= =?UTF-8?q?D378,=20D379)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- modules/rust/PLAN_REDESIGNS.md | 85 ++++++++++++++++++- rust-link/PROTOCOL.md | 146 +++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) diff --git a/modules/rust/PLAN_REDESIGNS.md b/modules/rust/PLAN_REDESIGNS.md index 2b10439..d8e9f4f 100644 --- a/modules/rust/PLAN_REDESIGNS.md +++ b/modules/rust/PLAN_REDESIGNS.md @@ -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 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 - 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 §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 | | **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 | +| **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) @@ -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) +> **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`**: `{ 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 @@ -2673,7 +2681,7 @@ in the app; wipe and see the points stay. | 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` | +| 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` | | 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 | @@ -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 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 **Answered on 2026-10-14:** Q17 as proposed, out (D358). **Q18 against the proposal: the end walk waits for diff --git a/rust-link/PROTOCOL.md b/rust-link/PROTOCOL.md index 25d52fc..ab148ec 100644 --- a/rust-link/PROTOCOL.md +++ b/rust-link/PROTOCOL.md @@ -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 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. + +### 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.`, 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`).