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