SOURCE / PINNED RELEASE
Made of little things.
Black Site Map Editor
- Release
- 32a57733b7e9…
- Author-recorded commit
- 3f54890f542f…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/black-site-map-editor
Archive hash verified: 7a4b1abbac7db5b0…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { createServer } from 'vite';
import {
mapResourceUrls,
mapEventTemplate,
parseMapEvent,
readMapBytes,
} from '../src/vendor/map-event.mjs';
import { createHash } from 'node:crypto';
const server = await createServer({
configFile: false,
optimizeDeps: { noDiscovery: true, entries: [] },
server: { middlewareMode: true },
appType: 'custom',
});
const model = await server.ssrLoadModule('/src/model.ts');
const { renderMap } = await server.ssrLoadModule('/src/map-renderer.ts');
await server.close();
const {
createMap,
place,
moveSelection,
deleteSelection,
validateMap,
validatePlayable,
buildGrid,
resizeMap,
MapHistory,
builtInTemplates,
hitTest,
} = model;
const golden = JSON.parse(
await readFile(new URL('./fixtures/authored-game-hashes.json', import.meta.url), 'utf8'),
);
const hash = (value) => createHash('sha256').update(value).digest('hex');
let liveBuildLevel;
try {
const source = await readFile(
new URL('../../black-site-map-schema/src/game/data.js', import.meta.url),
'utf8',
);
liveBuildLevel = new Function('MAPS', `${source}\nreturn {buildLevel};`)([]).buildLevel;
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
test('new maps and every authored template validate and have reachable extraction', () => {
for (const map of [createMap(), ...builtInTemplates]) {
assert.deepEqual(validateMap(map), []);
assert.deepEqual(validatePlayable(map), []);
assert.equal(map.generate, undefined);
}
});
test('authored grid, floors and relocated units match committed game golden hashes', async () => {
assert.equal(golden.provenance.license, 'CC-BY-NC-SA-4.0');
assert.deepEqual(
Object.keys(golden.templates).sort(),
builtInTemplates.map((m) => m.id).sort(),
);
for (const map of builtInTemplates) {
const fixture = golden.templates[map.id];
const source = await readFile(
new URL(`../src/vendor/${map.id}.json`, import.meta.url),
'utf8',
);
assert.equal(
hash(source),
fixture.templateSha256,
`${map.id}: source template changed; refresh fixture from the actual game`,
);
assert.equal(
hash(JSON.stringify(map)),
fixture.authoredMapSha256,
`${map.id}: authored template changed`,
);
const editor = buildGrid(map);
for (const key of golden.provenance.fields)
assert.equal(
hash(JSON.stringify(editor[key])),
fixture.fields[key],
`${map.id}: ${key}`,
);
}
});
test(
'optional adjacent game checkout confirms live builder parity',
{
skip: !liveBuildLevel
? 'Adjacent black-site-map-schema checkout unavailable; committed golden parity still runs'
: false,
},
() => {
for (const map of builtInTemplates) {
const game = liveBuildLevel(map),
editor = buildGrid(map);
for (const key of golden.provenance.fields)
assert.deepEqual(editor[key], game[key], `${map.id}: ${key}`);
}
},
);
test('building editing keeps doors and interior walls aligned during movement', () => {
let { map, selection } = place(createMap(), 'buildings', [6, 5], [12, 12]);
map = place(map, 'doors', [12, 8], undefined, { buildingIndex: 0 }).map;
map = place(map, 'interiorWalls', [8, 6], [8, 9], { buildingIndex: 0 }).map;
map = moveSelection(map, selection, 3, 2);
assert.deepEqual(map.buildings[0].r, [9, 7, 15, 14]);
assert.deepEqual(map.buildings[0].doors[1], [15, 10]);
assert.deepEqual(map.buildings[0].walls[0], [11, 8, 11, 11]);
assert.deepEqual(hitTest(map, 15, 10, 'doors'), { layer: 'doors', index: 1, buildingIndex: 0 });
assert.throws(
() => place(map, 'doors', [10, 10], undefined, { buildingIndex: 0 }),
/outer wall/,
);
assert.equal(deleteSelection(map, selection).buildings.length, 0);
});
test('geometry edits refuse invalid clipping and history restores complete document', () => {
const initial = createMap(),
h = new MapHistory(initial);
const next = place(initial, 'tanks', [10, 10]).map;
h.commit(next);
assert.equal(h.canUndo, true);
assert.deepEqual(h.undo(), initial);
assert.deepEqual(h.redo(), next);
assert.throws(() => resizeMap(next, 16, 16), /clip/);
assert.throws(() => moveSelection(next, { layer: 'tanks', index: 0 }, 30, 0), /outside/);
assert.deepEqual(initial.tanks, undefined);
});
test('closed compound rejects unreachable extraction after game-compatible nudge', () => {
const map = createMap();
map.walls = [[18, 0, 18, 29]];
assert.match(validatePlayable(map)[0], /no walkable route/);
});
test('publication bytes roundtrip hash and schema, with resource URL regression fixed', async () => {
const map = createMap();
const bytes = new TextEncoder().encode(JSON.stringify(map));
const sha256 = createHash('sha256').update(bytes).digest('hex');
const event = {
...mapEventTemplate(map, {
sha256,
size: bytes.length,
url: `https://blossom.example/${sha256}.json`,
}),
pubkey: 'a'.repeat(64),
id: 'b'.repeat(64),
};
const parsed = parseMapEvent(event);
assert.equal(parsed.ok, true);
assert.deepEqual(mapResourceUrls(parsed.map), [
`blossom:${sha256}`,
`https://blossom.example/${sha256}.json`,
]);
const digest = async (b) => createHash('sha256').update(b).digest('hex');
assert.deepEqual(await readMapBytes(bytes, parsed.map, digest), { ok: true, map });
const bad = bytes.slice();
bad[2] ^= 1;
assert.equal((await readMapBytes(bad, parsed.map, digest)).ok, false);
});
test('units with no open tile within the game relocation radius are rejected', () => {
const map = createMap();
map.enemies = [[15, 15]];
map.walls = [[9, 9, 21, 21]];
assert.match(validatePlayable(map)[0], /enemies\[0\].*blocked/);
map.enemies = [];
map.caches = [[15, 15]];
assert.match(validatePlayable(map)[0], /caches\[0\].*blocked/);
});
test('renderer limits tile work to the visible canvas and later cover masks container colors', () => {
const operations = [];
const ctx = new Proxy(
{
canvas: { width: 40, height: 40 },
getTransform: () => ({ a: 1, d: 1, e: 0, f: 0 }),
fillStyle: '',
fillRect(...r) {
operations.push({ color: this.fillStyle, r });
},
},
{
get(target, key) {
return key in target ? target[key] : () => {};
},
},
);
const map = createMap();
map.size = [256, 256];
map.containers = [[1, 1, 2, 2, '#abcdef']];
map.crates = [[1, 1]];
renderMap(ctx, map, { tileSize: 20, grid: buildGrid(map), showGrid: false });
assert.ok(operations.length < 50, 'a huge map paints only visible tiles');
const container = operations.findIndex((o) => o.color === '#abcdef');
const crate = operations.findIndex(
(o) => o.color === '#786943' && o.r[0] === 20 && o.r[1] === 20,
);
assert.ok(container >= 0 && crate > container, 'final cover paints over the container base');
});
