Compare commits

...

3 Commits

Author SHA1 Message Date
baef8f27eb docs(rust): §3's site half as walked — the presets page, a step from a preset, a run on Carbon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 09:54:53 -05:00
4056e5a14d docs(rust): §3 walked on the rigs, and D212 (the full dome is the default)
PLAN_REDESIGNS §3.4: the rig rows as walked 2026-09-29 without a player,
the dome types, D212, and the in-game rows deferred to the later in-game walk.
PROTOCOL §19.11: dome type 0 when absent; walk status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 09:42:25 -05:00
7f9026c9e7 docs(rust): zones and the dome as built — PROTOCOL 19.11, PLAN_REDESIGNS 3.4, D210, D211
PROTOCOL §19.11: world.zone's seven new fields, bad-option and
dome-unavailable, the bridge-said zone messages, the domes helper and
OnRgDomesReady, hello's zoneManager and integrations.zoneDomes.

PLAN_REDESIGNS: §0.6 corrected (ZoneFieldListRaw exists), §3.4 as built,
D210 (flags ticked on a presets page, the step copies a preset's line) and
D211 (the bridge reads the flag list). INSTALL names the new helper.

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

View File

@@ -42,7 +42,7 @@ probe plugin that wrote one data file. It was unloaded and deleted afterwards on
| 0.3 | Do groups carry anything the site does not model? | **Parents.** `GetGroupParent` answers on both. The site's `rust_perm_groups` has no parent column. | §1.4 |
| 0.4 | Is there a framework hook for a finished Rust mission (D173)? | **Yes: option 1 of D173's spike.** Oxide's patched `Assembly-CSharp.dll` raises `OnMissionSucceeded`, `OnMissionFailed`, `OnMissionStarted` and `OnMissionAssigned`, and `Carbon.Hooks.Oxide.dll` patches the same four names onto `BaseMission`. Rust's own methods are `BaseMission.MissionSuccess(MissionInstance, BasePlayer assignee)` and `MissionFailed(MissionInstance, BasePlayer, MissionFailReason, bool)`. The catalogues [`HOOKS.md`](HOOKS.md) and [`CARBON.md`](CARBON.md) predate them. The exact hook arguments are confirmed when a person finishes a mission (§5.7). | §5.7 |
| 0.5 | Is ZoneDomes' API safe with a null player (PLAN_FIXES §4.4, §7)? | **No.** `AddNewDome` and `RemoveExistingDome` end with `player.ChatMessage(...)` and no null check. With a null player the dome is made and saved, and then the call throws. On boot, ZoneDomes also drops every dome whose zone does not exist yet, and the bridge's temporary zones are re-created after that. | §3.2 |
| 0.6 | Can ZoneManager's flag list be read at run time? | **Only from inside.** The list is `ZoneManager.ZoneFlags.NameToIndex`, a public static on a nested type. No hook or `Call` method returns it. The zone helper (`RunicGatewayZones.cs`) already references ZoneManager's types, and the bridge may not (R2, D168). | §3.1 |
| 0.6 | Can ZoneManager's flag list be read at run time? | **Only from inside.** The list is `ZoneManager.ZoneFlags.NameToIndex`, a public static on a nested type. No hook or `Call` method returns it. The zone helper (`RunicGatewayZones.cs`) already references ZoneManager's types, and the bridge may not (R2, D168). **Wrong, found at the build (D211):** ZoneManager 3.1.14's API region has `ZoneFieldListRaw()`, which returns the zone field names followed by every `NameToIndex` key, and the bridge calls it by name. | §3.1, §3.4 |
| 0.7 | What does the map actually show? | **85 markers on the rigs' map (world 3000, seed 1234): 31 substations in four prefab kinds, 7 caves in six, 7 train-tunnel entrances in four, and 5 water wells in three.** The label (`Substation`) groups the variants. Rust also classifies monuments itself (`MonumentInfo`'s `MonumentType`: Cave, WaterWell, Lake, Mountain, Radtown, Building, Town, Airport, Lighthouse). | §4 |
| 0.8 | Can the site see how much of a kit players have used? | **One player at a time.** `kits.list` already returns each kit's `max` (MaximumUses) and `cooldown`, and the module ignores `cooldown`. Per-player use is read only during a credit settle (`GetPlayerKitUses`). | §2.6 |
| 0.9 | Where do the Ultima Online examples a Rust admin sees come from (U-3)? | **Core's own actions.** `core.lease` gives `uo.rate.skillgain` as its example, and the announcement actions talk about Britain and Cove. | §2.1 |
@@ -424,6 +424,120 @@ for one with it; the enter and leave messages arrive as chat and then as popups;
reload and a ZoneDomes reload all keep the flags and the dome; expiry and teardown remove the dome from the
world and from ZoneDomes' data file; and with the domes helper removed, the step stops offering a dome.
### 3.4 As built (2026-09-29; not yet walked on a rig)
Built on `edge` as Rust-Plugins `feat/zones-domes`, Module-Rust `feat/zones-domes` and installer
`feat/rust-domes-helper`; the wire is PROTOCOL §19.11. The build changed §3.1 and §3.2 in four places.
- **Where the flags are ticked (D210).** Core's step editor holds one value per field: a text box, a number,
a yes/no, a date, or one dropdown. It has no checkbox list and no slot for a module's own button, and
MODULE_API 1.12.0 (§8) adds neither. So the checkboxes are on a new page, **Admin → Rust zone presets**,
and the step's **Options** field is one line of text (`NoBuild, NoPlayerLoot, radiation=10`). The field's
dropdown lists the presets, and **each row's value is the preset's line**, so picking one writes that
line into the field. Core already writes a dropdown's pick into the free-text field beside it, so this
needed no core change. It keeps §3.1's promise: the step holds its own copy, and editing a preset
changes no published event. There is no **Save as preset** button in the step; a preset is made on its
page. Presets are `rust_zone_presets` (`name`, `flags`, `settings`, `all_servers`) and
`rust_zone_preset_servers`. As with a group (D189), the same name may be used on two servers but not
twice on one.
- **Who reads the flag list (D211).** The bridge does, with `ZoneFieldListRaw()` (§0.6 corrected). The zone
helper gained nothing, and a server without it still offers every flag, so there is no
"name, radius and duration only" mode. The list is in hello, and the bridge sends hello again when
ZoneManager, ZoneDomes or either helper loads or unloads.
- **The dome must wait for ZoneDomes' own start.** ZoneDomes' `InitializeDomes` also re-draws every dome
in its data file whose zone exists. A dome the bridge added before that is drawn twice, and the first
set of spheres is left in the world with nothing able to remove it. The domes helper therefore
postfixes `InitializeDomes` and raises `OnRgDomesReady`, and the bridge puts domes back only after it.
The helper's patch goes in at load, before ZoneDomes' `OnServerInitialized`.
- **A popup falls back to chat at the moment of speaking**, as §2.4 says, and the zone is not refused for
it: a zone's messages are said long after the step ran. (`rust.announce` still refuses a popup without
PopupNotifications, because its line is said at once.)
**Chosen in the build, within the plan:**
- **Settings bounds:** radiation 0–500, comfort 0–1, temperature −100–100 °C.
- **Permission:** up to 32 of `a–z0–9_`.
- **Dome stack:** 1–10. ZoneDomes itself has no limit, and each sphere is an entity.
- **The 5 s quiet:** after re-creating a zone, the bridge stays silent to anyone already inside it.
- **The flag groups:** in `server/model/zones/zoneOptions.js`. `NoTp` and ZoneManager's `Custom1`–`5`
are under Other.
**The default dome (D212).** ZoneDomes 2.0.2 has five types, and only **Standard** (type 0, Rust's shaded
`sphere.prefab`) is a whole dome: a dark, see-through sphere that each stacked copy makes darker. Red, Blue,
Green and Purple are the Twitch battle-royale spheres, which ZoneDomes itself says show only where they meet
terrain or an object, so they read as a ring at the zone's edge. **The org lead chose the full dome as the
default:** the step lists Standard first as "Full dome (shaded)", labels each colour for what it shows, and
a `dome` sent without a `type` is Standard. The default stack is still 1, until it is chosen by eye.
**Compiled on both rigs, and the rig rows walked, 2026-09-29** (no player; the "file API outage" that
morning was a Git Bash path-rewriting bug in the walk's own helper, not the rigs):
- **Both rigs:** the bridge and both helpers compiled and loaded (Oxide; Carbon with 0 failed plugins).
Both helpers reported `patched`, and the domes helper `ready`.
- **Hello (Oxide, read from the sidecar's board):**
- `zoneManager` carried all **69** flags of ZoneManager 3.1.14 (64 named and `Custom1`–`5`), with no zone
helper involved (D211).
- `integrations.zoneDomes` was `{ loaded, patched, ready }`.
- It was re-sent within a second each time ZoneDomes or a helper loaded or unloaded.
- **Refusals:** an unknown flag (`NoFlying`), a setting outside the allowlist (`rotation`) and radiation 900
were each refused `bad-option`, with the reason.
- **A full zone:** `nobuild, PvpGod, NoPlayerLoot`, radiation 5, safezone off, a popup enter/leave message and a
stacked dome.
- ZoneManager held exactly those flags and settings (read through a throwaway probe calling
`ZoneFieldList`).
- ZoneManager held no enter message of its own: the bridge says it (D193).
- The registry stored the flags as ZoneManager spells them.
- ZoneDomes' data file held the dome.
- **ZoneManager reload:** the zone came back ("1 re-created") with all its flags. The dome's data was
untouched.
- **ZoneDomes reload:** the helper re-patched and was ready again. The dome was redrawn from ZoneDomes' own
data.
- **Server restart:**
- The zone was re-created with its flags.
- On this boot the bridge's zones existed before ZoneDomes started, so ZoneDomes drew the dome itself,
and the bridge's restore added nothing.
- **Exactly 2 sphere entities for a stack of 2**, so the dome was not drawn twice.
- **The other order, forced:**
- The bridge was unloaded (ZoneManager erased the zone), then ZoneDomes reloaded and dropped the dome's
data.
- The bridge was loaded again: "domes after load: 1 put back", and again exactly 2 spheres. §0.5's
boot fault is handled whichever plugin starts first.
- **Teardown (`world.revert`):** the zone was erased, 0 spheres were left, and ZoneDomes' data was empty.
- **Expiry:** a 1-minute domed zone was erased at its deadline, and its dome went with it (0 spheres, data
empty).
- **Without the domes helper:** a dome was refused `dome-unavailable`, "RunicGatewayDomes.cs is not
installed beside the bridge…". Hello said `missing`, then `patched`/`ready` once the helper was loaded
again.
**The site half, walked 2026-09-29** on a local core (website `main` `f0e7d2a`) with this branch's module,
against `rust-carbon`. `rust-oxide`'s fresh egg installed the last released sidecar, which speaks protocol 12,
so the site refuses it until protocol 13 ships:
- **The page, in the browser as an admin:**
- Each server's line: Carbon's ZoneManager 3.1.14 with 69 flags, and "domes available".
- The edit form: the flags in their groups with the preset's ticked, the servers, and the settings with
their bounds.
- A save with comfort 3 was refused under the form ("comfort is 0 to 1, not "3""). Corrected, it saved,
and the preset's line updated.
- The sidebar row. No console errors.
- **The API:** a flag Carbon does not have was refused, naming the server. So were an out-of-range setting,
a preset for no server, and a second "arena" on the same server (409).
- **The step:**
- The Options dropdown's row value is the preset's line (D210).
- After the preset was edited, the published step still held its own old line. Picking the preset in the
editor wrote the new one.
- The dome dropdown carries D212's labels.
- A draft with `NoFlying` failed its dry run with that reason.
- **A run through the whole path:**
- The site opened the zone on Carbon: the preset's flags and settings, the popup messages, and ZoneDomes'
Standard dome at stack 2.
- The run's cleanup removed it, leaving ZoneDomes' data empty.
**Still to walk:**
- **The in-game rows, deferred by the org lead (2026-09-29) to the later in-game walk, with §5.7's:**
- The flags hold for a player without the exemption and not for one with it.
- The enter and leave messages arrive as chat and as a popup.
- The default stack is chosen by eye, in daylight and at night.
---
## 4. The live map's marker types (§4.5; D165)
@@ -916,3 +1030,11 @@ The org lead answered §11.4.4's three questions the same day:
These are new verbs, events and hello fields. If they are built before the cutover they join the unreleased
protocol 13 like everything else here; if the cutover comes first, they open protocol 14. Which one is decided
when §9 reaches item 7.
The §3 build raised two more, answered on 2026-09-29:
| # | Decision | Rejected | § |
|---|---|---|---|
| **D210** | **A zone's flags and settings are ticked on an Admin → Rust zone presets page**, and the zone step's Options field copies a preset's line when it is picked. Core is unchanged. | A list param type in core (MODULE_API) drawn as grouped checkboxes; a module-supplied step editor. | 3.4 |
| **D211** | **The bridge reads ZoneManager's flag list** through its API call `ZoneFieldListRaw()` and sends it in hello. A server without the zone helper still offers every flag. | Keeping the read in the zone helper, with a name/radius/minutes-only mode without it. | 3.4 |
| **D212** | **A zone's dome defaults to ZoneDomes' Standard type, the full shaded dome.** The coloured types show only where they meet the ground or a building, and the step says so. | A coloured default. | 3.4 |

View File

@@ -159,7 +159,9 @@ history, and the token is unchanged.
`oxide/plugins/` or `carbon/plugins/`, and every other `.cs` the tarball's `manifest.json` lists
in `files` beside it — from protocol 13 that is `RunicGatewayZones.cs`, which lets ZoneManager count a
player already standing in a zone when it opens or comes back after a restart. It is optional: leave
it out and events still score everybody, but ZoneManager's own flags miss that player. To name the server, create its config first —
it out and events still score everybody, but ZoneManager's own flags miss that player. The second is
`RunicGatewayDomes.cs`, which lets the bridge ask ZoneDomes for a dome over an event zone; leave it
out and zones open without one. To name the server, create its config first —
`oxide/config/RunicGateway.json` or `carbon/configs/RunicGateway.json` — holding
`{ "ServerId": "<id>", "Port": 7799 }`. Without it, the plugin calls the server `main`.
4. **The sidecar:** put the binary somewhere stable and give it a config:

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 |
@@ -2245,3 +2245,105 @@ logs its summary instead of replying.
Walked on both rigs, 2026-09-27/28. Oxide gave 85 permissions, every one owned (`RustCore` owns
`oxide.*`), in 148 ms over four ticks. Carbon gave 104, the 30 `adminmodule.*` unowned, in 204 ms. On
both, **`zonemanager.ignoreflag.nokits` belongs to ZoneManager**.
### 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.