R-278: raising SCAN_RUNS found that SCAN_RUNS had never been read

Asked to raise the scan until the redcap's pack size stopped flipping. Measured
the threshold -- unstable at 3000 and 4000, stable across twenty seeds at 6000 --
raised it, and the re-record took fifteen seconds, which was impossible.

winRate takes three parameters and pickSize passed SCAN_RUNS as a fourth.
JavaScript discards it, so every scan has always run at RUNS and SCAN_RUNS has
never been read by anything. The fix I was asked to make was inert in the same
way as the thing it was fixing.

winRate takes runs now. The redcap is still n=3 with gain 6.3, arrived at stably
rather than luckily; the_arrears drops out of the measurable band at an honest
scan, 31 packs to 30; the_committee moves 2 to 6 and stays pinned. Claim check
still passes, bestiary regenerated.

The only signal was a number being too small. A fifteen-second re-record is good
news, and good news is what nobody investigates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-09-13 14:18:46 +01:00
co-authored by Claude Opus 5
parent 00e5a993cc
commit a7eca91a9c
5 changed files with 74 additions and 22 deletions
+1 -1
View File
@@ -193,7 +193,7 @@ check-creatures: OK — 102 actor specs across 8 sources, every characteristic,
check-powers: OK — 41 powers classified, 2 wired and each one measurably changes its fight, 14 recorded as beyond the harness with a reason, 25 not fight rules
check-anatomy: OK — 5 body plans, 36 locations, d20 complete in both modes, every location drawn, every kind handled and destructible, 20 plan changes carried 432 wounds with their severity intact, 1 flyer(s) with a survivable landing
check-lethality: OK — 47 creatures, every one fighting exactly as recorded against the frozen party
check-focus: OK — focus fire measured against 31 packs, helps in all of them, 31 of them by more than their own noise
check-focus: OK — focus fire measured against 30 packs, helps in all of them, 30 of them by more than their own noise
check-firstblood: OK — the first disabling blow is worth 40 points of wipe rate in the four-player cut, 15.4 in the six-a-side line
check-attackers: OK — three of the four-player cut disable on 11.1% of their attacks or better, and one of them on 0.1%
check-fight-tail: OK — a fight past 15 rounds wipes the party 1.4x as often in the four-player cut and 2.4x at six a side, and nothing was truncated
+1 -1
View File
@@ -88,7 +88,7 @@ measured in the simulator and came to nothing distinguishable from noise.
**What you can do is kill them one at a time.** Against a pack, every agent putting
their attacks into the same creature beats spreading them — in **every one** of the
31 fights in this book whose outcome was ever in doubt, without exception, and by more
30 fights in this book whose outcome was ever in doubt, without exception, and by more
than the measurement’s own noise each time. It is worth about
**8 points** of win rate against a pair and
**11 points** against three or more; the best case in the book is
+45
View File
@@ -6403,3 +6403,48 @@ fights solo, read the wipe column when the agents-down column was the one that m
explained a power by a mechanism that does not hold. Each was found by somebody asking me to
act on it. The entry is worth keeping in that state rather than tidying: it is the clearest
example in this log of confident, internally consistent, repeatedly wrong.
## R-278 — raising SCAN_RUNS found that SCAN_RUNS had never been read
R-277 left the redcap's recorded pack size one seed in eight from being 2 instead of 3, so
the instruction was to raise the scan until it stopped flipping. Measured rather than
guessed: still unstable at 3000 and 4000, stable across twenty seeds at 6000. Raised it.
**Then the re-record took fifteen seconds, which was impossible.** Fifteen times the scan
work cannot cost what 400 runs cost, so I read the call:
```js
const winRate = (foes, targets, seed) => { for (let i = 0; i < RUNS; i++) ...
...
const w = winRate(Array(n).fill(spec), "random", seedFor(...), SCAN_RUNS);
```
**Three parameters, four arguments.** JavaScript discards the fourth in silence, so every
scan this guard has ever run used `RUNS`, and `SCAN_RUNS` — declared, commented, passed —
has never been read by anything. Changing it from 400 to 6000 also did nothing, which is
how it surfaced: the fix I was asked to make was inert in exactly the way the thing it was
fixing was.
That is the defect this project names as its own signature — a value computed and never
read — living inside a guard, undetected by seventeen other guards, for as long as the
scan has existed. **No guard can catch it.** The constant is used at its one call site and
the call site looks correct; only the clock disagreed.
**What the honest scan changed.** `winRate` takes `runs` now, and the re-record costs 49
seconds instead of 15:
- **the redcap is still n=3, gain still 6.3** — the answer R-277 gave was right, and is now
arrived at stably rather than luckily.
- **the_arrears drops out of the measurable band**, 31 packs to 30. Its best size was chosen
on 1000 runs and does not survive 6000, which means it has been carrying a recorded focus
gain that the scan would not now stand behind.
- **the_committee moves n 2 to 6** and stays pinned either way, so nothing is measured there
and nothing changes.
**The claim check still passes**: focus fire helps in all 30, all of them clear of their own
noise. Bestiary regenerated, and it prints 30 where it printed 31.
**The lesson is not about arity.** It is that the only signal was a number being too small,
and I nearly did not look — a fifteen-second re-record is good news, and good news is what
nobody investigates. The same instinct that made the README quieter as the build got worse
(R-268) made this cheaper as the scan got weaker.
+14 -4
View File
@@ -51,7 +51,13 @@ const RUNS = 1000;
const SEEDS = [11, 4242, 90210];
const SEED_MODE = "per-creature";
const SIZES = [2, 3, 4, 5, 6];
const SCAN_RUNS = 400;
/* R-278. 400 was enough to find a size and not enough to find the SAME size twice. For
the redcap the criterion is near-tied — |win-50| is about 25 at two of them and 23 at
three — so a 400-run scan picked 2 instead of 3 roughly one seed in eight, and the
recorded gain moved 6.3 to 4.9 with it. Measured rather than guessed: the pick is still
unstable at 3000 and at 4000, and stable across twenty seeds at 6000. This is --update
only, so the build pays nothing; re-recording pays about half a minute. */
const SCAN_RUNS = 6000;
/* A pack whose spread-fire rate falls outside this cannot move enough to measure, and a
row that cannot move is a row that would pass this guard whatever happened to it. */
const MEASURABLE = [15, 85];
@@ -71,12 +77,16 @@ const party = PARTY_KEYS.map(k => {
to roll its own LCG, so the two guards described the same game with different dice and
no figure here could be reproduced from simulate.mjs. */
const rngFor = makeRng;
const winRate = (foes, targets, seed) => {
/* R-278: `runs` was not a parameter. pickSize has always passed SCAN_RUNS as a fourth
argument to a three-argument function, so JavaScript discarded it and every scan ran at
RUNS instead — the constant was declared, documented, passed and never read, which is
the defect this project keeps finding in itself, sitting inside a guard. */
const winRate = (foes, targets, seed, runs = RUNS) => {
let won = 0;
for (let i = 0; i < RUNS; i++) {
for (let i = 0; i < runs; i++) {
if (runFight(rngFor(seed + i * 2654435761), party, foes, { partyTargets: targets }).won) won++;
}
return (100 * won) / RUNS;
return (100 * won) / runs;
};
const mean = a => a.reduce((x, y) => x + y, 0) / a.length;
+13 -16
View File
@@ -94,11 +94,11 @@
},
"supporter": {
"n": 2,
"pinned": 4
"pinned": 4.7
},
"hollow_man": {
"n": 6,
"pinned": 91.8
"pinned": 92.3
},
"the_tenant": {
"n": 2,
@@ -106,7 +106,7 @@
},
"the_choir": {
"n": 6,
"pinned": 99.4
"pinned": 99
},
"precedent": {
"n": 3,
@@ -138,7 +138,7 @@
},
"quarantine_unit": {
"n": 2,
"pinned": 0.2
"pinned": 0.1
},
"cuckoo_array": {
"n": 2,
@@ -149,7 +149,7 @@
},
"long_walker": {
"n": 2,
"pinned": 0.1
"pinned": 0.6
},
"auditor": {
"n": 4,
@@ -175,7 +175,7 @@
},
"knockers": {
"n": 6,
"pinned": 99.7
"pinned": 99.8
},
"spriggan": {
"n": 3,
@@ -186,11 +186,11 @@
},
"nuckelavee": {
"n": 2,
"pinned": 8.2
"pinned": 8.5
},
"hedley_kow": {
"n": 6,
"pinned": 97.8
"pinned": 97.3
},
"apple_man": {
"n": 2,
@@ -200,8 +200,8 @@
"noise": 0.8
},
"the_committee": {
"n": 2,
"pinned": 100
"n": 6,
"pinned": 99.9
},
"night_registry": {
"n": 4,
@@ -244,7 +244,7 @@
},
"switchboard": {
"n": 6,
"pinned": 86.9
"pinned": 85.9
},
"the_census": {
"n": 3,
@@ -269,7 +269,7 @@
},
"the_stanchion": {
"n": 2,
"pinned": 5.8
"pinned": 5.2
},
"the_yield": {
"n": 2,
@@ -287,10 +287,7 @@
},
"the_arrears": {
"n": 2,
"spread": 14.7,
"focus": 21,
"gain": 6.3,
"noise": 1.5
"pinned": 14.4
},
"the_margin": {
"n": 5,