Groundwork for many themes, plus the first new one. A pack validator enforces every structural rule this project learned the hard way: situations that render as broken English, decisions that gate progress with no alternative, motifs too thin to fill six areas without repeating, rosters that do not say what harms them. Fifteen packs cannot be hand-checked; this checks them in a second. Themes are now discovered from content/index.json rather than hardcoded, so the CLI takes --theme and --themes, the Foundry dropdown fills itself, and packs load on demand instead of all at once. Castle: four appetites — to be feared, never to give ground, to be obeyed at once, the name to go on — over barrow geometry, since a keep is rooms and corridors. 168 kernels from its own claimants and accommodations. Fixed a bug the second theme exposed: the question the bottom problem asks was hardcoded against barrow's appetite ids, so every other theme fell through to a generic "Well?". It lives on the appetite in the pack now, and the validator requires it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.9 KiB
JavaScript
Executable File
64 lines
2.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* DELVE — an authoring system for VANITY delves.
|
|
*
|
|
* new node cli.mjs --new --seed=X --areas=6 --file=my-delve.json
|
|
* worksheet node cli.mjs --file=my-delve.json --worksheet
|
|
* reroll node cli.mjs --file=my-delve.json --reroll=3:situation
|
|
* lock node cli.mjs --file=my-delve.json --lock=3:decision
|
|
* write node cli.mjs --file=my-delve.json --write=3:readAloud --text="..."
|
|
* play node cli.mjs --file=my-delve.json --play
|
|
*
|
|
* The JSON file is the working document. Rerolling never touches written prose.
|
|
*/
|
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname, join } from 'path';
|
|
import { newWorkingFile, reroll, lock, unlock, setAuthored, outstanding } from './authoring.mjs';
|
|
import { renderWorksheet, renderPlay } from './render-authoring.mjs';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const args = Object.fromEntries(process.argv.slice(2).map(a => {
|
|
const [k, ...rest] = a.replace(/^--/, '').split('=');
|
|
const v = rest.join('=');
|
|
return [k, v === '' ? true : (/^\d+$/.test(v) ? Number(v) : v)];
|
|
}));
|
|
|
|
const index = JSON.parse(readFileSync(join(here, 'content', 'index.json'), 'utf8'));
|
|
if (args.themes) { console.log(index.themes.map(t => `${t.id.padEnd(10)} ${t.geometry.padEnd(8)} ${t.motifs} motifs`).join('\n')); process.exit(0); }
|
|
const themeId = args.theme ?? 'barrow';
|
|
if (!index.themes.some(t => t.id === themeId)) {
|
|
console.error(`unknown theme "${themeId}" — available: ${index.themes.map(t => t.id).join(', ')}`);
|
|
process.exit(1);
|
|
}
|
|
const pack = JSON.parse(readFileSync(join(here, 'content', `${themeId}.json`), 'utf8'));
|
|
const file = args.file;
|
|
const load = () => JSON.parse(readFileSync(file, 'utf8'));
|
|
const save = d => writeFileSync(file, JSON.stringify(d, null, 2));
|
|
|
|
let d;
|
|
if (args.new || !file || !existsSync(file)) {
|
|
d = newWorkingFile({ pack, ...args });
|
|
if (file) { save(d); console.error(`created ${file} — seed ${d.seed}`); }
|
|
} else {
|
|
d = load();
|
|
}
|
|
|
|
const target = spec => { const [i, c] = String(spec).split(':'); return [i === 'ending' ? 'ending' : Number(i), c]; };
|
|
|
|
if (args.reroll) { const [i, c] = target(args.reroll); reroll(d, i, c, pack); save(d); console.error(`rerolled ${c} in area ${i}`); }
|
|
if (args.lock) { const [i, c] = target(args.lock); lock(d, i, c); save(d); console.error(`locked ${c} in area ${i}`); }
|
|
if (args.unlock) { const [i, c] = target(args.unlock); unlock(d, i, c); save(d); console.error(`unlocked ${c} in area ${i}`); }
|
|
if (args.write) {
|
|
const [i, f] = target(args.write);
|
|
setAuthored(d, i, f, args.text ?? '');
|
|
save(d); console.error(`wrote ${f} for ${i}`);
|
|
}
|
|
|
|
const out = args.play ? renderPlay(d)
|
|
: args.json ? JSON.stringify(d, null, 2)
|
|
: args.todo ? outstanding(d).map(t => `[ ] ${t.where} — ${t.what}`).join('\n')
|
|
: renderWorksheet(d);
|
|
|
|
if (args.out) writeFileSync(args.out, out); else console.log(out);
|