Four readings of generated output reached the same verdict, and the last one answered 'would you run this tomorrow' with No. Three renderer passes each fixed what the previous review named and each introduced a new fault: adding resolution blocks made it menu-like, and subtracting blocks buried the clue and the skip guidance. The density is not a rendering bug. Everything on the page is a vice at the table and a virtue at the desk. So one working file, two surfaces. The worksheet carries everything, labelled, with reroll commands beside each component and explicit prompts where only a human can write. The play sheet carries the GM's own prose, the leverage point, the decision on one line and the numbers — 61 lines against the worksheet's 137. The JSON is the document. An authored layer holds what the GM writes and the generator never touches it, so a reroll cannot destroy prose. Lock what is good, reroll what is not, write the read-aloud, export. Two bugs found while building it, both in reroll: the exclusion set omitted the area's own current value so a reroll could hand back what was already there, and when every option was in use — six areas against a six-item pool — the fallback did the same. A reroll that changes nothing looks broken. DELVE does not write the read-aloud and does not write the climax. It gets a GM to a strong first draft in a minute instead of an evening, then gets out of the way. Tests 28 -> 38. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
57 lines
2.5 KiB
JavaScript
Executable File
57 lines
2.5 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 pack = JSON.parse(readFileSync(join(here, 'content', `${args.theme ?? 'barrow'}.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);
|