diff --git a/docs/BESTIARY.md b/docs/BESTIARY.md index a537ba4..ae13804 100644 --- a/docs/BESTIARY.md +++ b/docs/BESTIARY.md @@ -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 diff --git a/tools/bestiary.mjs b/tools/bestiary.mjs index e91fc7c..f51a057 100644 --- a/tools/bestiary.mjs +++ b/tools/bestiary.mjs @@ -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`