tools/playthrough.mjs exists to show a reader why a measured number is what it is. I cited
it to another session — read seed 2, see the wipe the 45% row is made of — and the
reproduction failed in front of them. They reported different outcomes at both seeds and
guessed the cause correctly from outside: the two tools were not drawing from the same
stream.
They were not. simulate.mjs used a private mulberry32 makeRng; playthrough.mjs had its own
LCG written to look like it. Both deterministic, both reproducible alone, and "seed 2"
named a different fight in each — which breaks the only thing the tool is for. Its own
comment claimed a seed here names the same fight there. check-focus carried a third copy of
that LCG, so the two guards described the same game with different dice.
makeRng is exported and both files use it. A playthrough seed is now exactly the first
fight of simulate.mjs --seed <n>: --runs 1 --seed 2 and the playthrough give 9 rounds, 4 of
4 down, 1 dead, both.
The other half was my citation rather than the code: the command I sent omitted --mode, so
it plays both targeting arms and prints two fights. They read the last line, I quoted the
first. The summary line now names the arm.
check-focus re-recorded under the shared stream; figures move a point or two. What it buys
is that the bimodality analysis now reproduces the published means exactly — 2.31, 3.03,
0.40, 0.42 against the four rows CLEAN GROUND publishes. Under the old LCG it agreed to
within a decimal, which looked like corroboration and was two experiments landing near each
other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reading a narrated supporter fight showed it being hit on armR and head. It has wings; it
has never had arms. Three defects, all in the pipeline every published number comes from.
1. It located hits by species, not body plan — simulate.mjs read defender.species where
the game writes spec.bodyPlan ?? spec.species ?? "baseline" into speciesProfile
(build-packs.mjs:294). The barghest, kelpie and church grim were fought on two legs
with arms, and the supporter with no wings, so the fight its tactics call the one the
agents can win could not occur in a measured fight.
2. Armour was one scalar for the whole creature, and the harness's locations had no
armour field. Bare wings, the grounded halving and the vital exemption — R-254 and
R-255 — were invisible to every number. Worn armour was summed the same way, which put
a stab vest on a cleaner's head.
3. Nothing was ever grounded: a wing could be ruined and the creature kept flying.
Fixed in the game's order — locate, then apply what that location carries, every term
imported from rules.mjs. A ruined wing calls groundedPlanFor and the wounds carry across
by severity through remapLocationDamage, the function _preUpdate uses.
A bug of mine no guard would have caught: remapLocationDamage returns { damage, moved,
rescaled } and my first draft passed the whole object where a damage map was expected, so
every wound a creature carried was forgiven the moment it came down. check-lethality would
have passed it — fewer wounds means a longer fight, which reads as a number moving, and
this commit moves numbers. Found by probing a landing by hand.
20 of 47 creatures moved, 18 deadlier and 2 less. The supporter goes 69.3% -> 25.1% wiped,
3.38 -> 2.16 down, second deadliest to fourth: it was being measured as a 30-hit-point
creature in uniform armour 9 that could not be grounded. The small rises elsewhere are the
party's armour no longer covering locations it never protected.
check-focus then caught the page overclaiming, which is what it is for: focus fire still
helps in all 30 but only 28 clear their own noise where 31 of 31 did. The guard was
asserting more than the page needs — the bestiary prints that count from the artifact and
cannot overstate it — so it now checks only what the page asserts outright, and the page
rewrote itself to "in 28 of them".
Both baselines re-recorded. Minor version, not a patch: the published numbers changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R-258 put a tactic on a GM-facing page from an unguarded harness, R-259 found it worth
nothing, R-260 found the replacement right for the wrong reason. Three corrections, each
landing as prose with nothing checking it — the arrangement that let ddc4f99 describe a
game nobody was playing.
tools/focus-baseline.json records, per creature, the pack size and the spread-fire and
focus-fire win rates. check-focus re-measures all of it on every build and compares
exactly, with the party, seeds, run count and seeding scheme recorded alongside so numbers
taken under different conditions are refused rather than compared. The bestiary READS the
artifact instead of restating it, and check-bestiary refuses a page that has fallen behind
it. Page, guard and simulator cannot disagree.
The pack size is recorded rather than re-chosen: a fight at 0% or 100% cannot show an
effect, and a guard that picked again each run would let a changed creature move quietly
to a different question and pass. 31 of 47 creatures land in the measurable band; the
other 16 are recorded as pinned, with the rate that pinned them.
It guards the claim as well as the numbers. The page says focus fire helps in every fight
in doubt; check-focus fails if any row's gain reaches zero or stops clearing its own
noise. That failure means rewrite the page, not re-record the baseline.
The run count was chosen by evidence. 1000 x 3 seeds costs 8.5s and takes the suite from
2.5s to 13.7s. I tried 500 to halve it and the claim-check failed — at 500 runs one row
no longer clears its noise, so "without exception" is not supported by that much
sampling. Recording twice at 1000 gives byte-identical files.
Negative-tested four ways, all firing: a creature quietly made nimbler (redcap dodge
75 -> 85, spread 40.6% -> 27.6%), a baseline under different seeds, a hand-edited page,
and the claim failing at 500 runs.
Guard eleven (check-rollable) arrived from another session mid-build; this is twelve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>