Back to Black Site
SOURCE / PINNED RELEASE

Made of little things.

Black Site

Release
666bc7c74ebd…
Author-recorded commit
05e95af79676…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391

Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/campaign.test.mjs
// Campaigns (spec/campaign-format.md): format validation, events and pinned map stages.
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { CAMPAIGN_EVENT_KIND, campaignEventTemplate, parseCampaignEvent, stageMapFilter, stageMapRef } from '../spec/lib/campaign-event.mjs';
import { mapResourceUrls, readMapBytes } from '../spec/lib/map-event.mjs';
import { BUILTIN_MAP_IDS, DEFAULT_RULES, validateCampaign } from '../spec/lib/validate-campaign.mjs';

const root = dirname(dirname(fileURLToPath(import.meta.url)));
const readJson = async (path) => JSON.parse(await readFile(join(root, path), 'utf8'));
const original = await readJson('spec/examples/campaigns/black-site.json');
const borderRun = await readJson('spec/examples/campaigns/border-run.json');
const schema = await readJson('spec/schemas/black-site-campaign.schema.json');
const outpostBytes = await readFile(join(root, 'spec/examples/maps/outpost.json'));
const sha256Hex = async (buf) => createHash('sha256').update(buf).digest('hex');
const pubkey = 'a'.repeat(64);
const signed = (template) => ({ ...template, pubkey, id: 'b'.repeat(64), sig: 'c'.repeat(128) });

test('example campaigns are valid', () => {
  assert.deepEqual(validateCampaign(original), []);
  assert.deepEqual(validateCampaign(borderRun), []);
});

test('the built-in campaign example matches the game', async () => {
  const { campaign } = await readJson('src/game/maps/index.json');
  assert.deepEqual(original.stages.map((s) => s.builtin), campaign);
  assert.deepEqual(BUILTIN_MAP_IDS, campaign);
  assert.deepEqual(schema.$defs.stage.oneOf[0].properties.builtin.enum, campaign);
  assert.deepEqual(original.rules, DEFAULT_RULES);
});

test('the example stage pins the example map by its hash', async () => {
  assert.equal(borderRun.stages[1].x, await sha256Hex(outpostBytes));
});

test('validator reports readable errors', () => {
  const bad = {
    version: 1,
    name: 'X',
    extra: true,
    rules: { difficulty: 'nightmare', quitPenalty: -1 },
    stages: [{ builtin: 'moon' }, { address: '35858:abc:outpost', x: 'nope' }, {}],
  };
  assert.deepEqual(validateCampaign(bad).sort(), [
    'extra: unknown field',
    'rules.difficulty: must be one of easy, normal, hard',
    'rules.quitPenalty: must be an integer 0-100000',
    'stages[0].builtin: must be one of town, docks, desert, snow, refinery',
    'stages[1].address: must be 35858:<pubkey hex>:<map id>',
    'stages[1].x: must be the 64-char lowercase hex SHA-256 of the map JSON',
    'stages[2]: must have either "builtin" or "address" and "x"',
  ]);
  assert.deepEqual(validateCampaign({ ...original, stages: [] }), ['stages: must be a list of 1-50 stages']);
});

test('validator and schema accept the same top-level fields', () => {
  const unknown = validateCampaign(Object.fromEntries(Object.keys(schema.properties).map((k) => [k, undefined])))
    .filter((e) => e.endsWith('unknown field'));
  assert.deepEqual(unknown, []);
  assert.deepEqual(Object.keys(schema.$defs.rules.properties).sort(), ['carryInventory', 'completionBonus', 'difficulty', 'quitPenalty', 'restoreHealth']);
});

test('a campaign round-trips through its event', () => {
  const ev = campaignEventTemplate('border-run', borderRun, { hashtags: ['Hard'] });
  assert.equal(ev.kind, CAMPAIGN_EVENT_KIND);
  const tag = (n) => ev.tags.filter((t) => t[0] === n).map((t) => t.slice(1));
  assert.deepEqual(tag('d'), [['border-run']]);
  assert.deepEqual(tag('title'), [['BORDER RUN']]);
  assert.deepEqual(tag('version'), [['1']]);
  assert.deepEqual(tag('a'), [[borderRun.stages[1].address, 'wss://relay.example.com']]);
  assert.deepEqual(tag('t'), [['hard']]);

  const parsed = parseCampaignEvent(signed(ev));
  assert.equal(parsed.ok, true);
  const c = parsed.campaign;
  assert.equal(c.address, `${CAMPAIGN_EVENT_KIND}:${pubkey}:border-run`);
  assert.deepEqual(c.rules, { completionBonus: 5000, quitPenalty: 1000, carryInventory: true, restoreHealth: false, difficulty: 'hard' });
  assert.deepEqual(c.stages.map((s) => s.type), ['builtin', 'map', 'builtin']);
  assert.deepEqual(c.json, borderRun);
});

test('bad campaign events are rejected', () => {
  const ev = signed(campaignEventTemplate('border-run', borderRun));
  assert.deepEqual(parseCampaignEvent({ ...ev, kind: 35858 }).errors, [`kind must be ${CAMPAIGN_EVENT_KIND}`]);
  assert.deepEqual(parseCampaignEvent({ ...ev, content: '{' }).errors, ['content is not valid JSON']);
  assert.deepEqual(parseCampaignEvent({ ...ev, tags: ev.tags.map((t) => (t[0] === 'version' ? ['version', '2'] : t)) }).errors, ['campaign version 2 is newer than this game supports (1)']);
  assert.throws(() => campaignEventTemplate('Bad Id', borderRun), /campaign id/);
});

test('a published stage loads its pinned map version', async () => {
  const stage = parseCampaignEvent(signed(campaignEventTemplate('border-run', borderRun))).campaign.stages[1];
  const author = stage.pubkey;
  assert.deepEqual(stageMapFilter(stage), { kinds: [35858], authors: [author], '#d': ['outpost'] });
  // the map event only adds servers; its current x tag (a newer version) is ignored
  const ref = stageMapRef(stage, { servers: ['https://blossom.example.com'], sha256: 'f'.repeat(64) });
  assert.deepEqual(mapResourceUrls(ref), [`blossom:${stage.x}`, `https://blossom.example.com/${stage.x}.json`]);
  assert.equal((await readMapBytes(outpostBytes, ref, sha256Hex)).ok, true);
  const newer = Buffer.from(outpostBytes.toString('utf8').replace('OUTPOST', 'OUTPOST 2'));
  assert.deepEqual((await readMapBytes(newer, ref, sha256Hex)).errors, ['map JSON does not match the x tag hash']);
});