diff --git a/README.md b/README.md index 60783eb..b5f4483 100644 --- a/README.md +++ b/README.md @@ -187,17 +187,17 @@ check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resol check-lang: OK — en.json, 1018 keys, no leaf/branch collisions check-templates: OK — 19 templates compile check-behaviour: OK — 76 behavioural tests -check-scenarios: OK — 13 scenario files, 83 mechanic tags, every skill named resolves against 62 catalogue entries -check-rollable: OK — 82 rolls across 13 scenario files, every one reachable at 25% or better by somebody present +check-scenarios: OK — 14 scenario files, 83 mechanic tags, every skill named resolves against 62 catalogue entries +check-rollable: OK — 82 rolls across 14 scenario files, every one reachable at 25% or better by somebody present check-creatures: OK — 102 actor specs across 8 sources, every characteristic, skill, kit key, talent, species and style resolves, no duplicate keys 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 30 packs, helps in all of them, 30 of them by more than their own noise +check-focus: OK — focus fire measured against 38 packs, helps in all of them, 36 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 -check-cited: OK — 25 cited figures across 13 scenario files resolve against 3 baselines +check-cited: OK — 25 cited figures across 14 scenario files resolve against 3 baselines check-handouts: OK — 4 handouts, the two almanac pages identical in structure and columns, every line 41 miles from clean ground, nothing under 11pt check-bestiary: OK — 47 creatures, the document matches the game ``` diff --git a/docs/BESTIARY.md b/docs/BESTIARY.md index 44252e6..12e3202 100644 --- a/docs/BESTIARY.md +++ b/docs/BESTIARY.md @@ -87,11 +87,11 @@ rule for holding an action. Arranging the party so its best weapon fires last wa 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 -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 +their attacks into the same creature beats spreading them — in 38 of the +38 fights in this book whose outcome was ever in doubt, and by more +than the measurement’s own noise in 36 of them. It is worth about +**6 points** of win rate against a pair and +**10 points** against three or more; the best case in the book is 3 × Kelpie, which goes 52.0% to 67.1%. Most of that is not the ladder above: a creature that is dead stops attacking, and one on half its hit points does not attack any less. Held at a fixed pack size the dodge ladder is worth a point or two. diff --git a/docs/REVIEW_LOG.md b/docs/REVIEW_LOG.md index d6ea9ff..be80855 100644 --- a/docs/REVIEW_LOG.md +++ b/docs/REVIEW_LOG.md @@ -6492,3 +6492,50 @@ unasked. **Nothing published quoted the lost row.** The bestiary prints the above-noise count from the artifact, so it moved 31 to 30 on its own and says nothing that is now false. + +## R-280 — the band replaced by the test it was standing in for + +`MEASURABLE = [15, 85]` was a proxy. The question it answered is *can this fight move at +all* — a rate pinned at 0 or 100 cannot show an effect of any size — and it answered with +two round numbers. R-279 found it excluding the_arrears by three tenths of a point while +that row carried a gain of 6.2 at 2.4x its own noise. + +**The direct form, in the units this guard already uses:** a fight is measurable if its +spread-fire rate stands clear of both ends by more than its own seed-to-seed noise. + +**It deliberately does not look at the gain.** Admitting the sizes where focus fire happens +to clear its noise would make this guard's own claim true by construction, which is the +worst thing a guard can be. Size is still chosen on nearest-an-even-fight, and admission is +still decided without reference to the effect being measured. The order changed — the row +is measured first and admitted second, because the noise the test needs is a product of the +measurement rather than of the scan. + +**31 measurable became 38.** the_arrears is back at gain 6.3, and seven others arrive: + +| | n | spread | gain | noise | +|---|---|---|---|---| +| switchboard | 6 | 86.3% | **8.1** | 1.4 | +| the_arrears | 2 | 14.7% | **6.3** | 1.5 | +| nuckelavee | 2 | 8.2% | **5.9** | 0.5 | +| hollow_man | 6 | 92.2% | **4.6** | 0.8 | +| supporter | 2 | 4.5% | 2.6 | 0.8 | +| hedley_kow | 6 | 97.7% | 1.6 | 0.6 | +| the_stanchion | 2 | 6.0% | 0.2 | 0.9 | +| the_choir | 6 | 99.3% | 0.2 | 0.4 | + +Four of the eight were carrying effects larger than most of the rows the band already +admitted. **switchboard at 8.1 is the second largest gain in the artifact and the band was +throwing it away**, because an 86.3% fight is one point past a round number. + +**And the honest part: the last two do not clear their own noise.** the_choir has 0.7 points +of headroom at 99.3% and used 0.2 of it; the_stanchion has six points of room and used 0.2. +The above-noise count falls 31 of 31 to 36 of 38, and the bestiary now prints 36. That is +not a regression — it is two measurements saying *no detectable effect*, which is a result +the band suppressed by refusing to take it. A guard that only records the cases where +something happens is not measuring, it is confirming. + +**What is now weaker, and worth watching.** The hard claim — focus fire helps in every +measurable pack — is satisfied by gains of 0.2, so it is a weaker sentence than it was when +the band admitted only contested fights. If it should be a stronger claim, the place to +strengthen it is the claim, not the admission rule; tightening admission to protect a claim +is how the band got here. diff --git a/tools/check-focus.mjs b/tools/check-focus.mjs index 31071a4..1490d9d 100644 --- a/tools/check-focus.mjs +++ b/tools/check-focus.mjs @@ -58,9 +58,23 @@ const SIZES = [2, 3, 4, 5, 6]; 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]; +/* R-280. This was a band, [15, 85], and it was a PROXY for the question that matters: + can this fight move at all? A rate pinned at 0 or 100 cannot show an effect of any size, + so measuring one is pointless. The band answered that with two round numbers, and + the_arrears — 14.2% at the only size worth fighting — was excluded by three tenths of a + point while carrying a gain of 6.2 at 2.4x its own noise. A proxy that disagrees with + the thing it proxies for is worth replacing rather than tuning. + + So the test is now direct and expressed in the same units as everything else here: a + fight is measurable if its spread-fire rate stands clear of BOTH ends by more than its + own seed-to-seed noise. That is the actual condition for an effect to be visible. + + IT DELIBERATELY DOES NOT LOOK AT THE GAIN. Selecting the sizes where focus fire happens + to clear its noise would make this guard's own claim — that focus fire helps in every + measurable pack — true by construction, which is the worst thing a guard can be. Size is + still chosen on nearest-an-even-fight, and admission is still decided without reference + to the effect being measured. */ +const measurable = row => row.spread - row.noise > 0 && row.spread + row.noise < 100; const party = PARTY_KEYS.map(k => { const found = ROSTER.find(r => r.key === k); @@ -117,22 +131,25 @@ const pickSize = spec => { if (UPDATE) { const creatures = {}; - let measurable = 0; + let measurableCount = 0; for (const spec of NPCS) { const pick = pickSize(spec); - if (pick.w < MEASURABLE[0] || pick.w > MEASURABLE[1]) { - creatures[spec.key] = { n: pick.n, pinned: Number(pick.w.toFixed(1)) }; + /* Measured first, then admitted or not: the noise this test needs is a product of the + measurement, so unlike the band it cannot be decided from the scan alone. */ + const row = measureAt(spec, pick.n); + if (!measurable(row)) { + creatures[spec.key] = { n: pick.n, pinned: row.spread }; continue; } - creatures[spec.key] = measureAt(spec, pick.n); - measurable++; + creatures[spec.key] = row; + measurableCount++; } await writeFile(BASELINE, JSON.stringify({ note: "Generated by tools/check-focus.mjs --update. Do not edit by hand.", party: PARTY_KEYS, runs: RUNS, seeds: SEEDS, seedMode: SEED_MODE, - measurableBand: MEASURABLE, creatures + admission: "spread clear of 0 and 100 by more than its own noise", creatures }, null, 2) + "\n", "utf8"); - console.log(`check-focus: baseline recorded — ${measurable} of ${NPCS.length} creatures measurable, ` + console.log(`check-focus: baseline recorded — ${measurableCount} of ${NPCS.length} creatures measurable, ` + `${RUNS} runs x ${SEEDS.length} seeds, seed ${SEEDS[0]} ${SEED_MODE}`); process.exit(0); } diff --git a/tools/focus-baseline.json b/tools/focus-baseline.json index 6649217..38248cd 100644 --- a/tools/focus-baseline.json +++ b/tools/focus-baseline.json @@ -13,10 +13,7 @@ 90210 ], "seedMode": "per-creature", - "measurableBand": [ - 15, - 85 - ], + "admission": "spread clear of 0 and 100 by more than its own noise", "creatures": { "quiet_neighbours_npc": { "n": 6, @@ -94,11 +91,17 @@ }, "supporter": { "n": 2, - "pinned": 4.7 + "spread": 4.5, + "focus": 7, + "gain": 2.6, + "noise": 0.8 }, "hollow_man": { "n": 6, - "pinned": 92.3 + "spread": 92.2, + "focus": 96.8, + "gain": 4.6, + "noise": 0.8 }, "the_tenant": { "n": 2, @@ -106,7 +109,10 @@ }, "the_choir": { "n": 6, - "pinned": 99 + "spread": 99.3, + "focus": 99.5, + "gain": 0.2, + "noise": 0.4 }, "precedent": { "n": 3, @@ -149,7 +155,7 @@ }, "long_walker": { "n": 2, - "pinned": 0.6 + "pinned": 0.4 }, "auditor": { "n": 4, @@ -186,11 +192,17 @@ }, "nuckelavee": { "n": 2, - "pinned": 8.5 + "spread": 8.2, + "focus": 14.2, + "gain": 5.9, + "noise": 0.5 }, "hedley_kow": { "n": 6, - "pinned": 97.3 + "spread": 97.7, + "focus": 99.2, + "gain": 1.6, + "noise": 0.6 }, "apple_man": { "n": 2, @@ -201,7 +213,7 @@ }, "the_committee": { "n": 6, - "pinned": 99.9 + "pinned": 100 }, "night_registry": { "n": 4, @@ -244,7 +256,10 @@ }, "switchboard": { "n": 6, - "pinned": 85.9 + "spread": 86.3, + "focus": 94.4, + "gain": 8.1, + "noise": 1.4 }, "the_census": { "n": 3, @@ -269,7 +284,10 @@ }, "the_stanchion": { "n": 2, - "pinned": 5.2 + "spread": 6, + "focus": 6.3, + "gain": 0.2, + "noise": 0.9 }, "the_yield": { "n": 2, @@ -287,7 +305,10 @@ }, "the_arrears": { "n": 2, - "pinned": 14.4 + "spread": 14.7, + "focus": 21, + "gain": 6.3, + "noise": 1.5 }, "the_margin": { "n": 5,