docs(rust): stage 4 built — Dynamic PVP's zones (PROTOCOL §20.7, PLAN_REDESIGNS §14.3.17) #351
@@ -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