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:
slaguru666
2026-09-12 23:29:41 +01:00
co-authored by Claude Opus 5
parent c0d33ee81d
commit f847cb2171
8 changed files with 76 additions and 3 deletions
+32
View File
@@ -5363,3 +5363,35 @@ generated and validated against `npm run check`, and three sentences around it s
"the eight guards" while nine ran. `update-readme` now rewrites the count word as well as
the block. The comment warning that a hardcoded list goes stale was sitting four lines
above the prose that had gone stale.
## R-256 — the header said 9 while the body said 4
Verifying R-255 on a live sheet showed the rule working everywhere it was built and
contradicted in the one place a GM actually looks first. The header's ARMOUR field is the
hide the creature HAS; grounded, the hit-location table underneath it read 4 and the
header still read 9. Nothing computes off that field, so nothing was wrong in the code —
it was wrong at the table, which is the only place that counts.
The field stays editable and stays 9, because 9 is the creature's hide and that is what a
GM needs to type into. Underneath it, when `system.grounded` is set, the sheet now states
what is in play: **DOWN: 4 · CHEST 9**, from `exposedHideFor` rather than from a second
opinion, with the reason on the tooltip.
**The guard, and what it caught on the way.** check-rules forbids redefining a rule BY
NAME, which misses how this actually goes wrong: nobody redefines `exposedHideFor`, they
write `Math.floor(hide / 2)` in the sheet because it is one keystroke cheaper than an
import. I wrote the wrong version first to prove the point and check-rules passed it
happily. It now fails any file outside `rules.mjs` that halves a hide inline, with the
file and line; the check files are exempt, because independently stating the expected
value is what a test is for.
**Open, and worse than what I fixed.** Writing that guard turned up two copies of a
different armour rule. `armourAgainst` — critical ignores armour, special halves it —
lives in `ringbrp.mjs:414`, not in `rules.mjs`, so the authority never covered it; and
`tools/simulate.mjs:267` has its own inline version rather than importing it. They agree
today, so no published number is wrong. But every figure in BESTIARY.md and the whole
lethality baseline comes out of the simulator, so the day someone changes how a special
hit meets armour, the game changes and the measurements do not, and the guards will all
still pass. That is R-251 with a longer fuse. Not fixed here: moving a rule into
`rules.mjs` and re-pointing the simulator at it is a change to the measurement pipeline
and wants its own before-and-after.