R-267 addendum: read the cast, do not copy it
I told the scenario session that this guard turns a silent re-cast into a build failure, and it accepted the coupling on that basis. The cast was hardcoded, so a re-cast would have left it measuring the old six and reporting success. Reads the <!-- cast: --> marker check-rollable established, drops the two the scaling note drops by name rather than by position, and refuses to run if the marker is gone. Verified by re-casting CLEAN GROUND in a throwaway worktree and watching the guard name the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
996bd248d3
commit
60995b28c2
@@ -5898,3 +5898,21 @@ not wrong enough to mislead anybody and it was wrong enough to print.
|
||||
at twelve, which is exactly the hardcoded-list failure its own comment warns about two
|
||||
lines above, committed by the file that exists to prevent it. Extended to sixteen, which
|
||||
defers the same defect rather than curing it.
|
||||
|
||||
**Addendum — the coupling did not actually bite.** I told the scenario's session that this
|
||||
guard converts a silent re-cast into a build failure. It believed me and accepted the
|
||||
coupling on that basis. It was not true: the cast was written out by hand here, so a
|
||||
re-cast would have left this measuring the old six and reporting success — the project's
|
||||
own signature defect, committed inside the guard written to prevent a weaker version of
|
||||
it, three hours after writing "a claim nothing checks" about something else.
|
||||
|
||||
It now reads the `<!-- cast: ... -->` marker check-rollable already established, derives
|
||||
the cut by dropping the two the scaling note drops — by name, since "the first four" would
|
||||
survive a reordering and quietly measure different people — and refuses to run at all if
|
||||
the marker is missing. Proved by re-casting CLEAN GROUND in a throwaway worktree and
|
||||
watching it fail: *"cut: the scenario now casts different people — no longer braithwaite,
|
||||
now agyeman ... re-record with --update, do not revert the cast."*
|
||||
|
||||
**Which is the third time in this sequence that a thing was true only because nobody had
|
||||
tested it.** The generator comment, the costed restraint, and now the coupling — each
|
||||
stated confidently, each wrong, each found by somebody trying it rather than reading it.
|
||||
|
||||
+49
-6
@@ -29,7 +29,7 @@
|
||||
* cut rather than of the creature.
|
||||
*/
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
@@ -47,9 +47,43 @@ const RUNS = Number(opt("--runs", 2000));
|
||||
const SEEDS = [11, 4242, 90210];
|
||||
|
||||
/* CLEAN GROUND's own cast, not the frozen party of the other two guards: this measures a
|
||||
scenario's encounter, and the whole finding is about what four of these people can do. */
|
||||
const CUT = ["ashcroft", "bhattacharya", "renshaw", "braithwaite"];
|
||||
const LINE = [...CUT, "pollard", "okonkwo"];
|
||||
scenario's encounter, and the whole finding is about what four of these people can do.
|
||||
|
||||
Read from the document rather than copied out of it. A cast written here by hand would
|
||||
go on measuring the old six after a re-cast and report success, which is the defect this
|
||||
project keeps finding in itself and the one thing a guard must not do. check-rollable
|
||||
established the marker; this reads the same one. */
|
||||
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"];
|
||||
|
||||
function declaredCast() {
|
||||
if (!existsSync(SCENARIO)) {
|
||||
console.error(`first-blood: ${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 raw = readFileSync(SCENARIO, "utf8");
|
||||
const m = raw.match(/<!--\s*cast:\s*([^>]*?)\s*-->/i);
|
||||
if (!m) {
|
||||
console.error(`first-blood: ${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);
|
||||
}
|
||||
|
||||
const LINE = declaredCast();
|
||||
const CUT = LINE.filter(k => !DROPPED_AT_FOUR.includes(k));
|
||||
if (CUT.length !== 4 || LINE.length !== 6) {
|
||||
console.error(`first-blood: 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);
|
||||
}
|
||||
const CONFIGS = [
|
||||
{ id: "cut", label: "four of the roster vs three of the column", party: CUT, creature: "quiet_neighbours_npc", count: 3 },
|
||||
{ id: "column", label: "six of the roster vs six of the column", party: LINE, creature: "quiet_neighbours_npc", count: 6 }
|
||||
@@ -188,8 +222,17 @@ const now = {}, problems = [];
|
||||
for (const cfg of CONFIGS) {
|
||||
const was = base.configs[cfg.id];
|
||||
if (!was) { problems.push(` ${cfg.id}: not in the baseline — re-record with --update`); continue; }
|
||||
if (was.party.join() !== cfg.party.join() || was.creature !== cfg.creature || was.count !== cfg.count) {
|
||||
problems.push(` ${cfg.id}: recorded against a different fight (${was.party.length} vs ${was.count} x ${was.creature})`);
|
||||
if (was.party.join() !== cfg.party.join()) {
|
||||
const gone = was.party.filter(k => !cfg.party.includes(k));
|
||||
const added = cfg.party.filter(k => !was.party.includes(k));
|
||||
problems.push(` ${cfg.id}: the scenario now casts different people — `
|
||||
+ (gone.length ? `no longer ${gone.join(", ")}` : "the same six, reordered")
|
||||
+ (added.length ? `, now ${added.join(", ")}` : "")
|
||||
+ `. The recorded swing describes the old cast; re-record with --update, do not revert the cast.`);
|
||||
continue;
|
||||
}
|
||||
if (was.creature !== cfg.creature || was.count !== cfg.count) {
|
||||
problems.push(` ${cfg.id}: recorded against ${was.count} x ${was.creature}, now ${cfg.count} x ${cfg.creature}`);
|
||||
continue;
|
||||
}
|
||||
const m = now[cfg.id] = measure(cfg);
|
||||
|
||||
Reference in New Issue
Block a user