docs(rust): stage 4 built — Dynamic PVP's zones (PROTOCOL §20.7, PLAN_REDESIGNS §14.3.17)

- rust-link/PROTOCOL.md §20.7: hello's integrations.dynamicPvp, the two
  events and their fields, the sphere/cube shape and the live position, the
  quiet while the world is not live, map.live's pvp layer and its seed from
  Dynamic PVP's own API, and the website's classes, per-viewer reading and
  fifth map layer. §17.3's table gains the pvp row.
- modules/rust/PLAN_REDESIGNS.md §14.3.17: the stage-4 record — three things
  §14.3.3 had wrong or unsaid (the seed, cube zones, cargo's moving zone),
  eight points settled in code (all overrulable), the tests and the rig
  compile. Pointers from §9's summary and §14.3.3.

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 06:12:07 -05:00
parent 386bd83707
commit 586bc81566
2 changed files with 123 additions and 1 deletions

View File

@@ -930,7 +930,7 @@ pulls) is research, not a walk, so it still comes first:
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. **Stage 1, §13, was built on 2026-10-15
(§14.3.14)**, with D378 and D379. **Stage 2, core's `retryAfterMs` (MODULE_API 1.15.0), was built on
2026-10-15 (§14.3.15).** **Stage 3, TruePVE in the zone step, was built on 2026-10-15 (§14.3.16).**
2026-10-15 (§14.3.15).** **Stage 3, TruePVE in the zone step, was built on 2026-10-15 (§14.3.16)**, and **stage 4, Dynamic PVP, the same day (§14.3.17).**
**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
@@ -2531,6 +2531,9 @@ and see the leftover removed.
#### 14.3.3 Dynamic PVP
> **Built on 2026-10-15** (§14.3.17), specified in PROTOCOL.md §20.7. The seed, the zone's shape and the
> public names differ from this text; §14.3.17 says how.
**Hello.** `integrations.dynamicPvp`: `{ loaded, version }`.
**The wire.** Two events, `pvp.zone.created` `{ zoneId, event, x, z, radius, durationSec }` (−1 for "as long as
@@ -2976,6 +2979,59 @@ plugin's words). Full suites: server 603/603 and every check (imports, bundle, s
**Walk rows** are §14.3.2's, at the end walk (D331, D359).
#### 14.3.17 Stage 4, built: Dynamic PVP (2026-10-15)
Built in Rust-Plugins and Module-Rust, specified in [PROTOCOL.md](../../rust-link/PROTOCOL.md) §20.7 and §17.3.
**No Rust-Link change**: the sidecar stores the two events and forwards `map.live` whole. `PROTOCOL_VERSION`
stays 14. Builds: Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262; the code read is Dynamic PVP 5.2.0, the
version the §14.2 test ran and still the newest on uMod.
**Three things in §14.3.3 were wrong or unsaid, found in Dynamic PVP's source:**
1. **The seed comes from Dynamic PVP itself, not from TruePVE's table.** §14.3.3 seeded the layer from
*"ZoneManager's zones whose ids TruePVE maps for Dynamic PVP"*, but nothing in TruePVE's table says whose a
mapping is, and an operator's own numeric zone would be taken for one. Dynamic PVP has an API for exactly
this: `AllDynamicPVPZones()` and `GetEventName(zoneId)`.
2. **A zone is a sphere or a cube.** The plan read a radius from ZoneManager, but cargo's zone (the one that
works out of the box) is a cube, `Size` with no `Radius`. The layer and the line carry `shape`, and a cube's
`radius` is half its longer side across the ground, so the map can still draw a ring.
3. **Cargo's zone moves.** It is parented to the ship, and ZoneManager's `GetZoneLocation` answers where it was
made. The bridge reads the zone's transform each time, through `GetZoneByID` (a `MonoBehaviour`, read as a
`Component`, so the bridge needs none of ZoneManager's types).
**Settled in the code, and overrulable:**
1. **Only Dynamic PVP's eleven vanilla events carry their name to the public.** A monument zone is named after
the monument and an operator's event by whatever they typed, so below staff those read *"A PvP zone opened ·
G7"*. The map's ring shows the name as written, because the layer has its own audience.
2. **`pvp.zone.deleted` carries `event` too**, which the hook gives, so a staff line says which zone closed.
3. **Nothing is said while the world is not live**, as world events (§20.5): a boot opens every monument zone
at once. Dynamic PVP's own unload is said, because those zones really do close; the lines are staff's.
4. **The `pvp` layer is public by default**, its own `map.layer.pvp.audience`: the game draws the zones for
everyone near one, and they name nobody.
5. **The closing line is not on the public feed's list.** The client asks only for public kinds (its own test
holds it to that), and nothing on the site draws staff-only kinds today, `npc.died` included; the map shows
a zone going, and the API serves the line to staff.
6. **A zone ZoneManager no longer holds is dropped from the set when the map reads it**, since an erase by a
ZoneManager reload raises no Dynamic PVP hook. Dynamic PVP's unload clears the set after its deletes.
7. **Hello is sent again when Dynamic PVP loads or unloads**, and the set is re-seeded on the tick after it loads.
8. **Server settings' plugin line names Dynamic PVP.**
**Tests.** Module-Rust server: `test/dynamicPvp.test.js`, six new (the public read the grid and never the
position; staff read the position and id; a monument's or an operator's name is staff's; no geometry or off
the map, no grid; another kind untouched; the feed shapes a stored row per viewer); `catalogue.test.js` holds
the two kinds and their classes; `map.test.js` two new (the layer passes through, is empty from an older
plugin, and is absent when set to staff) and the fresh-install defaults. Client: four new in `feed.test.js`
(the opening's words, detail and duration; an unnamed and a monument zone; the closing; every vanilla event has
words, held against the server's list) and one in `mapEvents.test.js` (the ring's tooltip, and the client's
names held against the server's list). Full suites: server 611/611 and every check; client 89/89, the build
and externals. The plugin compiled and loaded on both rigs, `rg.world` read `dynamicPvp=False pvpZones=0` on
each, and both were put back on the protocol-13 bridge. Dynamic PVP itself is not on the rigs; its hooks
are exercised by §14.3.3's walk rows.
**Walk rows** are §14.3.3's, at the end walk, with one more: hotload the bridge while a zone stands and see
the ring still drawn (the seed).
### 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

View File

@@ -1809,6 +1809,7 @@ drawing a second time. A new map key makes the file stale, and it is ignored.
| `events` | what this site's events placed (§15.2): `kind` (`zone`, `crate`, `npc`), `runId`, `x`, `z`, and a zone's `radius` and `name` or a thing's `prefab` and `key` (the step that placed it). Protocol 13 adds, for one of RunicNPC's NPCs, its `name`, and for a boss `boss: true` and `health` (0–1); and a boss's **adds**, which no step placed, under their boss's run with `add: true` |
| `players` | `steamId`, `name`, `x`, `z`, `sleeping`, `online`. Connected players, then offline sleepers |
| `bases` | `kind` (`tc`, `vending`), `x`, `z`. **Positions only**: no owner, no authorised list, no shop name |
| `pvp` | Protocol 14 (§20.7): Dynamic PVP's own zones, `zoneId`, `event`, `x`, `z` (where the zone is now), `radius`, `shape` (`sphere`, `cube`). Empty without Dynamic PVP |
It also carries `mapKey` and `tookMs`, the plugin's own time for the ask. `playersTruncated` and
`basesTruncated` are set when a bound cut a layer short.
@@ -3075,3 +3076,68 @@ name over 64 characters, and either one is refused where the server's last hello
(a plugin before protocol 14, or never reached) is left to the plugin. `truepve-missing` and
`truepve-ruleset` fail the step for good, with the plugin's words. Server settings' plugin line says whether
TruePVE is loaded.
### 20.7 Dynamic PVP's zones — `pvp.zone.created`, `pvp.zone.deleted`, the `pvp` layer (§14.3.3)
HunterZ's **Dynamic PVP** (5.2.0, uMod; it requires ZoneManager and TruePVE), optional like Economics (D199)
and not in `overlay.toml`. It opens its own PvP zones around a downed patrol helicopter, a Bradley, a locked
crate, an airdrop, the cargo ship and the rest. **The bridge only reports them**: it never makes, maps or
erases a Dynamic PVP zone, and nothing on the site starts one. The design is
[`PLAN_REDESIGNS.md`](../modules/rust/PLAN_REDESIGNS.md) §14.3.3; what the §14.2 test found is §14.2.1 there,
and what the build found in Dynamic PVP's own source is §14.3.17.
**Hello and status.** `integrations` gains `dynamicPvp`:
```json
"dynamicPvp": { "loaded": true, "version": "5.2.0" }
```
Hello is sent again when Dynamic PVP loads or unloads. `rg.world` prints `dynamicPvp=` and `pvpZones=` (the
zones the bridge knows are live).
**Two events**, from Dynamic PVP's own hooks:
```json
{"kind":"pvp.zone.created","type":"event","zoneId":"1412203045","event":"Helicopter",
"x":812.4,"z":-340.5,"radius":100,"shape":"sphere","durationSec":600}
{"kind":"pvp.zone.deleted","type":"event","zoneId":"1412203045","event":"Helicopter"}
```
- **`pvp.zone.created`** is `OnCreatedDynamicPVP(zoneId, eventName, position, duration)`. `event` is Dynamic
PVP's own name for what opened it: one of its vanilla events (`Bradley`, `Helicopter`, `Satellite`,
`TimedSupply`, `SupplySignal`, `CargoShip`, `HackableCrate`, `ExcavatorIgnition`, `GhostShip`,
`DeepSeaIsland`, `IslandCannon`), a monument's name for a monument zone, or an operator's own event name.
The hook carries no size, so **`radius` and `shape` are read from ZoneManager**: a sphere's `Radius`, or for
a cube (cargo's) half the longer of its `Size` across the ground, `shape: "cube"`. **`x`, `z` are where the
zone is**, from its transform, which for a parented zone (cargo) is the ship. `durationSec` is −1 for a zone
that lasts as long as its event (cargo, a monument's).
- **`pvp.zone.deleted`** is `OnDeletedDynamicPVP(zoneId, eventName)`, for a zone the bridge knew.
- **Neither is sent while the world is not live** (§20.5's rule): a boot or a shutdown opens or closes every
monument zone at once, and that is not news. Dynamic PVP's own unload deletes every zone and is said, as
`deleted` lines.
- Dynamic PVP's ids are numeric (a time stamp, or the entity's net id), never one of the bridge's `rg-…`.
- `OnPlayerEnterPVP` / `OnPlayerExitPVP` are not subscribed: they would say who stands where.
**The `pvp` layer of `map.live`** (§17.3) is every zone the bridge knows is live and ZoneManager still holds,
with where it is **now** (cargo's moves with the ship), `radius` and `shape`. The bridge keeps the set by the
two hooks, and **seeds it from Dynamic PVP itself** (`AllDynamicPVPZones()`, `GetEventName(zoneId)`) when the
world is ready and when Dynamic PVP loads, because a hotload of the bridge hears no creates. A zone ZoneManager
no longer holds is dropped from the set when the layer is read.
**Hooks.** `OnCreatedDynamicPVP` and `OnDeletedDynamicPVP` join `rg.hooks`' list.
**The sidecar** stores and forwards the two frames like every other event, and `map.live` whole; nothing in
it changed.
**The website (Module-Rust).**
- **`pvp.zone.created` is a public kind, `pvp.zone.deleted` a staff kind.** Each row is read per viewer by
`dynamicPvp.shapeFor`, as a world event is (§20.5): everyone gets `grid`, `radius`, `shape` and
`durationSec`; **`event` only when it is one of Dynamic PVP's vanilla events**, because a monument's or an
operator's name is the operator's own text; and **`x`, `z`, `zoneId` only for staff**, because the map has its
own audience for where things are (D111). The public line reads *"A PvP zone opened around the downed patrol
helicopter · G7 · for 10 min"* and names nobody. No trigger and no step.
- **The map gains a fifth layer, `pvp`** (*PvP zones*), with its own audience in Admin → Rust visibility
(`map.layer.pvp.audience`), **public by default**: the game shows the zones to everybody near one, and they
name nobody. It is drawn as a red ring with its event's name.
- Server settings' plugin line says whether Dynamic PVP is loaded.