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:
@@ -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.<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`).
|
||||
|
||||
Reference in New Issue
Block a user