I asked for this on a false premise of my own: I reported that adding a
creature shifted a shared random stream and perturbed every other
creature's recorded number. That is not true and the code never did it.
measure() builds its own mulberry32 from the seed on every call, and
check-lethality calls it once per creature, so a creature's numbers do
not depend on its neighbours or its position. Measured rather than
argued: inserting a creature ahead of the barghest changes 0 of 47
existing entries. The file's own claim — "the only thing that can move
the number is a change to the rules or to the creature" — was accurate
all along, and my last commit message says otherwise. It is wrong.
The real cause, found by replaying each commit against the baseline as
committed at 4b71859:
4b71859 baseline recorded 0 of 46 differ
322389b bestiary 0 of 46 differ
ddc4f99 hit locations reach combat 26 of 46 differ <-- here
a90c4f3 .. e5dc9b5 26 of 46 differ
ddc4f99 routed every ordinary blow through the hit location table. That
is the largest change the combat system has had and it moved 26 of 46
creatures, which is correct and expected. What is not correct is that
nobody noticed for four commits: each creature moved by one or two
points, the guard allowed six, and it reported OK while describing a
game nobody was playing.
So the tolerance goes. It exists for sampling noise and there is no
sampling noise here — same party, same seed, same counts, and two
recordings of unchanged code are byte-identical. Anything that moves is
a real change, which is the entire point of the file. `rounds` is now
compared too; it was recorded and then never read, so a creature could
take a round longer to kill forever without a word.
Because exactness only means something if the measurement is exact, the
guard now proves it instead of assuming it: one creature measured twice
must come back identical, and it says so plainly if a future change
reaches for Math.random.
Negative-tested. A 2% change to locationMaxHp now trips 8 creatures at
+1.5 and +0.5 points of wipe rate — every one of which the old tolerance
would have passed. Breaking determinism is caught and named.
Also fixes update-readme, which advertised 8 guards while the build ran
9: check-anatomy was added without touching the list, which is precisely
what the comment above that list already warned had happened once. The
list is no longer trusted — it is checked against the `check` script in
package.json, and refuses to write a README advertising a different set
than the build runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>