The sheet header said 9 while the body said 4 (R-256)
Verifying R-255 on a live sheet found the rule working everywhere it was built and contradicted in the first place a GM looks. The header's ARMOUR field is the hide the creature HAS; on a grounded beast the hit-location table under it read 4 and the header still read 9. Nothing computes off that field, so nothing in the code was wrong. It was wrong at the table. The field stays editable and stays 9 — that is the creature's hide and what a GM types into. Under it, when system.grounded is set, the sheet now says what is in play: DOWN: 4 · CHEST 9, derived from exposedHideFor rather than from a second opinion, with the reason on the tooltip. Verified live: no badge in the air, and grounded the badge and the location table agree at 4 with the forequarters at 9. check-rules forbids redefining a rule by NAME, which misses how this goes wrong in practice — nobody redefines exposedHideFor, they write Math.floor(hide / 2) in the sheet because it is cheaper than an import. I wrote that version first and check-rules passed it. It now fails any file outside rules.mjs that halves a hide inline, naming file and line; check files are exempt because stating the expected value independently is what a test is for. That guard also turned up something I have not fixed, recorded in R-256: armourAgainst lives in ringbrp.mjs rather than rules.mjs, and tools/simulate.mjs has its own inline copy of it. They agree today so no published number is wrong, but every figure in the bestiary and the lethality baseline comes out of that simulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c0d33ee81d
commit
f847cb2171
@@ -42,6 +42,25 @@ for (const rel of files) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 1b. and none of them may re-derive the hide ------------------------------- *
|
||||
* Redefinition is caught by NAME above, which misses the way this actually goes
|
||||
* wrong: the sheet header wanted the grounded hide and `Math.floor(hide / 2)` is
|
||||
* one keystroke cheaper than importing exposedHideFor. Then the header and the hit
|
||||
* locations are two rules that happen to agree, until one of them is changed. The
|
||||
* exemption is the check files, where stating the expected value independently is
|
||||
* the entire point of a test. */
|
||||
{
|
||||
const inlineHalving = /Math\.(?:floor|round|ceil)\(\s*[^()]*\b(?:hide|naturalArmour)\b[^()]*\/\s*2/i;
|
||||
for (const rel of files) {
|
||||
if (rel.startsWith("tools/check-")) continue;
|
||||
const src = await readFile(new URL(rel, root), "utf8");
|
||||
src.split("\n").forEach((line, i) => {
|
||||
if (inlineHalving.test(line))
|
||||
fail(`${rel}:${i + 1} halves natural armour inline — exposedHideFor() owns that`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. aliases must be identity-equal ---------------------------------------- *
|
||||
* REQUIRED is authoritative and hand-held on purpose. Discovery alone is not
|
||||
* enough: the original bug replaced an alias with an arrow function, which a
|
||||
|
||||
Reference in New Issue
Block a user