const { test } = require('node:test') const assert = require('node:assert/strict') const { aggregateCreatures, displayName, encodePoint } = require('../scripts/buildSpawnAtlas') const { readPoint } = require('../scripts/importSpawnAtlas') // buildSpawnAtlas is the fs/CLI shell, but its aggregation and its artifact // encoding are pure and worth covering — the encoding especially, because it is // mirrored by hand in importSpawnAtlas.readPoint() and a drift between the two // would corrupt every imported point silently rather than failing loudly. // ── aggregateCreatures ───────────────────────────────────────────────────── const POINTS = [ { facet: 'Felucca', types: [ { type: 'Lizardman', max: 3 }, { type: 'Orc', max: 1 }, ], }, { facet: 'Felucca', types: [{ type: 'Lizardman', max: 2 }] }, { facet: 'Trammel', types: [{ type: 'lizardman', max: 5 }] }, ] test('aggregateCreatures: sums each type’s own max across points', () => { const creatures = aggregateCreatures(POINTS) const lizardman = creatures.find((c) => c.slug === 'lizardman') // 3 + 2 + 5 — how many exist in the world at once. assert.equal(lizardman.total, 10) assert.equal(lizardman.points, 3) }) test('aggregateCreatures: counts points per facet', () => { const lizardman = aggregateCreatures(POINTS).find((c) => c.slug === 'lizardman') assert.deepEqual(lizardman.facets, { Felucca: 2, Trammel: 1 }) }) test('aggregateCreatures: differing case collapses into one creature', () => { // "Lizardman" and "lizardman" are the same creature; the shard's spawn files // are not consistent about case. const creatures = aggregateCreatures(POINTS) assert.equal(creatures.filter((c) => c.slug === 'lizardman').length, 1) }) test('aggregateCreatures: sorted by slug, and internal state is not leaked', () => { const creatures = aggregateCreatures(POINTS) assert.deepEqual( creatures.map((c) => c.slug), ['lizardman', 'orc'], ) // The spelling tally is a build detail and must not reach the artifact. assert.equal(Object.hasOwn(creatures[0], 'spellings'), false) }) test('aggregateCreatures: empty input yields no creatures', () => { assert.deepEqual(aggregateCreatures([]), []) }) // ── displayName ──────────────────────────────────────────────────────────── test('displayName: the most common spelling wins', () => { assert.equal(displayName(new Map([['lizardman', 9], ['Lizardman', 2]])), 'lizardman') assert.equal(displayName(new Map([['Lizardman', 9], ['lizardman', 2]])), 'Lizardman') }) test('displayName: ties break toward the capitalised spelling', () => { // Otherwise the display name depends on file read order, which would produce // spurious diffs in the committed artifact on an unrelated rebuild. assert.equal(displayName(new Map([['lizardman', 5], ['Lizardman', 5]])), 'Lizardman') assert.equal(displayName(new Map([['acidslug', 1], ['AcidSlug', 1]])), 'AcidSlug') }) test('displayName: fully tied spellings fall back to alphabetical, not input order', () => { const forward = displayName(new Map([['abc', 1], ['abd', 1]])) const reverse = displayName(new Map([['abd', 1], ['abc', 1]])) assert.equal(forward, reverse) }) // ── encodePoint / readPoint round trip ───────────────────────────────────── const FULL_POINT = { facet: 'Trammel', name: 'CovetousSpawner26', x: 5412, y: 1970, width: 10, height: 10, range: 5, maxCount: 3, minDelay: 5, maxDelay: 10, todStart: 4, todEnd: 8, todMode: 1, region: 'Covetous', landmark: null, types: [ { type: 'Lizardman', max: 3 }, { type: 'Orc', max: 1 }, ], } test('encodePoint: drops facet and tuple-encodes types', () => { const encoded = encodePoint(FULL_POINT) assert.equal(Object.hasOwn(encoded, 'facet'), false) assert.deepEqual(encoded.types, [ ['Lizardman', 3], ['Orc', 1], ]) }) test('round trip: encodePoint → JSON → readPoint restores every field', () => { // The artifact goes through JSON on disk, so round-trip through it here too. const wire = JSON.parse(JSON.stringify(encodePoint(FULL_POINT))) const decoded = readPoint(wire, 'Trammel') for (const key of Object.keys(FULL_POINT)) { if (key === 'types') continue assert.deepEqual(decoded[key], FULL_POINT[key], `field "${key}" survived the round trip`) } assert.deepEqual(decoded.types, FULL_POINT.types) }) test('round trip: omitted defaults come back as zeros, not undefined', () => { // The build omits width/height/range/minDelay/maxDelay/tod* when they are 0, // which is the majority of spawners. They must decode to 0 — a NULL would // violate the NOT NULL columns. const sparse = { facet: 'Felucca', name: 'Simple', x: 100, y: 200, width: 0, height: 0, range: 0, maxCount: 1, minDelay: 0, maxDelay: 0, todStart: 0, todEnd: 0, todMode: 0, region: null, landmark: null, types: [{ type: 'Orc', max: 1 }], } const encoded = JSON.parse(JSON.stringify(encodePoint(sparse))) // Precondition: the build really did omit them. assert.equal(Object.hasOwn(encoded, 'width'), false) assert.equal(Object.hasOwn(encoded, 'todMode'), false) const decoded = readPoint(encoded, 'Felucca') for (const key of ['width', 'height', 'range', 'minDelay', 'maxDelay', 'todStart', 'todEnd', 'todMode']) { assert.equal(decoded[key], 0, `${key} decodes to 0`) } }) test('round trip: label is recomputed, not stored', () => { const encoded = encodePoint(FULL_POINT) // Precondition: the build does not write it. assert.equal(Object.hasOwn(encoded, 'label'), false) assert.equal(readPoint(encoded, 'Trammel').label, 'Covetous') assert.equal(readPoint({ ...encoded, region: undefined, landmark: 'Britain' }, 'T').label, 'Britain') assert.equal( readPoint({ ...encoded, region: undefined, landmark: undefined }, 'T').label, 'Wilderness', ) }) test('readPoint: takes its facet from the shard file, not the record', () => { const decoded = readPoint(encodePoint(FULL_POINT), 'Felucca') assert.equal(decoded.facet, 'Felucca') }) test('readPoint: tolerates already-decoded object types', () => { // Defensive: an artifact written before tuple encoding still imports. const decoded = readPoint({ x: 1, y: 2, types: [{ type: 'Orc', max: 2 }] }, 'Felucca') assert.deepEqual(decoded.types, [{ type: 'Orc', max: 2 }]) }) test('readPoint: a record with no types decodes to an empty list', () => { assert.deepEqual(readPoint({ x: 1, y: 2 }, 'Felucca').types, []) })