diff --git a/DESIGN.md b/DESIGN.md index a380e6e..a1b92ab 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -38,11 +38,34 @@ GM-readable delve. Samples in [`samples/`](samples/). --- -## 2. What DELVE is +## 2. What DELVE is — an authoring system -The dungeon-scale layer over VANITY's Forge. It does not generate maps, monsters, encounters or -hoards — those exist and work. It generates **the fiction, the order, and the pressure**, and it -sequences the Forge without letting the Forge talk over it. +**This changed in draft 7, after four readings of generated output.** + +DELVE is the dungeon-scale layer over VANITY's Forge — it generates the fiction, the order and +the pressure, and sequences the Forge without letting it talk over DELVE. But it is **not** a tool +that hands a GM a delve to run. Four independent reviews converged: + +> *"The handwritten delve still tells the GM what matters faster."* +> *"Would you run this at a real table tomorrow, as-is?"* — **No.** + +Three renderer passes each fixed what the last review named and each introduced a new problem +(blocks → menu-like; subtraction → buried). The density is not a bug to render away: **everything +on the page is a vice at the table and a virtue at the desk.** + +So a delve has two surfaces and one working file: + +| | For | Carries | +|---|---|---| +| **Worksheet** | the desk | everything, labelled, reroll commands beside each component, explicit prompts where only a human can write | +| **Play sheet** | the table | the GM's own prose, the live leverage point, the decision on one line, the numbers. Nothing else | + +The JSON file is the document. An `authored` layer holds what the GM writes and the generator +never touches it, so **rerolling a component cannot destroy prose**. Lock what is good, reroll +what is not, write the read-aloud, export the play sheet. + +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, and then gets out of the way. --- diff --git a/core/authoring.mjs b/core/authoring.mjs new file mode 100644 index 0000000..9fdad03 --- /dev/null +++ b/core/authoring.mjs @@ -0,0 +1,150 @@ +/** + * DELVE as an authoring system. + * + * Four readings of generated output landed on the same verdict: the fiction and the structure are + * good, the table-readiness is not, and the honest product is a strong first draft a GM finishes. + * "The handwritten delve still tells the GM what matters faster." + * + * That reframes the density problem rather than solving it. Everything on the page is a vice at + * the table and a virtue at the desk. So a delve now has two surfaces: + * + * WORKSHEET — everything, labelled, with explicit prompts for what only a human can write. + * PLAY SHEET — only what survived, in the GM's own words, tight enough to scan. + * + * The delve JSON is the working file. It carries an `authored` layer that the generator never + * touches, so rerolling a component cannot destroy written prose. + */ + +import { Rng } from './rng.mjs'; +import { generateDelve } from './delve.mjs'; + +/** Fields a GM writes. Anything here, once written, outranks the generated text. */ +export const AUTHORED_FIELDS = ['readAloud', 'notes', 'nameOverride']; + +/** Components that can be individually rerolled. */ +export const REROLLABLE = ['situation', 'decision', 'temptation', 'feature', 'name']; + +/** A fresh working file: the generated delve plus an empty authored layer per area. */ +export function newWorkingFile(params) { + const delve = generateDelve(params); + delve.authored = { title: null, intro: null, areas: {} }; + for (const a of delve.areas) { + delve.authored.areas[a.index] = { readAloud: null, notes: null, nameOverride: null, locked: [] }; + } + delve.authored.ending = { readAloud: null, notes: null, locked: [] }; + return delve; +} + +const authoredFor = (d, i) => d.authored?.areas?.[i] ?? { locked: [] }; + +export const isLocked = (d, i, component) => (authoredFor(d, i).locked ?? []).includes(component); + +export function lock(d, i, component) { + const a = authoredFor(d, i); + if (!a.locked.includes(component)) a.locked.push(component); + return d; +} + +export function unlock(d, i, component) { + const a = authoredFor(d, i); + a.locked = (a.locked ?? []).filter(c => c !== component); + return d; +} + +export function setAuthored(d, i, field, value) { + if (!AUTHORED_FIELDS.includes(field)) throw new Error(`not an authored field: ${field}`); + if (i === 'ending') { d.authored.ending[field] = value; return d; } + authoredFor(d, i)[field] = value; + return d; +} + +/** + * Reroll one component of one area, keeping everything else — including anything written. + * + * Regeneration draws from a salted seed so a reroll gives something genuinely different rather + * than the same draw again, and the salt is recorded so the file stays reproducible. + */ +export function reroll(d, i, component, pack) { + if (!REROLLABLE.includes(component)) throw new Error(`not rerollable: ${component}`); + if (isLocked(d, i, component)) return d; + + d.rerolls = d.rerolls ?? {}; + const key = `${i}:${component}`; + d.rerolls[key] = (d.rerolls[key] ?? 0) + 1; + + const salt = `${d.seed}::reroll::${key}::${d.rerolls[key]}`; + const rng = new Rng(salt); + const motif = pack.motifs?.[d.skeleton.motif.id] ?? {}; + const area = d.areas.find(a => a.index === i); + if (!area) throw new Error(`no area ${i}`); + + /** + * Draw fresh, avoiding what the rest of the delve uses. When every option is already in play — + * six areas against a six-item pool — prefer duplicating another area over handing back what is + * already here. A reroll that changes nothing looks broken. + */ + const choose = (all, taken, keyOf, current) => { + const fresh = all.filter(x => !taken.has(keyOf(x))); + if (fresh.length) return rng.pick(fresh); + const notCurrent = all.filter(x => keyOf(x) !== current); + return notCurrent.length ? rng.pick(notCurrent) : null; + }; + + // Draw fresh, avoiding what the rest of the delve is already using. + const used = (get, current) => { + const taken = new Set(d.areas.filter(x => x.index !== i).map(get).filter(Boolean)); + if (current) taken.add(current); // never hand back what is already there + return taken; + }; + switch (component) { + case 'situation': { + const taken = used(a => a.situation?.occupant + a.situation?.doing, area.situation ? area.situation.occupant + area.situation.doing : null); + const pick = choose(motif.situations ?? [], taken, x => x.occupant + x.doing, + area.situation ? area.situation.occupant + area.situation.doing : null); + if (pick) area.situation = { occupant: pick.occupant, doing: pick.doing, onArrival: pick.onArrival, because: pick.because, offer: pick.offer }; + break; + } + case 'decision': { + const taken = used(a => a.decision?.cue, area.decision?.cue); + const pick = choose(motif.decisions ?? [], taken, x => x.cue, area.decision?.cue); + if (pick) area.decision = pick; + break; + } + case 'temptation': { + if (!area.hoard) break; + const taken = used(a => a.temptation?.id, area.temptation?.id); + const pick = choose(motif.temptations ?? [], taken, x => x.id, area.temptation?.id); + if (pick) area.temptation = pick; + break; + } + case 'feature': { + const taken = used(a => a.cueFragments?.[1], area.cueFragments?.[1]); + const pick = choose(motif.features ?? [], taken, x => x, area.cueFragments?.[1]); + if (pick) area.cueFragments = [area.cueFragments[0], pick].filter(Boolean); + break; + } + case 'name': { + const names = pack.areaNames?.[area.role] ?? []; + const taken = used(a => a.name, area.name); + const pick = choose(names, taken, x => x, area.name); + if (pick) area.name = pick; + break; + } + } + return d; +} + +/** What still needs a human. This is the worksheet's to-do list and the play sheet's gate. */ +export function outstanding(d) { + const todo = []; + if (!d.authored?.intro) todo.push({ where: 'intro', what: 'an opening paragraph for the delve' }); + for (const a of d.areas) { + const au = authoredFor(d, a.index); + if (!au.readAloud) todo.push({ where: `area ${a.index}`, what: 'read-aloud text' }); + } + if (!d.authored?.ending?.readAloud) todo.push({ where: 'ending', what: 'read-aloud text' }); + if (d.ending.authored && !d.authored?.ending?.notes) todo.push({ where: 'ending', what: 'the authored climax — DELVE deliberately leaves this to you' }); + return todo; +} + +export const readyToPlay = d => outstanding(d).length === 0; diff --git a/core/cli.mjs b/core/cli.mjs index 54d381c..dc16d46 100755 --- a/core/cli.mjs +++ b/core/cli.mjs @@ -1,20 +1,56 @@ #!/usr/bin/env node -/** DELVE CLI — map-free by necessity: forgeStage needs browser APIs (see spike report §3A). */ -import { readFileSync, writeFileSync } from 'fs'; +/** + * 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 { generateDelve } from './delve.mjs'; -import { renderMarkdown } from './render.mjs'; +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, v = true] = a.replace(/^--/, '').split('='); - return [k, v === true ? true : (/^\d+$/.test(v) ? Number(v) : v)]; + const [k, ...rest] = a.replace(/^--/, '').split('='); + const v = rest.join('='); + return [k, v === '' ? true : (/^\d+$/.test(v) ? Number(v) : v)]; })); -const theme = args.theme ?? 'barrow'; -const pack = JSON.parse(readFileSync(join(here, 'content', `${theme}.json`), 'utf8')); -const delve = generateDelve({ pack, ...args }); +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)); -if (args.json) { const out = JSON.stringify(delve, null, 2); args.out ? writeFileSync(args.out, out) : console.log(out); } -else { const md = renderMarkdown(delve); args.out ? writeFileSync(args.out, md) : console.log(md); } +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); diff --git a/core/render-authoring.mjs b/core/render-authoring.mjs new file mode 100644 index 0000000..6cb97bb --- /dev/null +++ b/core/render-authoring.mjs @@ -0,0 +1,163 @@ +/** + * Two renders of one delve. + * + * WORKSHEET is for the desk: everything the generator knows, labelled, with the reroll commands + * beside each component and an explicit prompt where only a human can write. + * + * PLAY is for the table: the GM's own prose, the live leverage point, the decision on one line, + * the numbers. Nothing else. Four reviews said the same thing — "the handwritten delve tells the + * GM what matters faster" — and the only way to be fast is to carry less. + */ +import { outstanding, isLocked, REROLLABLE } from './authoring.mjs'; + +const cap = s => (s ? s[0].toUpperCase() + s.slice(1) : s); +const cost = c => { + if (!c) return 'free'; + const b = []; + if (c.bane) b.push(`+${c.bane} Bane${c.bane > 1 ? 's' : ''}`); + if (c.vice) b.push(`the Vice ${c.vice}`); + return b.length ? b.join(', ') : 'free'; +}; +const au = (d, i) => d.authored?.areas?.[i] ?? {}; + +/* ------------------------------------------------------------- worksheet */ + +export function renderWorksheet(d) { + const L = []; + const sk = d.skeleton, ap = sk.appeasement; + const todo = outstanding(d); + + L.push(`# WORKSHEET — ${d.authored?.title ?? sk.placeName}`); + L.push(''); + L.push(`\`seed ${d.seed} · ${d.params.theme} · ${d.params.areas} areas · depth ${d.params.depth} · generator ${d.generatorVersion}\``); + L.push(''); + L.push(todo.length + ? `**${todo.length} things still need you.** This is a first draft, not a play document. Finish it, then export the play sheet.` + : `**Finished.** Export the play sheet.`); + L.push(''); + if (todo.length) { for (const t of todo) L.push(`- [ ] **${t.where}** — ${t.what}`); L.push(''); } + L.push('---'); + L.push(''); + L.push('## The kernel'); + L.push(''); + L.push(`| | |`); + L.push(`|---|---|`); + L.push(`| **Transgression** | ${sk.knot.transgression} |`); + L.push(`| **Bottom problem** | ${sk.bottomProblem.label} — ${sk.bottomProblem.failureState} |`); + L.push(`| **Prize** | ${sk.prize.label} — ${sk.prize.function} |`); + L.push(`| **Motif / danger** | ${sk.motif.id} · ${sk.motif.danger} — ${sk.motif.dangerLine} |`); + L.push(`| **Appeasement** | ${ap.move} \`[${ap.attribute} ${ap.successes}]\` — +${ap.gain.vanity} Vanity, +${ap.cost.bane} Bane, repeatable |`); + L.push(`| **It asks** | ${d.ending.question} |`); + L.push(''); + L.push(`**WRITE ▸ an opening paragraph.** ${d.authored?.intro ? '✔ written' : '_(nothing yet — set `authored.intro`)_'}`); + if (d.authored?.intro) { L.push(''); L.push(`> ${d.authored.intro}`); } + L.push(''); + L.push('---'); + L.push(''); + + for (const a of d.areas) { + const w = au(d, a.index); + const locks = (w.locked ?? []); + const rerollable = REROLLABLE.filter(c => !locks.includes(c) && (c !== 'temptation' || a.hoard)); + L.push(`## Area ${a.index} — ${w.nameOverride ?? a.name}`); + L.push(`\`${a.role} · ${a.facet}${a.encounter ? ` · ${a.encounter.heat}` : ''}${a.hoard ? ` · ${a.hoard}` : ''}\``); + L.push(`*reroll:* ${rerollable.map(c => `\`--reroll=${a.index}:${c}\``).join(' ')}${locks.length ? ` · *locked:* ${locks.join(', ')}` : ''}`); + L.push(''); + L.push(`**IMAGE** ${a.cueFragments.join(' · ')}`); + if (a.situation) { + L.push(`**SITUATION** ${cap(a.situation.occupant)} — ${a.situation.doing}. On arrival, ${a.situation.onArrival}.`); + L.push(`**WHY** ${cap(a.situation.because)}. **OFFERS** ${a.situation.offer}.`); + } + L.push(`**DECISION** ${cap(a.decision.cue)} — *${a.decision.cost}*`); + const rv = a.decision.resolve ?? {}; + if (rv.roll) L.push(` · \`[${rv.roll}]\` → ${rv.success}`); + if (rv.failure) L.push(` · **miss** → ${rv.failure}`); + if (rv.orElse) L.push(` · **or** ${rv.orElse}`); + if (a.encounter?.roster) { + const R = a.encounter.roster; + L.push(`**ENCOUNTER** ${R.line} — ${R.foes.map(f => `${f.n}× ${f.name} atk${f.atk}/def${f.def}/G${f.grit}`).join(', ')}`); + L.push(` · harmed by ${R.harmedBy} · ${R.avoid}`); + } + if (a.temptation) { + const t = a.temptation; + L.push(`**TEMPTATION** ${cap(t.id)} — ${t.cue}: ${t.benefit}`); + L.push(` · take ${cost(t.acceptanceCost)} · use ${cost(t.useCost)} · standing: ${t.standingDrawback}`); + } + L.push(`**PRESSURE** trigger ${a.trigger}${a.baneBeat ? ` · bane beat ${a.baneBeat}` : ''} · fallback ${a.fallback.route}`); + L.push(''); + L.push(`**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above.`); + L.push(w.readAloud ? `> ${w.readAloud}` : `> _(nothing yet)_`); + if (w.notes) { L.push(''); L.push(`**Notes.** ${w.notes}`); } + L.push(''); + L.push('---'); + L.push(''); + } + + const we = d.authored?.ending ?? {}; + L.push(`## Ending — ${d.ending.name}`); + L.push(d.ending.authored ? '`AUTHORED SLOT — DELVE stops here on purpose`' : '`generated`'); + L.push(''); + L.push(`**IMAGE** ${d.ending.cueFragments.join(' · ')}`); + L.push(`**IT WANTS** ${sk.bottomProblem.wantNow}. **IT ASKS** ${d.ending.question}`); + L.push(`**APPEASED EVEN ONCE** it listens, and will trade ${sk.prize.label}.`); + L.push(''); + L.push('**WRITE ▸ read-aloud**'); + L.push(we.readAloud ? `> ${we.readAloud}` : `> _(nothing yet)_`); + L.push(''); + L.push('**WRITE ▸ the climax.** How it goes, what harms it, what it will take instead of a fight.'); + L.push(we.notes ? we.notes : `_(nothing yet — this is the seam; DELVE will not write it for you)_`); + L.push(''); + return L.join('\n'); +} + +/* ------------------------------------------------------------ play sheet */ + +export function renderPlay(d) { + const L = []; + const sk = d.skeleton, ap = sk.appeasement; + const todo = outstanding(d); + + L.push(`# ${d.authored?.title ?? sk.placeName}`); + L.push(''); + if (todo.length) { + L.push(`> ⚠ **${todo.length} unfinished** — ${todo.slice(0, 3).map(t => t.where).join(', ')}${todo.length > 3 ? '…' : ''}.`); + L.push('> Fragments are shown where nothing was written. Run the worksheet first.'); + L.push(''); + } + if (d.authored?.intro) { L.push(`> ${d.authored.intro}`); L.push(''); } + L.push(`**Appease:** ${ap.move} \`[${ap.attribute} ${ap.successes}]\` — +1 Vanity now, +1 Bane later. Repeatable, every area.`); + L.push(`**Clock:** 1d6 on a trigger. **1 — something comes.** **Tab:** ${d.pressure.target} by the end.`); + L.push(`**GM truth:** ${sk.knot.transgression} ${sk.bottomProblem.label} ${sk.bottomProblem.failureState}.`); + L.push(''); + L.push('---'); + L.push(''); + + for (const a of d.areas) { + const w = au(d, a.index); + const rv = a.decision.resolve ?? {}; + L.push(`### ${a.index} · ${w.nameOverride ?? a.name}`); + L.push(''); + L.push(w.readAloud ? `> ${w.readAloud}` : `> *(unwritten)* ${a.cueFragments.join('. ')}.`); + L.push(''); + if (a.situation) L.push(`**Here:** ${cap(a.situation.occupant)}, ${a.situation.doing} — ${a.situation.onArrival}. **They can:** ${a.situation.offer}.`); + L.push(`**${cap(a.decision.cue)}** — ${rv.roll ? `\`[${rv.roll}]\` ${rv.success}` : ''}${rv.failure ? ` · **miss** ${rv.failure}` : ''}${rv.orElse ? ` · **or** ${rv.orElse}` : ''}`); + if (a.encounter?.roster) { + const R = a.encounter.roster; + L.push(`**${cap(a.encounter.heat)}:** ${R.foes.map(f => `${f.n}× ${f.name} \`${f.atk}/${f.def}/${f.grit}\``).join(' · ')} — harmed by ${R.harmedBy}.`); + } + if (a.temptation) L.push(`**${cap(a.temptation.id)}:** ${a.temptation.benefit}. *Use: ${cost(a.temptation.useCost)}. ${a.temptation.standingDrawback}.*`); + if (w.notes) L.push(`**Note:** ${w.notes}`); + L.push(`\`${a.trigger}${a.baneBeat ? ` · ${a.baneBeat}` : ''} · skipped → move the image on\``); + L.push(''); + } + + const we = d.authored?.ending ?? {}; + L.push(`### ${d.ending.name}`); + L.push(''); + L.push(we.readAloud ? `> ${we.readAloud}` : `> *(unwritten)* ${d.ending.cueFragments.join('. ')}.`); + L.push(''); + L.push(`**It asks:** ${d.ending.question}`); + L.push(we.notes ? we.notes : `*(the climax is unwritten — this is the authored slot)*`); + L.push(''); + return L.join('\n'); +} diff --git a/core/test.mjs b/core/test.mjs index 003538c..9f55b3a 100644 --- a/core/test.mjs +++ b/core/test.mjs @@ -174,5 +174,48 @@ for (let i = 0; i < 30; i++) { } t('openers read as English', badOpener === 0, `${badOpener} malformed`); + +// ---- authoring layer ------------------------------------------------------- +import { newWorkingFile, reroll, lock, setAuthored, outstanding, readyToPlay } from './authoring.mjs'; +import { renderWorksheet, renderPlay } from './render-authoring.mjs'; + +{ + let changed = 0, lockHeld = 0, proseKept = 0; + for (let i = 0; i < 25; i++) { + const d = newWorkingFile({ pack, seed: `auth-${i}`, areas: 6 }); + const before = d.areas[2].decision.cue; + reroll(d, 3, 'decision', pack); + if (d.areas[2].decision.cue !== before) changed++; + + setAuthored(d, 3, 'readAloud', 'written by a human'); + lock(d, 3, 'situation'); + const sit = d.areas[2].situation.occupant; + reroll(d, 3, 'situation', pack); + if (d.areas[2].situation.occupant === sit) lockHeld++; + if (d.authored.areas[3].readAloud === 'written by a human') proseKept++; + } + t('a reroll always returns something different', changed === 25, `${changed}/25`); + t('a lock is respected', lockHeld === 25, `${lockHeld}/25`); + t('rerolling never destroys written prose', proseKept === 25, `${proseKept}/25`); + + const d = newWorkingFile({ pack, seed: 'auth-x', areas: 4 }); + t('a fresh delve is not ready to play', !readyToPlay(d), `${outstanding(d).length} outstanding`); + const ws = renderWorksheet(d); + t('the worksheet shows what still needs a human', ws.includes('WRITE ▸') && ws.includes('still need you')); + t('the worksheet offers reroll commands', ws.includes('--reroll=')); + const play = renderPlay(d); + t('an unfinished play sheet says so', play.includes('unfinished') && play.includes('(unwritten)')); + + setAuthored(d, 'ending', 'readAloud', 'x'); setAuthored(d, 'ending', 'notes', 'y'); + d.authored.intro = 'z'; + for (const a of d.areas) setAuthored(d, a.index, 'readAloud', `prose for ${a.index}`); + t('a finished delve reports ready', readyToPlay(d), `${outstanding(d).length} left`); + const done = renderPlay(d); + t('a finished play sheet uses the GM prose', done.includes('prose for 1') && !done.includes('(unwritten)')); + t('the play sheet is shorter than the worksheet', + done.split('\n').length < renderWorksheet(d).split('\n').length, + `play ${done.split('\n').length} vs worksheet ${renderWorksheet(d).split('\n').length} lines`); +} + console.log(`\n${pass} passed, ${fail} failed`); process.exit(fail ? 1 : 0); diff --git a/samples/finished-delve.json b/samples/finished-delve.json new file mode 100644 index 0000000..f33552f --- /dev/null +++ b/samples/finished-delve.json @@ -0,0 +1,484 @@ +{ + "schema": 1, + "generatorVersion": "0.1.0", + "catalogVersion": "barrow@1", + "seed": "live-table-01", + "params": { + "areas": 5, + "depth": 2, + "party": 4, + "deadliness": "standard", + "density": "standard", + "greed": "standard", + "clock": "standard", + "ending": "authored", + "theme": "barrow" + }, + "skeleton": { + "placeName": "The Vault of the Patient Lord", + "knot": { + "appetiteId": "obeyed", + "accommodationId": "purchased", + "claimant": "Patient Lord", + "conscript": "a household", + "transgression": "The Patient Lord bought a household from those with nothing left, so that someone would always obey.", + "cruelty": 2 + }, + "motif": { + "id": "voices", + "danger": "obedience", + "dangerLine": "the dead here still take instruction, and give it", + "trigger": "loud" + }, + "prize": { + "kind": "seal", + "label": "the signet that is still obeyed", + "function": "the only thing the household answers to" + }, + "bottomProblem": { + "label": "The Patient Lord", + "wantNow": "to be obeyed without asking", + "failureState": "is disobeyed now, and cannot bear it", + "handledBy": "giving them what they want, at a price" + }, + "appeasement": { + "move": "do one small thing it asks, without arguing", + "attribute": "Poise", + "successes": 2, + "why": "it was obeyed", + "gain": { + "vanity": 1 + }, + "cost": { + "bane": 1 + }, + "repeatable": true, + "faction": "The Silent Host" + }, + "factions": [ + { + "id": "host", + "name": "The Silent Host", + "kind": "undead", + "essence": "still performing the ritual they were buried for", + "handledBy": "courtesy", + "conscriptedAs": "a household" + }, + { + "id": "scavengers", + "name": "things that moved in", + "kind": "beast", + "essence": "neither know nor care whose tomb this is", + "handledBy": "force or food" + } + ], + "foreshadow": [ + { + "index": 1, + "role": "approach", + "facet": "institution", + "fragment": "servants' bones arranged as though mid-errand" + }, + { + "index": 2, + "role": "complication", + "facet": "ritual", + "fragment": "orders scratched into the floor, the last one unfinished" + }, + { + "index": 3, + "role": "turn", + "facet": "demand", + "fragment": "a hall where your own voice arrives before you speak" + }, + { + "index": 4, + "role": "descent", + "facet": "wound", + "fragment": "an instruction repeated until the stone gave out" + }, + { + "index": 5, + "role": "threshold", + "facet": "anchor", + "fragment": "a key that fits nothing here" + }, + { + "index": 6, + "role": "ending", + "facet": "demand", + "fragment": "a seat that expects to be addressed" + } + ] + }, + "areas": [ + { + "index": 1, + "role": "approach", + "name": "The Weeping Stair", + "facet": "institution", + "cueFragments": [ + "servants' bones arranged as though mid-errand", + "someone has been talking here, and stopped when you arrived" + ], + "situation": { + "occupant": "three of the Host in servants' dress", + "doing": "carrying out an errand that has no destination left", + "onArrival": "they stop, and wait to be told what to do", + "because": "the order was never rescinded and nobody outranks it", + "offer": "tell them where to go, and they go" + }, + "truth": "This is how the place was ordered, and it still is", + "decision": { + "cue": "an order carved into the floor, still legible", + "cost": "obey it and the Host counts you as household; step over it and they do not", + "resolve": { + "roll": "Poise 2", + "success": "you obey exactly enough to be counted household — Skeletons stand down for you", + "failure": "you obey wrongly, which is worse than not obeying: +1 Bane", + "orElse": "step over it: no cost now, and the Host does not count you as theirs" + } + }, + "encounter": null, + "hoard": null, + "temptation": null, + "trigger": "seenTwice", + "baneBeat": "announcing", + "clue": { + "automatic": "servants' bones arranged as though mid-errand — and it was done deliberately", + "extra": "[VANITY: Observation — 1 success] buys who did it, and roughly when" + }, + "failureCase": "If they skip this, move its cue fragment into the next area so the motif still completes.", + "fallback": { + "route": "The Silent Host will receive anyone who behaves as a guest", + "cost": "one Bane on the tab, and they will expect it again" + } + }, + { + "index": 2, + "role": "complication", + "name": "The Lesser Vault", + "facet": "ritual", + "cueFragments": [ + "orders scratched into the floor, the last one unfinished", + "a door braced shut from within by furniture" + ], + "situation": { + "occupant": "a living grave-robber", + "doing": "repeating a phrase over and over, trying to get the intonation right", + "onArrival": "they go quiet and put their back to the wall", + "because": "they heard the password and cannot say it correctly", + "offer": "trade something for the phrase, badly remembered" + }, + "truth": "The office is still being performed, for someone who is no longer served by it", + "decision": { + "cue": "two doors, and behind one a voice giving instructions to nobody", + "cost": "the quiet door is longer; the voice would tell you exactly where to go", + "resolve": { + "roll": "Poise 2", + "success": "you answer the voice as a subordinate would — it tells you the route AND answers one question about what lies on it, without taking you into its service", + "failure": "you engage and it takes that as enlistment: it will give you orders for the rest of the delve, and the first is now", + "orElse": "take the quiet door and never speak to it: one extra Turn, no obligation" + } + }, + "encounter": { + "heat": "fight", + "kind": "undead", + "roster": { + "line": "1 Ghoul and 2 Skeletons", + "foes": [ + { + "n": 1, + "name": "Ghoul", + "atk": 4, + "def": 3, + "grit": 4, + "nerve": 5, + "note": "on a hit, a free Called Shot to freeze for a round" + }, + { + "n": 2, + "name": "Skeleton", + "atk": 3, + "def": 2, + "grit": 1, + "nerve": 5, + "note": "fearless" + } + ], + "harmedBy": "anything", + "avoid": "the Ghoul goes for court dress first — send someone plain" + } + }, + "hoard": "cache", + "temptation": { + "id": "tube", + "cue": "a short length of speaking-tube, cut and carried", + "benefit": "ask one question and get one true answer", + "useCost": { + "bane": 1 + }, + "standingDrawback": "whatever answers can now hear you" + }, + "trigger": "loud", + "baneBeat": "noticing", + "clue": { + "automatic": "orders scratched into the floor, the last one unfinished — and it was done deliberately", + "extra": "[VANITY: Observation — 1 success] buys who did it, and roughly when" + }, + "failureCase": "If they skip this, move its cue fragment into the next area so the motif still completes.", + "fallback": { + "route": "The Silent Host will receive anyone who behaves as a guest", + "cost": "one Bane on the tab, and they will expect it again" + } + }, + { + "index": 3, + "role": "turn", + "name": "The Crossing", + "facet": "demand", + "cueFragments": [ + "a hall where your own voice arrives before you speak", + "an order half-scratched out and rewritten by a different hand" + ], + "situation": { + "occupant": "the bells", + "doing": "ringing a summons room by room, coming closer", + "onArrival": "the bell in this room begins", + "because": "something has called for service and the house is obliging", + "offer": "answer the bell yourself before whatever is meant to arrives" + }, + "truth": "Here the want is explicit — the place asks something of whoever enters", + "decision": { + "cue": "a bell-pull that would summon someone", + "cost": "ring it and help comes, and it is not the kind you wanted", + "resolve": { + "roll": "Poise 2", + "success": "what comes is answerable to you for one scene", + "failure": "what comes is not the kind of help you wanted", + "orElse": "do not ring: the way on is longer by one Turn" + } + }, + "encounter": null, + "hoard": null, + "temptation": null, + "trigger": "loud", + "baneBeat": "noticing", + "clue": { + "automatic": "a hall where your own voice arrives before you speak — and it was done deliberately", + "extra": "[VANITY: Observation — 1 success] buys who did it, and roughly when" + }, + "failureCase": "If they skip this, move its cue fragment into the next area so the motif still completes.", + "fallback": { + "route": "The Silent Host will receive anyone who behaves as a guest", + "cost": "one Bane on the tab, and they will expect it again" + } + }, + { + "index": 4, + "role": "descent", + "name": "The Servants' Deep", + "facet": "wound", + "cueFragments": [ + "an instruction repeated until the stone gave out", + "boot-prints that stop mid-corridor and do not resume" + ], + "situation": { + "occupant": "a single figure at a speaking-tube", + "doing": "reporting, at length, to a listener who died first", + "onArrival": "it pauses mid-sentence and asks whether you are the reply", + "because": "it has never once been answered", + "offer": "answer as the listener would, and learn what it has been reporting" + }, + "truth": "This is the damage the want did to those who had to satisfy it", + "decision": { + "cue": "a door that opens to a spoken password nobody living knows", + "cost": "guessing costs a Bane per attempt; the long way costs two Turns", + "resolve": { + "roll": "Wits 2", + "success": "the word is already in your notes. It is in something you passed earlier — *someone has been talking here, and stopped when you arrived*. Say it and the door opens; if nobody wrote it down, the roll tells them now", + "failure": "you have the shape of it but not the word: one more attempt, or take the long way", + "orElse": "the long way: two Turns, no guessing, no Banes" + } + }, + "encounter": null, + "hoard": "pocket", + "temptation": { + "id": "bell", + "cue": "a small bell from a servant's board", + "benefit": "ring it and one order is obeyed, by anything", + "useCost": { + "bane": 2 + }, + "standingDrawback": "it rings faintly when someone gives you an order" + }, + "trigger": "disturbed", + "baneBeat": "relicUse", + "clue": { + "automatic": "an instruction repeated until the stone gave out — and it was done deliberately", + "extra": "[VANITY: Observation — 1 success] buys who did it, and roughly when" + }, + "failureCase": "If they skip this, move its cue fragment into the next area so the motif still completes.", + "fallback": { + "route": "The Silent Host will receive anyone who behaves as a guest", + "cost": "one Bane on the tab, and they will expect it again" + } + }, + { + "index": 5, + "role": "threshold", + "name": "The Last Door", + "facet": "anchor", + "cueFragments": [ + "a key that fits nothing here", + "wax jammed into a speaking-tube, still soft" + ], + "situation": { + "occupant": "one of the Host, kneeling", + "doing": "waiting for an instruction that was promised and never came", + "onArrival": "it addresses the first of you to speak as its superior", + "because": "it was told to await further orders and did", + "offer": "give it the instruction and it will carry it out to the letter, wherever that leads" + }, + "truth": "The thing the whole barrow is arranged around is close", + "decision": { + "cue": "an instruction you can obey, ignore, or countermand", + "cost": "countermanding it works once and marks you as someone who gives orders", + "resolve": { + "roll": "Poise 2", + "success": "you countermand it and the dead obey you instead — this marks you as someone who gives orders: from now on the Host brings you their disputes, and once per delve you may issue one order they must follow", + "failure": "the order stands and you have been seen to fail at giving one: +1 Bane, and the Host addresses someone else from now on", + "orElse": "obey it, or ignore it and go round: ignoring costs one Turn" + } + }, + "encounter": { + "heat": "battle", + "kind": "undead", + "roster": { + "line": "1 Wraith and 3 Skeletons", + "foes": [ + { + "n": 1, + "name": "Wraith", + "atk": 5, + "def": 3, + "grit": 10, + "nerve": 5, + "note": "INCORPOREAL — chill touch ignores armour and drains 1 max Grit" + }, + { + "n": 3, + "name": "Skeleton", + "atk": 3, + "def": 2, + "grit": 1, + "nerve": 5, + "note": "fearless" + } + ], + "harmedBy": "blessed, silvered or magical weapons ONLY", + "avoid": "it will take an appeasement over a fight every time" + } + }, + "hoard": "chest", + "temptation": { + "id": "staff", + "cue": "a staff of office, propped where it was set down", + "benefit": "give one order the dead must follow", + "acceptanceCost": { + "vice": "Pride" + }, + "useCost": { + "bane": 2 + }, + "standingDrawback": "you may not refuse an order given to you" + }, + "trigger": "disturbed", + "baneBeat": "refusal", + "clue": { + "automatic": "a key that fits nothing here — and it was done deliberately", + "extra": "[VANITY: Observation — 1 success] buys who did it, and roughly when" + }, + "failureCase": "If they skip this, the ending opens with them holding nothing the bottom problem wants.", + "fallback": { + "route": "The Silent Host will receive anyone who behaves as a guest", + "cost": "one Bane on the tab, and they will expect it again" + } + } + ], + "ending": { + "index": 6, + "role": "ending", + "mode": "authored", + "heat": null, + "hoard": null, + "name": "The Audience", + "cueFragments": [ + "a seat that expects to be addressed" + ], + "question": "\"Do as you are told.\"", + "authored": true + }, + "triggerGlossary": { + "announced": "entered somewhere grandly — the big doors, a shout, a herald", + "seenTwice": "reflected, echoed or witnessed twice over in one place", + "loud": "a raised voice, a broken door, steel on stone", + "lingering": "stayed in one place longer than it takes to search it", + "disturbed": "struck or moved something that had not moved in centuries" + }, + "budget": { + "plannedFights": 2, + "heatBase": "fight", + "wantFights": 2 + }, + "pressure": { + "turn": 0, + "clockRolls": 0, + "clockFired": 0, + "triggerNumber": 1, + "escalated": false, + "tab": 5, + "target": 5 + }, + "authored": { + "title": null, + "intro": "They bought a household from people with nothing left, so that someone would always obey. The household is still obeying.", + "areas": { + "1": { + "readAloud": "Forty steps down and the bones are still on their errand — a tray here, a folded cloth there, set down mid-stride three hundred years ago. Somebody was talking as you came through the arch, and stopped.", + "notes": null, + "nameOverride": null, + "locked": [] + }, + "2": { + "readAloud": "The floor is scratched over with orders, the last one unfinished. A door at the far end is braced shut from the inside with furniture, and behind you a living man is whispering the same phrase again and again, trying to get the sound of it right.", + "notes": null, + "nameOverride": null, + "locked": [] + }, + "3": { + "readAloud": "The hall answers before you do — your own voice reaches the far wall a half-beat early, saying what you have not said yet. Somewhere below a bell starts, and then the one in this room takes it up.", + "notes": null, + "nameOverride": null, + "locked": [] + }, + "4": { + "readAloud": "An instruction has been cut into the stone so many times the stone gave out. Bootprints cross the floor and simply stop. At the far wall a figure is talking steadily into a speaking-tube, reporting to somebody who died first.", + "notes": null, + "nameOverride": null, + "locked": [] + }, + "5": { + "readAloud": "Wax has been jammed into the tube here, still soft. A key that fits nothing hangs on a nail. One of them is kneeling at the last door, and has been for a very long time, waiting to be told what to do.", + "notes": null, + "nameOverride": null, + "locked": [] + } + }, + "ending": { + "readAloud": "The seat at the head of the hall is occupied by a shape that keeps the gown's outline and nothing else. It turns toward you and there is nothing in the collar to turn.", + "notes": "**The Patient Lord** — Wraith, Grit 6, atk 5, def 3, incorporeal. Blessed, silvered or magical weapons only; say so before initiative. It opens with \"Do as you are told.\" Obey one small instruction and it will trade the signet. Refuse and it is a fight it will win slowly.", + "locked": [] + } + } +} \ No newline at end of file diff --git a/samples/finished-play.md b/samples/finished-play.md new file mode 100644 index 0000000..f0657d7 --- /dev/null +++ b/samples/finished-play.md @@ -0,0 +1,61 @@ +# The Vault of the Patient Lord + +> They bought a household from people with nothing left, so that someone would always obey. The household is still obeying. + +**Appease:** do one small thing it asks, without arguing `[Poise 2]` — +1 Vanity now, +1 Bane later. Repeatable, every area. +**Clock:** 1d6 on a trigger. **1 — something comes.** **Tab:** 5 by the end. +**GM truth:** The Patient Lord bought a household from those with nothing left, so that someone would always obey. The Patient Lord is disobeyed now, and cannot bear it. + +--- + +### 1 · The Weeping Stair + +> Forty steps down and the bones are still on their errand — a tray here, a folded cloth there, set down mid-stride three hundred years ago. Somebody was talking as you came through the arch, and stopped. + +**Here:** Three of the Host in servants' dress, carrying out an errand that has no destination left — they stop, and wait to be told what to do. **They can:** tell them where to go, and they go. +**An order carved into the floor, still legible** — `[Poise 2]` you obey exactly enough to be counted household — Skeletons stand down for you · **miss** you obey wrongly, which is worse than not obeying: +1 Bane · **or** step over it: no cost now, and the Host does not count you as theirs +`seenTwice · announcing · skipped → move the image on` + +### 2 · The Lesser Vault + +> The floor is scratched over with orders, the last one unfinished. A door at the far end is braced shut from the inside with furniture, and behind you a living man is whispering the same phrase again and again, trying to get the sound of it right. + +**Here:** A living grave-robber, repeating a phrase over and over, trying to get the intonation right — they go quiet and put their back to the wall. **They can:** trade something for the phrase, badly remembered. +**Two doors, and behind one a voice giving instructions to nobody** — `[Poise 2]` you answer the voice as a subordinate would — it tells you the route AND answers one question about what lies on it, without taking you into its service · **miss** you engage and it takes that as enlistment: it will give you orders for the rest of the delve, and the first is now · **or** take the quiet door and never speak to it: one extra Turn, no obligation +**Fight:** 1× Ghoul `4/3/4` · 2× Skeleton `3/2/1` — harmed by anything. +**Tube:** ask one question and get one true answer. *Use: +1 Bane. whatever answers can now hear you.* +`loud · noticing · skipped → move the image on` + +### 3 · The Crossing + +> The hall answers before you do — your own voice reaches the far wall a half-beat early, saying what you have not said yet. Somewhere below a bell starts, and then the one in this room takes it up. + +**Here:** The bells, ringing a summons room by room, coming closer — the bell in this room begins. **They can:** answer the bell yourself before whatever is meant to arrives. +**A bell-pull that would summon someone** — `[Poise 2]` what comes is answerable to you for one scene · **miss** what comes is not the kind of help you wanted · **or** do not ring: the way on is longer by one Turn +`loud · noticing · skipped → move the image on` + +### 4 · The Servants' Deep + +> An instruction has been cut into the stone so many times the stone gave out. Bootprints cross the floor and simply stop. At the far wall a figure is talking steadily into a speaking-tube, reporting to somebody who died first. + +**Here:** A single figure at a speaking-tube, reporting, at length, to a listener who died first — it pauses mid-sentence and asks whether you are the reply. **They can:** answer as the listener would, and learn what it has been reporting. +**A door that opens to a spoken password nobody living knows** — `[Wits 2]` the word is already in your notes. It is in something you passed earlier — *someone has been talking here, and stopped when you arrived*. Say it and the door opens; if nobody wrote it down, the roll tells them now · **miss** you have the shape of it but not the word: one more attempt, or take the long way · **or** the long way: two Turns, no guessing, no Banes +**Bell:** ring it and one order is obeyed, by anything. *Use: +2 Banes. it rings faintly when someone gives you an order.* +`disturbed · relicUse · skipped → move the image on` + +### 5 · The Last Door + +> Wax has been jammed into the tube here, still soft. A key that fits nothing hangs on a nail. One of them is kneeling at the last door, and has been for a very long time, waiting to be told what to do. + +**Here:** One of the Host, kneeling, waiting for an instruction that was promised and never came — it addresses the first of you to speak as its superior. **They can:** give it the instruction and it will carry it out to the letter, wherever that leads. +**An instruction you can obey, ignore, or countermand** — `[Poise 2]` you countermand it and the dead obey you instead — this marks you as someone who gives orders: from now on the Host brings you their disputes, and once per delve you may issue one order they must follow · **miss** the order stands and you have been seen to fail at giving one: +1 Bane, and the Host addresses someone else from now on · **or** obey it, or ignore it and go round: ignoring costs one Turn +**Battle:** 1× Wraith `5/3/10` · 3× Skeleton `3/2/1` — harmed by blessed, silvered or magical weapons ONLY. +**Staff:** give one order the dead must follow. *Use: +2 Banes. you may not refuse an order given to you.* +`disturbed · refusal · skipped → move the image on` + +### The Audience + +> The seat at the head of the hall is occupied by a shape that keeps the gown's outline and nothing else. It turns toward you and there is nothing in the collar to turn. + +**It asks:** "Do as you are told." +**The Patient Lord** — Wraith, Grit 6, atk 5, def 3, incorporeal. Blessed, silvered or magical weapons only; say so before initiative. It opens with "Do as you are told." Obey one small instruction and it will trade the signet. Refuse and it is a fight it will win slowly. diff --git a/samples/finished-worksheet.md b/samples/finished-worksheet.md new file mode 100644 index 0000000..c61518f --- /dev/null +++ b/samples/finished-worksheet.md @@ -0,0 +1,137 @@ +# WORKSHEET — The Vault of the Patient Lord + +`seed live-table-01 · barrow · 5 areas · depth 2 · generator 0.1.0` + +**Finished.** Export the play sheet. + +--- + +## The kernel + +| | | +|---|---| +| **Transgression** | The Patient Lord bought a household from those with nothing left, so that someone would always obey. | +| **Bottom problem** | The Patient Lord — is disobeyed now, and cannot bear it | +| **Prize** | the signet that is still obeyed — the only thing the household answers to | +| **Motif / danger** | voices · obedience — the dead here still take instruction, and give it | +| **Appeasement** | do one small thing it asks, without arguing `[Poise 2]` — +1 Vanity, +1 Bane, repeatable | +| **It asks** | "Do as you are told." | + +**WRITE ▸ an opening paragraph.** ✔ written + +> They bought a household from people with nothing left, so that someone would always obey. The household is still obeying. + +--- + +## Area 1 — The Weeping Stair +`approach · institution` +*reroll:* `--reroll=1:situation` `--reroll=1:decision` `--reroll=1:feature` `--reroll=1:name` + +**IMAGE** servants' bones arranged as though mid-errand · someone has been talking here, and stopped when you arrived +**SITUATION** Three of the Host in servants' dress — carrying out an errand that has no destination left. On arrival, they stop, and wait to be told what to do. +**WHY** The order was never rescinded and nobody outranks it. **OFFERS** tell them where to go, and they go. +**DECISION** An order carved into the floor, still legible — *obey it and the Host counts you as household; step over it and they do not* + · `[Poise 2]` → you obey exactly enough to be counted household — Skeletons stand down for you + · **miss** → you obey wrongly, which is worse than not obeying: +1 Bane + · **or** step over it: no cost now, and the Host does not count you as theirs +**PRESSURE** trigger seenTwice · bane beat announcing · fallback The Silent Host will receive anyone who behaves as a guest + +**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above. +> Forty steps down and the bones are still on their errand — a tray here, a folded cloth there, set down mid-stride three hundred years ago. Somebody was talking as you came through the arch, and stopped. + +--- + +## Area 2 — The Lesser Vault +`complication · ritual · fight · cache` +*reroll:* `--reroll=2:situation` `--reroll=2:decision` `--reroll=2:temptation` `--reroll=2:feature` `--reroll=2:name` + +**IMAGE** orders scratched into the floor, the last one unfinished · a door braced shut from within by furniture +**SITUATION** A living grave-robber — repeating a phrase over and over, trying to get the intonation right. On arrival, they go quiet and put their back to the wall. +**WHY** They heard the password and cannot say it correctly. **OFFERS** trade something for the phrase, badly remembered. +**DECISION** Two doors, and behind one a voice giving instructions to nobody — *the quiet door is longer; the voice would tell you exactly where to go* + · `[Poise 2]` → you answer the voice as a subordinate would — it tells you the route AND answers one question about what lies on it, without taking you into its service + · **miss** → you engage and it takes that as enlistment: it will give you orders for the rest of the delve, and the first is now + · **or** take the quiet door and never speak to it: one extra Turn, no obligation +**ENCOUNTER** 1 Ghoul and 2 Skeletons — 1× Ghoul atk4/def3/G4, 2× Skeleton atk3/def2/G1 + · harmed by anything · the Ghoul goes for court dress first — send someone plain +**TEMPTATION** Tube — a short length of speaking-tube, cut and carried: ask one question and get one true answer + · take free · use +1 Bane · standing: whatever answers can now hear you +**PRESSURE** trigger loud · bane beat noticing · fallback The Silent Host will receive anyone who behaves as a guest + +**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above. +> The floor is scratched over with orders, the last one unfinished. A door at the far end is braced shut from the inside with furniture, and behind you a living man is whispering the same phrase again and again, trying to get the sound of it right. + +--- + +## Area 3 — The Crossing +`turn · demand` +*reroll:* `--reroll=3:situation` `--reroll=3:decision` `--reroll=3:feature` `--reroll=3:name` + +**IMAGE** a hall where your own voice arrives before you speak · an order half-scratched out and rewritten by a different hand +**SITUATION** The bells — ringing a summons room by room, coming closer. On arrival, the bell in this room begins. +**WHY** Something has called for service and the house is obliging. **OFFERS** answer the bell yourself before whatever is meant to arrives. +**DECISION** A bell-pull that would summon someone — *ring it and help comes, and it is not the kind you wanted* + · `[Poise 2]` → what comes is answerable to you for one scene + · **miss** → what comes is not the kind of help you wanted + · **or** do not ring: the way on is longer by one Turn +**PRESSURE** trigger loud · bane beat noticing · fallback The Silent Host will receive anyone who behaves as a guest + +**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above. +> The hall answers before you do — your own voice reaches the far wall a half-beat early, saying what you have not said yet. Somewhere below a bell starts, and then the one in this room takes it up. + +--- + +## Area 4 — The Servants' Deep +`descent · wound · pocket` +*reroll:* `--reroll=4:situation` `--reroll=4:decision` `--reroll=4:temptation` `--reroll=4:feature` `--reroll=4:name` + +**IMAGE** an instruction repeated until the stone gave out · boot-prints that stop mid-corridor and do not resume +**SITUATION** A single figure at a speaking-tube — reporting, at length, to a listener who died first. On arrival, it pauses mid-sentence and asks whether you are the reply. +**WHY** It has never once been answered. **OFFERS** answer as the listener would, and learn what it has been reporting. +**DECISION** A door that opens to a spoken password nobody living knows — *guessing costs a Bane per attempt; the long way costs two Turns* + · `[Wits 2]` → the word is already in your notes. It is in something you passed earlier — *someone has been talking here, and stopped when you arrived*. Say it and the door opens; if nobody wrote it down, the roll tells them now + · **miss** → you have the shape of it but not the word: one more attempt, or take the long way + · **or** the long way: two Turns, no guessing, no Banes +**TEMPTATION** Bell — a small bell from a servant's board: ring it and one order is obeyed, by anything + · take free · use +2 Banes · standing: it rings faintly when someone gives you an order +**PRESSURE** trigger disturbed · bane beat relicUse · fallback The Silent Host will receive anyone who behaves as a guest + +**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above. +> An instruction has been cut into the stone so many times the stone gave out. Bootprints cross the floor and simply stop. At the far wall a figure is talking steadily into a speaking-tube, reporting to somebody who died first. + +--- + +## Area 5 — The Last Door +`threshold · anchor · battle · chest` +*reroll:* `--reroll=5:situation` `--reroll=5:decision` `--reroll=5:temptation` `--reroll=5:feature` `--reroll=5:name` + +**IMAGE** a key that fits nothing here · wax jammed into a speaking-tube, still soft +**SITUATION** One of the Host, kneeling — waiting for an instruction that was promised and never came. On arrival, it addresses the first of you to speak as its superior. +**WHY** It was told to await further orders and did. **OFFERS** give it the instruction and it will carry it out to the letter, wherever that leads. +**DECISION** An instruction you can obey, ignore, or countermand — *countermanding it works once and marks you as someone who gives orders* + · `[Poise 2]` → you countermand it and the dead obey you instead — this marks you as someone who gives orders: from now on the Host brings you their disputes, and once per delve you may issue one order they must follow + · **miss** → the order stands and you have been seen to fail at giving one: +1 Bane, and the Host addresses someone else from now on + · **or** obey it, or ignore it and go round: ignoring costs one Turn +**ENCOUNTER** 1 Wraith and 3 Skeletons — 1× Wraith atk5/def3/G10, 3× Skeleton atk3/def2/G1 + · harmed by blessed, silvered or magical weapons ONLY · it will take an appeasement over a fight every time +**TEMPTATION** Staff — a staff of office, propped where it was set down: give one order the dead must follow + · take the Vice Pride · use +2 Banes · standing: you may not refuse an order given to you +**PRESSURE** trigger disturbed · bane beat refusal · fallback The Silent Host will receive anyone who behaves as a guest + +**WRITE ▸ read-aloud** — ≤3 sentences, built from the IMAGE and SITUATION above. +> Wax has been jammed into the tube here, still soft. A key that fits nothing hangs on a nail. One of them is kneeling at the last door, and has been for a very long time, waiting to be told what to do. + +--- + +## Ending — The Audience +`AUTHORED SLOT — DELVE stops here on purpose` + +**IMAGE** a seat that expects to be addressed +**IT WANTS** to be obeyed without asking. **IT ASKS** "Do as you are told." +**APPEASED EVEN ONCE** it listens, and will trade the signet that is still obeyed. + +**WRITE ▸ read-aloud** +> The seat at the head of the hall is occupied by a shape that keeps the gown's outline and nothing else. It turns toward you and there is nothing in the collar to turn. + +**WRITE ▸ the climax.** How it goes, what harms it, what it will take instead of a fight. +**The Patient Lord** — Wraith, Grit 6, atk 5, def 3, incorporeal. Blessed, silvered or magical weapons only; say so before initiative. It opens with "Do as you are told." Obey one small instruction and it will trade the signet. Refuse and it is a fight it will win slowly.