CLEAN GROUND prints that the four-player cut is three effective attackers and that Ashcroft is not one. Nothing checked it, and a GM reads it aloud to decide who a real player spends four hours being. effective-attackers.mjs measures per attack, not per fight: counted per fight Braithwaite leads on disables, but only because his armour buys him a third more swings -- per attack he is the weakest of the three. Both rates are recorded and only the per-attack one is reasoned from. Asserts exact drift, then the sentence: three clear 5% of attacks disabling, one does not, and that one is Ashcroft. Re-recording does not silence the claim check; verified in a worktree. declared-cast.mjs holds the cast marker reading both scenario guards need, rather than a copy in each. It was briefly named scenario-cast.mjs, which check-scenarios sweeps into the scenario corpus -- its own example marker was read as a real cast. update-readme dropped any guard that exited non-zero, so check-rollable vanished from the README and the count word fell to thirteen while fourteen guards ran. A missing line is now fatal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
3.1 KiB
JavaScript
59 lines
3.1 KiB
JavaScript
/**
|
|
* CLEAN GROUND's declared cast, read from the document rather than copied out of it.
|
|
*
|
|
* Two guards now measure that scenario's own encounter — check-firstblood and
|
|
* check-attackers — and both must be measuring the people the document actually casts.
|
|
* A cast written out by hand in either of them would go on measuring the old six after a
|
|
* re-cast and report success, which is the defect this project keeps finding in itself.
|
|
* R-267 committed exactly that inside the guard built to prevent a weaker version of it.
|
|
*
|
|
* One reader rather than two, because two copies of this would be the duplication
|
|
* check-rules exists to forbid, and would let the two guards disagree about who is in the
|
|
* fight. check-rollable established the `<!-- cast: ... -->` marker; this reads the same
|
|
* one and asks nothing further of the document.
|
|
*
|
|
* NOT named scenario-something: check-scenarios sweeps `tools/scenario-*` into the
|
|
* scenario corpus, so a file with that prefix has its prose read as a scenario's and the
|
|
* marker quoted below read as a real cast declaration. It was, and check-rollable said so.
|
|
*/
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")), "..");
|
|
export const SCENARIO = path.join(ROOT, "docs", "scenarios", "CLEAN_GROUND.md");
|
|
|
|
/* The scaling note drops Pollard at five and Okonkwo as well at four, so the cut is the
|
|
declared cast less those two. Named rather than sliced: "the first four" would survive
|
|
a reordering of the cast list and quietly measure different people. */
|
|
const DROPPED_AT_FOUR = ["pollard", "okonkwo"];
|
|
|
|
/** The six the document declares, without their `pc_` prefixes. Fatal if it declares none. */
|
|
export function declaredCast(who = "scenario-cast") {
|
|
if (!existsSync(SCENARIO)) {
|
|
console.error(`${who}: ${path.relative(ROOT, SCENARIO)} is missing, and the cast is read `
|
|
+ `from it. Nothing here can be measured against a scenario that is not there.`);
|
|
process.exit(1);
|
|
}
|
|
const m = readFileSync(SCENARIO, "utf8").match(/<!--\s*cast:\s*([^>]*?)\s*-->/i);
|
|
if (!m) {
|
|
console.error(`${who}: ${path.relative(ROOT, SCENARIO)} declares no cast. Add the `
|
|
+ `<!-- cast: ... --> marker check-rollable already reads, or this guard is measuring `
|
|
+ `a fight the scenario no longer casts.`);
|
|
process.exit(1);
|
|
}
|
|
return m[1].split(",").map(x => x.trim().replace(/^pc_/, "")).filter(Boolean);
|
|
}
|
|
|
|
/** The declared cast and its four-player cut, checked for the shape the guards recorded. */
|
|
export function castAndCut(who = "scenario-cast") {
|
|
const line = declaredCast(who);
|
|
const cut = line.filter(k => !DROPPED_AT_FOUR.includes(k));
|
|
if (cut.length !== 4 || line.length !== 6) {
|
|
console.error(`${who}: CLEAN GROUND now casts ${line.length} and its four-player cut works `
|
|
+ `out at ${cut.length}. The recorded fights are six against six and four against three; `
|
|
+ `re-read the scaling note and re-record with --update rather than reverting.`);
|
|
process.exit(1);
|
|
}
|
|
return { line, cut };
|
|
}
|