The bestiary was still describing the fight R-275 changed

Two things in the generated document had gone stale the moment powers reached the
simulator, and eighteen guards were green over both because nothing connects
powers.mjs to bestiary.mjs.

The dodge-ladder section listed Redcap among "the ones that take the most
stripping" and counted it in "across 47 creatures", when NOT TIRED means it never
spends a defence at all -- the exact opposite of what its own entry says three
pages down. It is off the ladder now, the count reads 46 that spend defences, and
the page names it: stripping is not a plan against it, killing it is.

And the paragraph listing what the harness does and does not model never mentioned
that it fights 39 of the 41 creatures with a power without it. It now says so, and
counts from powers.mjs rather than stating it, including the 14 that are fight
rules it cannot express -- so every figure for one of those is the creature with
its best trick taken away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
slaguru666
2026-09-13 14:55:36 +01:00
co-authored by Claude Opus 5
parent 97f5cbbcae
commit 81ff44e70e
2 changed files with 40 additions and 4 deletions
+11 -2
View File
@@ -71,7 +71,7 @@ costs it **30 points** on the next one, and the whole thing resets when the
round does. The median dodge in this book is 45%, which goes **45%** → **15%** → **1%**
and is down to the floor after 2 landing hits.
Across 47 creatures: **13** are stripped by 1 landing hit, **25** are stripped by 2 landing hits, **9** are stripped by 3 landing hits.
Across 46 creatures that spend defences: **13** are stripped by 1 landing hit, **25** are stripped by 2 landing hits, **8** are stripped by 3 landing hits.
Two things follow:
@@ -100,13 +100,15 @@ attack any less. Held at a fixed pack size the dodge ladder is worth a point or
A pack is measured when its spread-fire odds stand clear of 0% and 100% by more than its own noise, so a fight nobody could lose
and a fight nobody could win are both left out.)*
**Redcap is not on this ladder at all.** NOT TIRED means it never spends a defence: every dodge it makes is at 75%, however many have landed that round. Stripping is not a plan against it — killing it is.
The ones that take the most stripping:
- **The margin** — dodge 80%, 3 landing hits to strip
- **Redcap** — dodge 75%, 3 landing hits to strip
- **Lantern man** — dodge 75%, 3 landing hits to strip
- **Hedley Kow** — dodge 70%, 3 landing hits to strip
- **The courier** — dodge 70%, 3 landing hits to strip
- **The Quiet Neighbours** — dodge 68%, 3 landing hits to strip
It runs the other way too: agents defend on the same ladder, which is why a second
attacker in a round is worth so much more than the first, and why anything that gets
@@ -832,6 +834,13 @@ clock. It does **not** model bleeding, panic, Coherence, cover, range bands or f
modes, and every one of those makes a fight worse for whoever is losing. Read all of it
as a floor.
**And it fights most of these creatures without their powers.** 2 of the
41 POWERS in this book are in the
simulator; **14** are fight rules it cannot express — a grapple into
drowning, a Coherence drain, an initiative it takes by right — and the rest are not
fight rules at all. Every figure above for one of those 14 is the
creature with its best trick taken away. `tools/powers.mjs` says which is which and why.
## A known gap
22 creatures fight with nothing but their own bodies and still carry
+29 -2
View File
@@ -25,6 +25,11 @@ import { writeFile, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import path from "node:path";
import { NPCS } from "./content.mjs";
import { POWERS, combatPowerFor } from "./powers.mjs";
const wiredPowers = Object.values(POWERS).filter(p => p.kind === "wired").length;
const unwiredFightPowers = Object.values(POWERS).filter(p => p.kind === "notSimulable").length;
const outOfCombatPowers = Object.values(POWERS).filter(p => p.kind === "outOfCombat").length;
import { hitPointsFor, majorWoundFor, locationsFor,
resolveBands, defencePenaltyFor, DEFENCE_STEP } from "../rules.mjs";
import { buildCombatant } from "./simulate.mjs";
@@ -185,7 +190,16 @@ function focusParagraph() {
return 8;
};
const all = rows.map(r => ({ name: r.spec.name, d: dodgeOf(r.spec) })).filter(x => x.d > 0);
/* A creature whose power exempts it from the stacking penalty is not on this ladder at
all, and listing it among the ones that "take the most stripping" told a GM the exact
opposite of what its own entry says three pages down. R-275 wired NOT TIRED into the
simulator and left this section describing the behaviour it replaced. */
const exempt = rows.map(r => r.spec)
.filter(sp => combatPowerFor(sp.key)?.defenceStacking === "ignores")
.map(sp => ({ name: sp.name, d: dodgeOf(sp), power: POWERS[sp.key].name }));
const exemptNames = new Set(exempt.map(x => x.name));
const all = rows.map(r => ({ name: r.spec.name, d: dodgeOf(r.spec) }))
.filter(x => x.d > 0 && !exemptNames.has(x.name));
const sorted = [...all].sort((a, b) => a.d - b.d);
const median = sorted[Math.floor(sorted.length / 2)].d;
const ladder = [0, 1, 2].map(u => effAfter(median, u));
@@ -199,7 +213,7 @@ function focusParagraph() {
+ `round does. The median dodge in this book is ${median}%, which goes `
+ `${ladder.map(v => `**${v}%**`).join(" → ")}\n`
+ `and is down to the floor after ${stripsIn(median)} landing hits.\n\n`
+ `Across ${all.length} creatures: `
+ `Across ${all.length} creatures that spend defences: `
+ Object.keys(spread).sort().map(k => `**${spread[k]}** are stripped by ${k} landing hit${k === "1" ? "" : "s"}`).join(", ")
+ `.\n\n`
+ `Two things follow:\n\n`
@@ -213,6 +227,11 @@ function focusParagraph() {
+ `rule for holding an action. Arranging the party so its best weapon fires last was\n`
+ `measured in the simulator and came to nothing distinguishable from noise.\n\n`
+ focusParagraph()
+ (exempt.length
? exempt.map(x => `**${esc(x.name)} is not on this ladder at all.** ${x.power} means it `
+ `never spends a defence: every dodge it makes is at ${x.d}%, however many have landed `
+ `that round. Stripping is not a plan against it — killing it is.\n\n`).join("")
: "")
+ `The ones that take the most stripping:\n\n`
+ hardest.map(x => `- **${esc(x.name)}** — dodge ${x.d}%, ${stripsIn(x.d)} landing hits to strip\n`).join("")
+ `\nIt runs the other way too: agents defend on the same ladder, which is why a second\n`
@@ -265,6 +284,14 @@ md += `## Reading the measured column\n\n`
+ `clock. It does **not** model bleeding, panic, Coherence, cover, range bands or fire\n`
+ `modes, and every one of those makes a fight worse for whoever is losing. Read all of it\n`
+ `as a floor.\n\n`
/* Counted from powers.mjs rather than stated, because the last time this paragraph
described what the harness could do it was a year out of date within a session. */
+ `**And it fights most of these creatures without their powers.** ${wiredPowers} of the\n`
+ `${wiredPowers + unwiredFightPowers + outOfCombatPowers} POWERS in this book are in the\n`
+ `simulator; **${unwiredFightPowers}** are fight rules it cannot express — a grapple into\n`
+ `drowning, a Coherence drain, an initiative it takes by right — and the rest are not\n`
+ `fight rules at all. Every figure above for one of those ${unwiredFightPowers} is the\n`
+ `creature with its best trick taken away. \`tools/powers.mjs\` says which is which and why.\n\n`
+ `## A known gap\n\n`
+ `${unmodelled.length} creatures fight with nothing but their own bodies and still carry\n`
+ `baseline-human anatomy. Some of those are people and that is fine. Several plainly are\n`