Merge main into docs/rust-title-conditions: §3's docs

PROTOCOL.md: §19.10 (titles) and §19.11 (zones) both kept, in order.
PLAN_REDESIGNS: the D210-D212 table moved from the end of the file, where
docs#296 appended it by mistake, into §10 after D209.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-29 11:36:45 -05:00
3 changed files with 229 additions and 3 deletions

View File

@@ -1436,7 +1436,7 @@ five routes.
| Command | Answers | |
|---|---|---|
| `world.monuments` | `world.monuments` | This map's monuments in one stable order (grouped by prefab short name, then by position). Each has `value` (`kind`, or `kind#n` when the kind repeats), `kind`, `instance`, `of`, `label` (the game's display phrase), `x`, `z` and `grid`. Also `worldSize`, the placeable `prefabs` (`key`, `kind`, `label`), `eventsEnabled`, `maxCrates`, `maxNpcs` and `zoneManager` |
| `world.zone` | `world.ok` or `world.error` | Opens a ZoneManager temporary zone owned by the bridge. Needs `runId`, `key`, a location, `radius` (5–150 m) and `holdMs` (1 minute to 7 days); `name` is optional |
| `world.zone` | `world.ok` or `world.error` | Opens a ZoneManager temporary zone owned by the bridge. Needs `runId`, `key`, a location, `radius` (5–150 m) and `holdMs` (1 minute to 7 days); `name` is optional. Protocol 13 adds `flags`, `settings`, `enterMessage`, `leaveMessage`, `delivery`, `format` and `dome` (§19.11) |
| `world.place` | `world.ok` or `world.error` | Places `count` of one allowlisted `prefab` at a location, scattered within `spread` m (0–50, 10 by default for a group). All or nothing: if the game refuses one, the ones already made are killed |
| `world.revert` | `world.ok` | Gives back what a run owns: the named `ids`, or else everything under `key`, or else everything the run owns. The answer lists `removed`, `gone` and `refused` |
| `world.owned` | `world.owned` | What the world still holds of what events made, **looked for** by net id or zone id, narrowed by `runId`. Anything gone is pruned from the registry as the walk passes it |
@@ -2281,3 +2281,105 @@ frame from an older plugin has none:
are in `ExpectedHooks`, so a framework that stops raising one shows as zero in `rg.hooks`.
No sidecar change: the sidecar stores and forwards `player.tally` whole, as it does every frame.
### 19.11 A zone's options, its messages and its dome (PLAN_REDESIGNS §3)
**`world.zone` gains seven optional fields.** A zone that sends none of them is opened exactly as before.
```json
{"cmd":"world.zone","reqId":"r-4","runId":"13","key":"9c1e…","monument":"airfield_1","radius":40,
"holdMs":1800000,"name":"Arena",
"flags":["NoBuild","PvpGod"],"settings":{"radiation":10,"safezone":false},
"enterMessage":"You entered the arena.","leaveMessage":"You left the arena.",
"delivery":"chat","format":"<color=#ffb400>[Event]</color> {message}",
"dome":{"type":1,"stack":2}}
```
- **`flags`** is a list of ZoneManager flag names, at most 80. Each is checked against the flags **this
server's ZoneManager** declares, case-insensitively, and passed on as ZoneManager spells it. An unknown
one is refused **`bad-option`**, never dropped: a zone that quietly lacks the "no building" its author
chose is worse than a step that fails and says why.
- **`settings`** is an object of five allowlisted keys: `radiation` (0–500), `comfort` (0–1), `temperature`
(−100–100), `safezone` (true/false) and `permission` (1–32 of `a–z`, `0–9`, `_`; ZoneManager registers it
as `zonemanager.<name>`). Anything else is `bad-option`, and a value out of range is `bad-option` with the
range. Every other ZoneManager field (rotation, parent, size, eject spawns) stays out, because each one
changes what the bridge believes the zone is.
- Both are passed to **`CreateOrUpdateTemporaryZone` as ZoneManager's own key/value arguments** (a setting
by its key, a flag as `"<Flag>", "true"`), kept on the zone's registry entry, and passed again whenever the
bridge re-creates the zone: at a restart, a hot reload of the bridge, and a ZoneManager reload. A flag a
newer ZoneManager no longer has is ignored by ZoneManager on re-creation rather than failing the zone.
- **`enterMessage` and `leaveMessage`** (up to 256 characters each) are said **by the bridge**, never given
to ZoneManager (D193): ZoneManager says a zone's messages as popups only when its own config turns
popups on for every zone on the server. The bridge handles `OnEnterZone(zoneId, player)` and
`OnExitZone(zoneId, player)` and speaks only for zones in its registry. A zone it has just re-created says
nothing for 5 s, because ZoneManager (with the zone helper, §19.6) re-enters whoever is standing in it.
- **`delivery`** is `chat` (the default) or `popup`. **A popup where PopupNotifications is missing falls back
to chat**; the zone is not refused for it, because the moment the message is said can be days after the
step ran. **`format`** is the chat voice (§33.2 of PLAN.md), one `{message}`, used only for chat.
- **`dome`** asks ZoneDomes for a dome over the zone: `type` is ZoneDomes' own sphere type (0 Standard,
1 Red, 2 Blue, 3 Green, 4 Purple; **0 when absent**, the one full dome, D212) and `stack` 1–10 spheres (1 when
absent). The coloured types show only where they meet terrain or a structure. It is refused
**`dome-unavailable`**, with which piece is missing, unless ZoneDomes is loaded and the domes helper has
patched it and seen ZoneDomes finish starting. A dome ZoneDomes does not make takes its zone with it:
the zone is erased and the step refused.
**The dome's lifecycle is the bridge's.** It adds the dome after the zone, because ZoneDomes reads the
zone's place and radius from ZoneManager. At expiry and at `world.revert` it calls `RemoveExistingDome`
**before** erasing the zone, so a dome never outlives its zone in ZoneDomes' data file. After re-creating
zones it calls `AddNewDome` again for each zone that had one; ZoneDomes answers false for a dome it
already has, so this is safe to repeat.
**The domes helper, `RunicGatewayDomes.cs`** (D194, D168), ships beside the bridge like the zone helper.
It does two things, both by Harmony:
- A **prefix on ZoneDomes' `TranslateMessage(BasePlayer, string, string)`** skips the message when the
player is null. ZoneDomes 2.0.2's `AddNewDome` and `RemoveExistingDome` end with that message and no null
check, so a call from a plugin made the dome, saved it, and then threw (PLAN_REDESIGNS §0.5).
- A **postfix on `InitializeDomes`**, ZoneDomes' own start, raises **`OnRgDomesReady`**. ZoneDomes' start
drops every dome whose zone does not exist yet and draws every one that does. A dome added **before**
it is drawn twice, and the first set of spheres stays in the world with nothing able to remove it. So the
bridge puts domes back only once the helper says ZoneDomes has started. A helper or ZoneDomes loaded into
a running server is ready on the next tick.
It patches at load (and when ZoneDomes loads), so at a real start the patch is in before ZoneDomes'
`OnServerInitialized`. `rgd.status` prints its state.
**Hello and status.** `server.hello` gains, wherever ZoneManager is loaded:
```json
"zoneManager": { "version": "3.1.14", "flags": ["AutoLights", "AlwaysLights", "NoKits", "…"] }
```
`flags` comes from ZoneManager's own API call **`ZoneFieldListRaw()`**, less the leading field names
(`name`, `radius`, `enter_message`, …). **Plan §0.6 said only a plugin that references ZoneManager's types
could read the list; that was wrong for 3.1.14** (D211), so the bridge reads it and a server without the
zone helper still reports every flag. It is in hello rather than every status frame because it changes only
when ZoneManager does, and **the bridge sends hello again when ZoneManager, ZoneDomes, or either helper
loads or unloads**.
`integrations` on `server.status` (and so on hello) gains `zoneDomes`:
```json
"zoneDomes": { "loaded": true, "version": "2.0.2",
"helper": { "state": "patched", "version": "0.1.0" }, "ready": true }
```
`helper.state` is `patched`, `unsupported` (with `reason`), `no-zonedomes` or `missing`, as §19.6's are.
**Hooks.** `OnEnterZone`, `OnExitZone` and `OnRgDomesReady` join `rg.hooks`' list.
**The sidecar** forwards `world.zone` whole, as before; nothing in it changed.
**The website** (Module-Rust): the zone step `rust.zone.open` gains `options` (one line, `NoBuild,
NoPlayerLoot, radiation=10`, read into `flags` and `settings`), `enterMessage`, `leaveMessage`, `delivery`,
`dome` (`standard`, `red`, `blue`, `green`, `purple`) and `domeStack`. Core's step editor holds single values
only, so the flags are ticked on a new page, **Admin → Rust zone presets**, and saved by name for one server,
several, or every server (D195, D210). The step's `options` field offers the presets (option source
`rust.options.zone_presets`); **a row's value is the preset's line itself**, so picking one copies it and a
preset edited later changes no published event. The step checks the line and the dome against the server's
last hello when it is saved and in a dry run; `bad-option` and `dome-unavailable` are permanent refusals.
**Compiled on both rigs and walked without a player, 2026-09-29** (PLAN_REDESIGNS §3.4): every refusal,
ZoneManager and ZoneDomes reloads, a server restart and the forced opposite boot order (exactly one set of
spheres each time), revert and expiry removing the dome, and a dome refused without the helper. The
in-game rows (flags on a player, the messages) wait for the later in-game walk.