Back to Black Site Map Editor
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.

tests/model.test.mjs
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');
});