feat(rust): the live map draws monuments by label, minor labels hidden (PLAN_REDESIGNS §4, D196)
A switch per monument label, a fleet default and a per-server override, on Admin -> Rust visibility's live map card. Substations, caves, train tunnels, wells and the other minor labels start hidden; every other label is drawn, so a monument a game update adds appears. GET /map leaves hidden labels out of the answer, so the website and the app both lose them. No schema change: rows are map.marker.<label key> in rust_settings and rust_map_overrides. No wire change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
// a fetch that straddles a map change is not spliced
|
||||
// one fetch per server, and a plugin that predates the map is never asked
|
||||
// any number of viewers cost one ask of the game (D111)
|
||||
// D196: the minor labels start off, every other label on, and a hidden label
|
||||
// is not sent; a server's switch wins over the fleet's, label by label
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert')
|
||||
@@ -39,6 +41,7 @@ const visibilityDb = require('../model/visibility/visibility.db')
|
||||
function settings(t, { stored = {}, overrides = [], viewer = { level: 'public', userId: null }, presence = 'staff', links = [], mates = [] } = {}) {
|
||||
const saved = {
|
||||
getSetting: visibilityDb.getSetting,
|
||||
listSettings: visibilityDb.listSettings,
|
||||
getOverrides: mapDb.getOverrides,
|
||||
viewer: visibility.viewer,
|
||||
presenceFor: visibility.presenceFor,
|
||||
@@ -47,6 +50,8 @@ function settings(t, { stored = {}, overrides = [], viewer = { level: 'public',
|
||||
}
|
||||
const asked = { clans: 0 }
|
||||
visibilityDb.getSetting = async (key) => (key in stored ? stored[key] : null)
|
||||
visibilityDb.listSettings = async (prefix) =>
|
||||
Object.entries(stored).filter(([k]) => k.startsWith(prefix)).map(([setting, value]) => ({ setting, value }))
|
||||
mapDb.getOverrides = async () => overrides
|
||||
visibility.viewer = async () => viewer
|
||||
visibility.presenceFor = async () => presence
|
||||
@@ -57,6 +62,7 @@ function settings(t, { stored = {}, overrides = [], viewer = { level: 'public',
|
||||
}
|
||||
t.after(() => {
|
||||
visibilityDb.getSetting = saved.getSetting
|
||||
visibilityDb.listSettings = saved.listSettings
|
||||
mapDb.getOverrides = saved.getOverrides
|
||||
visibility.viewer = saved.viewer
|
||||
visibility.presenceFor = saved.presenceFor
|
||||
@@ -82,7 +88,7 @@ const LIVE = {
|
||||
|
||||
test('a fresh install shows the world and events to anybody, and positions to staff only', async (t) => {
|
||||
settings(t)
|
||||
assert.deepStrictEqual(await map.fleet(), { world: 'public', events: 'public', players: 'staff', bases: 'staff', mates: true })
|
||||
assert.deepStrictEqual(await map.fleet(), { world: 'public', events: 'public', players: 'staff', bases: 'staff', mates: true, markers: {} })
|
||||
|
||||
const acc = await map.access({}, 'main')
|
||||
assert.equal(acc.layers.world.visible, true)
|
||||
@@ -218,6 +224,118 @@ test('the admin write refuses a word it does not know, and writes nothing on a d
|
||||
])
|
||||
})
|
||||
|
||||
// ── Which monuments are drawn (PLAN_REDESIGNS §4, D196) ───────────────────
|
||||
|
||||
const MONUMENTS = [
|
||||
{ kind: 'power_sub_big_1', label: 'Substation', x: 1, z: 1 },
|
||||
{ kind: 'power_sub_small_2', label: 'Substation', x: 2, z: 2 },
|
||||
{ kind: 'ue_jungle_swamp_a', label: 'jungle swamp', x: 3, z: 3 },
|
||||
{ kind: 'fishing_village_b', label: 'Fishing Village', x: 4, z: 4 },
|
||||
{ kind: 'fishing_village_a', label: 'Large Fishing Village', x: 5, z: 5 },
|
||||
{ kind: 'powerplant_1', label: 'Power Plant', x: 6, z: 6 },
|
||||
{ kind: 'brand_new_thing', label: 'Something Facepunch Added', x: 7, z: 7 },
|
||||
]
|
||||
|
||||
const labelsOf = (list) => list.map((m) => m.label)
|
||||
|
||||
test('D196: a fresh install hides the minor labels and shows everything else, a new label included', () => {
|
||||
assert.deepStrictEqual(labelsOf(map.filterMonuments(MONUMENTS, {})), [
|
||||
'Large Fishing Village',
|
||||
'Power Plant',
|
||||
'Something Facepunch Added',
|
||||
])
|
||||
assert.equal(map.markerShown({}, 'JUNGLE Swamp'), false, 'labels match case- and space-insensitively')
|
||||
})
|
||||
|
||||
test('D196: the fleet turns a minor label on, and a server turns it off again for itself only', async (t) => {
|
||||
settings(t, {
|
||||
stored: { 'map.marker.substation': 'on', 'map.marker.power plant': 'off', 'map.marker.jungle swamp': 'maybe' },
|
||||
overrides: [{ setting: 'map.marker.substation', value: 'off' }],
|
||||
})
|
||||
const fleet = await map.fleet()
|
||||
assert.deepStrictEqual(fleet.markers, { substation: true, 'power plant': false }, 'a word that is neither on nor off follows the default')
|
||||
|
||||
const pve = await map.forServer('pve')
|
||||
assert.deepStrictEqual(pve.markers, { substation: false, 'power plant': false })
|
||||
assert.deepStrictEqual(labelsOf(map.filterMonuments(MONUMENTS, pve.markers)), ['Large Fishing Village', 'Something Facepunch Added'])
|
||||
assert.deepStrictEqual(labelsOf(map.filterMonuments(MONUMENTS, fleet.markers)), [
|
||||
'Substation',
|
||||
'Substation',
|
||||
'Large Fishing Village',
|
||||
'Something Facepunch Added',
|
||||
])
|
||||
})
|
||||
|
||||
test('D196: the viewer’s access carries the switches that apply to that server', async (t) => {
|
||||
settings(t, { overrides: [{ setting: 'map.marker.water well', value: 'on' }] })
|
||||
const acc = await map.access({}, 'main')
|
||||
assert.deepStrictEqual(acc.markers, { 'water well': true })
|
||||
})
|
||||
|
||||
test('D196: the admin sees each label once, with its count and a capitalised spelling', () => {
|
||||
const labels = map.markerLabels([...MONUMENTS, { kind: 'ue_jungle_swamp_b', label: 'Jungle Swamp', x: 8, z: 8 }])
|
||||
assert.deepStrictEqual(
|
||||
labels.map((l) => [l.key, l.label, l.count]),
|
||||
[
|
||||
['fishing village', 'Fishing Village', 1],
|
||||
['jungle swamp', 'Jungle Swamp', 2],
|
||||
['large fishing village', 'Large Fishing Village', 1],
|
||||
['power plant', 'Power Plant', 1],
|
||||
['something facepunch added', 'Something Facepunch Added', 1],
|
||||
['substation', 'Substation', 2],
|
||||
],
|
||||
)
|
||||
assert.deepStrictEqual(map.markerLabels(undefined), [])
|
||||
})
|
||||
|
||||
test('D196: the admin write stores a switch per label, clears with null, and refuses a bad key or word', async (t) => {
|
||||
settings(t)
|
||||
const writes = []
|
||||
const saved = { setSetting: visibilityDb.setSetting, clearSetting: visibilityDb.clearSetting, setOverride: mapDb.setOverride }
|
||||
visibilityDb.setSetting = async (...a) => writes.push(['fleet', ...a])
|
||||
visibilityDb.clearSetting = async (...a) => writes.push(['fleet-clear', ...a])
|
||||
mapDb.setOverride = async (...a) => writes.push(['server', ...a])
|
||||
t.after(() => Object.assign(visibilityDb, { setSetting: saved.setSetting, clearSetting: saved.clearSetting }) && Object.assign(mapDb, { setOverride: saved.setOverride }))
|
||||
|
||||
assert.equal((await map.update({ fleet: { markers: { Substation: true } } })).status, 400, 'keys are lower case')
|
||||
assert.equal((await map.update({ fleet: { markers: { substation: 'on' } } })).status, 400, 'values are booleans')
|
||||
assert.equal((await map.update({ fleet: { markers: ['substation'] } })).status, 400)
|
||||
assert.equal((await map.update({ fleet: { markers: { ['x'.repeat(map.MARKER_KEY_MAX + 1)]: true } } })).status, 400)
|
||||
assert.equal((await map.update({ servers: { pve: { markers: { substation: 'off' } } } })).status, 400)
|
||||
assert.equal(writes.length, 0)
|
||||
|
||||
const done = await map.update(
|
||||
{ fleet: { world: 'staff', markers: { substation: true, 'water well': null } }, servers: { pve: { markers: { substation: false, 'power plant': null } } } },
|
||||
{ id: 3 },
|
||||
)
|
||||
assert.equal(done.ok, true)
|
||||
assert.deepStrictEqual(writes, [
|
||||
['fleet', 'map.layer.world.audience', 'staff', 3],
|
||||
['fleet', 'map.marker.substation', 'on', 3],
|
||||
['fleet-clear', 'map.marker.water well'],
|
||||
['server', 'pve', 'map.marker.substation', 'off', 3],
|
||||
['server', 'pve', 'map.marker.power plant', null, 3],
|
||||
])
|
||||
assert.deepStrictEqual(done.changed, {
|
||||
fleet: { world: 'staff', markers: { substation: true, 'water well': 'default' } },
|
||||
servers: { pve: { markers: { substation: false, 'power plant': 'inherit' } } },
|
||||
})
|
||||
})
|
||||
|
||||
test('D196: the admin card lists the minor labels, and each server’s marker overrides', async (t) => {
|
||||
settings(t, { stored: { 'map.marker.ranch': 'on' } })
|
||||
const saved = { listOverrides: mapDb.listOverrides }
|
||||
mapDb.listOverrides = async () => [{ serverId: 'pve', setting: 'map.marker.substation', value: 'on' }]
|
||||
t.after(() => Object.assign(mapDb, saved))
|
||||
|
||||
const card = await map.describeSwitches([{ id: 'pve' }, { id: 'pvp' }])
|
||||
assert.ok(card.minorLabels.includes('Substation'))
|
||||
assert.deepStrictEqual(card.fleet.markers, { ranch: true })
|
||||
assert.deepStrictEqual(card.servers[0].overrides.markers, { substation: true })
|
||||
assert.deepStrictEqual(card.servers[0].effective.markers, { ranch: true, substation: true })
|
||||
assert.deepStrictEqual(card.servers[1].overrides.markers, {})
|
||||
})
|
||||
|
||||
test('derive: a map with no picture is sized like the Rust+ cache would be', () => {
|
||||
const row = map.derive('main', { mapKey: '4500.7.1', source: 'none', worldSize: 4500, oceanMargin: 500, gridCells: 30, gridCellSize: 150, monuments: [] })
|
||||
assert.equal(row.width, 3250)
|
||||
|
||||
Reference in New Issue
Block a user