Files
Module-Rust/server/test/dynamicPvp.test.js
wtclaude 549b3a5ffd
All checks were successful
PR Checks / client-build (pull_request) Successful in 25s
PR Checks / server-tests (pull_request) Successful in 31s
PR Checks / frozen-manifest (pull_request) Successful in 44s
feat(rust): Dynamic PVP's zones on the feed and the map (PLAN_REDESIGNS §14.3.3)
Stage 4 of the §14.3 integration plan, on protocol 14 (docs
rust-link/PROTOCOL.md §20.7).

- pvp.zone.created is a public kind, pvp.zone.deleted a staff kind. Each row
  is read per viewer by the new dynamicPvp.js: grid for everyone, the event's
  name only for Dynamic PVP's own vanilla events, position and zone id for
  staff only.
- The feed's world filter carries the opening: "A PvP zone opened around the
  downed patrol helicopter · G7 · for 10 min". It names nobody.
- The map gains a fifth layer, `pvp` (PvP zones), public by default with its
  own audience in Admin → Rust visibility, drawn as a red ring with its
  event's name.
- Server settings' plugin line names Dynamic PVP; the swagger schemas and
  route descriptions carry the layer and the integration (fragment
  regenerated); ci/bundle.json ships dynamicPvp.js; README lists it.

Tests: server 611/611 (dynamicPvp.test.js new, catalogue and map extended)
and every check; client 89/89 (feed and map tooltip), build and externals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-15 06:12:07 -05:00

103 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ── Dynamic PVP's zones (PLAN_REDESIGNS §14.3.3, protocol 14) ──────────────
//
// Each test is one way the feature could look right and be wrong:
//
// a zone's position reaches a viewer below staff through the feed, round the
// map's own audience (D111)
// an operator's own event name, typed into Dynamic PVP's config, reaches a
// public page
// the feed shapes a zone line without its grid
// a frame of another kind is changed by the zone reader
const test = require('node:test')
const assert = require('node:assert')
const { fakeCtx } = require('./_fakes')
/** Installs a core whose `db.query` answers `rows`. */
function withRows(rows = []) {
const ctx = fakeCtx({
db: { query: (sql, params = []) => Promise.resolve(typeof rows === 'function' ? rows(sql, params) : rows), pool: {} },
})
require('../core')._reset()
require('../core').init(ctx)
}
withRows()
const dynamicPvp = require('../dynamicPvp')
const { gridOf } = require('../worldEvents')
const NOW = Date.now()
const GEOMETRY = { worldSize: 6000, gridCells: 41, gridCellSize: 146.34146341463415 }
const created = (over = {}) => ({
kind: 'pvp.zone.created',
type: 'event',
t: NOW,
serverId: 'main',
wipeId: 'w1',
zoneId: '1412203045',
event: 'Helicopter',
x: 812,
z: -340.5,
radius: 100,
shape: 'sphere',
durationSec: 600,
...over,
})
test('the public read the zone, its grid and how long it lasts, and never where it is', () => {
const out = dynamicPvp.shapeFor(created(), { staff: false, geometry: GEOMETRY })
assert.strictEqual(out.event, 'Helicopter')
assert.strictEqual(out.grid, gridOf(GEOMETRY, 812, -340.5))
assert.strictEqual(out.radius, 100)
assert.strictEqual(out.durationSec, 600)
for (const hidden of ['x', 'z', 'zoneId']) assert.strictEqual(hidden in out, false, `${hidden} is staff's`)
})
test('staff read the position and the zone id too', () => {
const out = dynamicPvp.shapeFor(created(), { staff: true, geometry: GEOMETRY })
assert.strictEqual(out.x, 812)
assert.strictEqual(out.z, -340.5)
assert.strictEqual(out.zoneId, '1412203045')
})
test("a monument's or an operator's own event name is staff's alone", () => {
for (const event of ['Airfield', 'My secret arena']) {
assert.strictEqual('event' in dynamicPvp.shapeFor(created({ event }), { staff: false, geometry: GEOMETRY }), false)
assert.strictEqual(dynamicPvp.shapeFor(created({ event }), { staff: true, geometry: GEOMETRY }).event, event)
}
for (const event of dynamicPvp.GENERAL_EVENTS) assert.ok(dynamicPvp.isGeneral(event))
})
test('without a map geometry a line has no grid, and off the map neither', () => {
assert.strictEqual('grid' in dynamicPvp.shapeFor(created(), { geometry: null }), false)
assert.strictEqual('grid' in dynamicPvp.shapeFor(created({ x: 9000 }), { geometry: GEOMETRY }), false)
})
test('a frame of another kind is passed back untouched', () => {
const other = { kind: 'world.event', event: 'heli.downed', x: 1, z: 2 }
assert.strictEqual(dynamicPvp.shapeFor(other, { staff: false }), other)
})
test('the feed reads a zone line for its viewer, with the map’s grid', async () => {
const stored = created({ event: 'Airfield' })
withRows((sql) => {
if (sql.includes('FROM rust_events')) return [{ id: 11, kind: 'pvp.zone.created', t: NOW, wipeId: 'w1', steamId: null, raw: JSON.stringify(stored) }]
if (sql.includes('FROM rust_servers')) return [{ id: 'main', name: 'Main', enabled: 1 }]
if (sql.includes('FROM rust_map_images')) return [{ serverId: 'main', ...GEOMETRY }]
return []
})
const events = require('../model/events/events.model')
const [signedOut] = await events.recent({ serverId: 'main' })
assert.strictEqual(signedOut.frame.grid, gridOf(GEOMETRY, 812, -340.5))
assert.strictEqual('x' in signedOut.frame, false)
assert.strictEqual('event' in signedOut.frame, false)
const [staff] = await events.recent({ serverId: 'main', staff: true })
assert.strictEqual(staff.frame.x, 812)
assert.strictEqual(staff.frame.event, 'Airfield')
})