The starter is now an action case: a train running through a station whose rails were lifted in 1976, two engineers aboard, eleven minutes until it returns. Descend, take the platform, board, rescue, fight forward along a moving train, stop it. Simpler than PAPER WINDOW on purpose — one clue scene, no social subsystem — which also means it cannot reproduce R-248, because there are no groups or standings to leave unwired. Every agent can fight, deliberately: floors of firearm 42%, melee 40%, Brawl 40%, Dodge 40% so nobody is a spectator on their first evening. Floors only RAISE, so a posting already better stays better — Callum's shield lands at 53, not the floor's 45. The builder now handles combat floors, extra weapons issued for a case, and a scenario with NO Borrowed Authority at all; the last was assumed to exist and would have thrown. Two new tests: every floor names a real skill, and every agent clears the fighting floors the handout promises. Sixty-two dialog strings reworded. They were naming mechanics at people: "Situational modifier %" is now "Anything else helping or hurting? (%)", "Target cover" is "Is the target behind anything?", "Difficult (÷2)" is "Difficult — half your skill". Labels ask the question; hints say what it does to the number; notifications say what to do next rather than what went wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 lines
3.7 KiB
JavaScript
71 lines
3.7 KiB
JavaScript
/**
|
|
* Rewrite the README's counted sections from the real data.
|
|
*
|
|
* Those tables have been wrong three times, because a catalogue that grows every
|
|
* session and a number typed by hand are not the same kind of thing. A stale count in
|
|
* a README is the same defect as a stale comment in the engine: a claim nothing checks.
|
|
*
|
|
* Only the marked regions are touched; the prose around them is left alone.
|
|
*/
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { execFileSync } from "node:child_process";
|
|
import { SKILL_CATALOGUE, WEAPONS, ARMOURS, GEAR, VEHICLES, NPCS, TALENTS }
|
|
from "./content.mjs";
|
|
import { ROLES, TRADES } from "../postings.mjs";
|
|
import { ICON_MAP } from "./icons.mjs";
|
|
import { RULES_PAGES } from "./rules-text.mjs";
|
|
|
|
const root = new URL("../", import.meta.url);
|
|
const readme = new URL("README.md", root);
|
|
let text = await readFile(readme, "utf8");
|
|
|
|
const items = WEAPONS.length + ARMOURS.length + GEAR.length + VEHICLES.length;
|
|
|
|
// The guard lines are quoted from the guards themselves rather than paraphrased.
|
|
const runGuard = name => {
|
|
try {
|
|
return execFileSync(process.execPath, [new URL(name, import.meta.url).pathname],
|
|
{ encoding: "utf8" }).split("\n").find(l => l.startsWith(name.replace(".mjs", "")));
|
|
} catch { return null; }
|
|
};
|
|
const guardLines = ["check-rules.mjs", "check-kits.mjs", "check-lang.mjs",
|
|
"check-behaviour.mjs"].map(runGuard).filter(Boolean);
|
|
|
|
const box = `| | |
|
|
|---|---|
|
|
| **Postings** | ${Object.keys(ROLES).length}, each with a key characteristic, trained skills, talents and its own kit |
|
|
| **Trades** | ${Object.keys(TRADES).length} ordinary jobs the department recruits from, plus induction |
|
|
| **Catalogue** | ${items} items — ${GEAR.length} gear, ${WEAPONS.length} weapons, ${ARMOURS.length} armour, ${VEHICLES.length} vehicles |
|
|
| **Skills / talents** | ${SKILL_CATALOGUE.length} skills, ${TALENTS.length} talents |
|
|
| **Bestiary** | ${NPCS.length} statblocks across folklore, horror and the far side |
|
|
| **Eras** | modern, antique, future, anomalous — the agency is old and never abolishes anything |
|
|
| **Icons** | ${Object.keys(ICON_MAP).length}, all generated from \`tools/icons.mjs\`; no external art |
|
|
| **Starter scenario** | **BLACK PLATFORM**, a six-player action starter, importable as a single Adventure |
|
|
| **Compendia** | pregens, NPCs, skills, weapons, armour, gear, vehicles, powers, tables, rules, the starter |
|
|
| **Rules journal** | ${RULES_PAGES.length} pages, generated from \`rules.mjs\` so the book cannot drift from the code |`;
|
|
|
|
const replaceRegion = (src, marker, body) => {
|
|
const open = `<!-- ${marker}:start -->`, close = `<!-- ${marker}:end -->`;
|
|
const i = src.indexOf(open), j = src.indexOf(close);
|
|
if (i < 0 || j < 0) return null;
|
|
return src.slice(0, i + open.length) + "\n" + body + "\n" + src.slice(j);
|
|
};
|
|
|
|
// First run: wrap the existing hand-written regions in markers.
|
|
if (!text.includes("<!-- box:start -->")) {
|
|
text = text.replace(/\| \| \|\n\|---\|---\|\n(?:\|.*\n)+/,
|
|
m => `<!-- box:start -->\n${m}<!-- box:end -->\n`);
|
|
text = text.replace(/```\ncheck-rules:[\s\S]*?```/,
|
|
m => `<!-- guards:start -->\n${m}\n<!-- guards:end -->`);
|
|
}
|
|
|
|
const withBox = replaceRegion(text, "box", box);
|
|
if (!withBox) { console.error("update-readme: box markers missing"); process.exit(1); }
|
|
const withGuards = replaceRegion(withBox, "guards",
|
|
"```\n" + guardLines.join("\n") + "\n```");
|
|
if (!withGuards) { console.error("update-readme: guard markers missing"); process.exit(1); }
|
|
|
|
await writeFile(readme, withGuards, "utf8");
|
|
console.log(`update-readme: OK — ${items} items, ${Object.keys(ROLES).length} postings, `
|
|
+ `${Object.keys(TRADES).length} trades, ${guardLines.length} guard lines`);
|