Files
slaguru666andClaude Opus 5 f68b98190f bestiary + check-powers: read the seam, and classify what that reveals
bestiary.mjs built the document from NPCS, so it described 47 creatures of
147 while check-bestiary agreed with it — two things agreeing is not
verification when both read the same wrong source. It reads collectSpecs()
now: 184 creatures across 5 sections, every one with a measured lethality
figure, which only works because check-lethality was fixed first.

Three faults surfaced fixing it:

  - a second NPCS reference in focusParagraph()
  - which then broke on `rows`, because focusParagraph declares its own
    local `rows` for focus-fire data and shadowed the creature list. Keyed
    lookup now, so the shadowing cannot bite again.
  - the document grouped on the raw role tag against a list of four, and
    `if (!group.length) continue` DROPS an unlisted tag rather than
    mis-filing it. Nine of the twelve tags were absent, so those creatures
    would not have appeared at all. Twelve tags now map onto five sections,
    with a new "Beasts and heraldry", and a completeness assertion counts
    every creature into a section and fails if one is unplaced.

check-powers was the fifth consumer bypassing all-specs.mjs. It reconciled
its counts against BESTIARY while reading NPCS, so it called the document
wrong when the document was right.

Reading the real population then demanded 101 classifications. All 101 are
done and NOT ONE was wired. That is the right answer, not a shortfall:
the three available effects describe the creature — its own attack rating,
the damage it takes, whether it feels defence stacking — and essentially
every power is conditional on a place, a posture, a round count, a weapon
type, an attacker count, a named agent, or Coherence. R-275 records what
wiring one anyway costs: 25.1% to 75.2% on a single creature.

The near-misses are the evidence the reluctance was applied, not recited:
tangie's "HALF damage" is edged-only and ends in open flame; the_heron is
+30% or -20% depending on movement the harness does not have, so which
branch applies is undecidable; mirrorline's halving lands on an AGENT, not
on the creature, so wiring it would have protected the wrong combatant;
yale's defence waiver is capped at two attackers and its written counter is
a fourth agent, which defenceStacking:"ignores" cannot express.

Classifications live in four band files and are spread into POWERS last, so
a hand-written entry always wins a collision. 142 classified: 2 wired, 78
beyond the harness with a reason, 62 not fight rules.

All 23 guards pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 23:28:13 +01:00

130 lines
8.6 KiB
JavaScript

/**
* What a creature's POWER actually does in a fight.
*
* Every statblock in content.mjs carries its tactics as prose, and 41 of them contain a
* POWER. R-274 found that none of them reach `simulate.mjs`: the harness reads
* characteristics and weapons and has no idea a redcap ignores the thing that makes every
* other defender worse. So the redcap has been measured — in check-lethality, in
* check-focus, in every figure this project has published about it — as a creature that
* tires, which its own entry says it does not.
*
* This is the classification, not a second copy of the prose. content.mjs remains the
* text; this says which of those powers are fight rules, which of the fight rules the
* harness can express, and — for the ones it cannot — why, in one line, so the gap is a
* recorded decision rather than an absence.
*
* wired the simulator applies it; `effect` says how
* outOfCombat not a fight rule at all — a scene, a social problem, a clock
* notSimulable a fight rule the harness cannot express, with the reason
*
* check-powers refuses any POWER in content.mjs that is not in one of the three, so a
* creature added tomorrow cannot quietly arrive unclassified. That is the whole point:
* the defect was not that the powers were unimplemented, it was that nothing said so.
*
* EFFECTS THE SIMULATOR UNDERSTANDS
* defenceStacking: "ignores" the cumulative penalty never applies to its defences
* damageFactor: n damage it takes is multiplied by n, floored
* attackFactor: n its attack rating is multiplied by n, floored
*/
import { POWERS_FOLKLORE } from "./powers-folklore.mjs";
import { POWERS_WATER } from "./powers-water.mjs";
import { POWERS_ANOMALOUS } from "./powers-anomalous.mjs";
import { POWERS_BEASTS } from "./powers-beasts.mjs";
/* The bestiary's own classifications, one file per band, merged here.
*
* Kept in separate files for the same reason the creatures are: four of them were
* written at once, and a single shared object literal is the one thing concurrent
* authors cannot both edit. Spread last so this file's hand-written entries always win
* a key collision — the originals were argued over and measured, and a band file must
* not silently take one of them over.
*
* Not one of the 99 was wired. That is the correct outcome rather than a shortfall:
* every effect the harness offers describes the CREATURE — its own attack rating, the
* damage it takes, whether it feels the defence-stacking penalty — and essentially every
* power in the bestiary is conditional on a place, a posture, a round count, a weapon
* type, an attacker count, a named agent, or Coherence. R-275 is what wiring one of
* those anyway costs: 25.1% to 75.2% on a single creature. */
export const POWERS = {
/* ---- wired: the harness applies these ---- */
redcap: { name: "NOT TIRED", kind: "wired", effect: { defenceStacking: "ignores" },
note: "every parry it makes is at full skill" },
the_courier: { name: "THE CONSIGNMENT", kind: "wired", effect: { attackFactor: 0.5 },
note: "it will not fight with the carrying limb, so it attacks at half until the consignment is taken" },
/* ---- notSimulable: fight rules the harness cannot express ---- */
/* Wired first, and reverted on the measurement. Halving everything the supporter takes
tripled its wipe rate — 25.1% to 75.2% against the frozen party — because the harness
has no ground and so applied "while it stands on ground the borough holds"
unconditionally. That is a 50-point swing resting on an assumption the harness cannot
state, which is the same reason THE PULL is not wired: a power conditional on a place
cannot be simulated by something with no places in it. R-275. */
supporter: { name: "ARGENT AND GULES", kind: "notSimulable",
why: "half damage only on ground the borough holds, and the harness has no ground — wiring it "
+ "unconditionally moved its wipe rate 25.1% to 75.2%, which is the assumption speaking" },
the_choir: { name: "ONE VOICE", kind: "notSimulable", why: "costs Coherence, which the harness does not model" },
hollow_man: { name: "THE FILE IS YOURS NOW", kind: "notSimulable", why: "an opposed POWx5 whose stake is Coherence, which the harness does not model" },
carrion_file: { name: "AMENDMENT", kind: "notSimulable", why: "removes a trained skill permanently; the harness has no character to amend" },
the_understudy: { name: "REHEARSED", kind: "notSimulable", why: "acts before the agent it is copying, and the harness has no notion of which agent that is" },
the_predecessor: { name: "HANDOVER", kind: "notSimulable", why: "takes an agent's action for a task the harness does not model" },
grindylow: { name: "THE PULL", kind: "notSimulable", why: "only in water, and resolves as a grapple into the ASPHYXIA rules" },
nuckelavee: { name: "THE BLIGHT", kind: "notSimulable", why: "a CONx5 each round for 1 point; the harness rolls no characteristics" },
surveyor: { name: "ALREADY SURVEYED", kind: "notSimulable", why: "cover and ambush, neither of which the harness has" },
spriggan: { name: "WHAT IT REALLY IS", kind: "notSimulable", why: "runs at a smaller statblock until the barrow is disturbed, which is a scene state" },
long_walker: { name: "DISTANCE MEANS NOTHING", kind: "notSimulable", why: "damage buys movement rather than injury; the harness has no ground" },
the_stanchion: { name: "DRIVEN", kind: "notSimulable", why: "its armour is already on the statblock; the rest is about a crossing" },
the_margin: { name: "NOT MENTIONED", kind: "notSimulable", why: "a Spot penalty for looking, and the harness never looks" },
the_relay: { name: "WHILE QUORATE", kind: "notSimulable", why: "body count holds a crossing open; nothing about the fight itself" },
/* ---- outOfCombat: not fight rules ---- */
barghest: { name: "THE SUMMONS", kind: "outOfCombat" },
kelpie: { name: "ADHESION", kind: "outOfCombat", why: "its own entry says run ASPHYXIA and not a combat" },
boggart: { name: "THE WRONG NAME", kind: "outOfCombat" },
churchgrim: { name: "THE GROUND IS MINE", kind: "outOfCombat" },
the_tenant: { name: "TENANCY", kind: "outOfCombat" },
precedent: { name: "COUNTERSIGNED", kind: "outOfCombat" },
quarantine_unit: { name: "STERILISATION ORDER", kind: "outOfCombat" },
cuckoo_array: { name: "HOUSE RULES", kind: "outOfCombat" },
auditor: { name: "THE DISCREPANCY", kind: "outOfCombat" },
washer_ford: { name: "THE COLLAR", kind: "outOfCombat" },
lantern_man: { name: "THE SHORTER WAY", kind: "outOfCombat" },
knockers: { name: "THE KNOCK", kind: "outOfCombat" },
hedley_kow: { name: "THE SWAP", kind: "outOfCombat" },
apple_man: { name: "THE RECKONING", kind: "outOfCombat" },
the_committee: { name: "RESOLVED", kind: "outOfCombat" },
night_registry: { name: "CORRECTLY FILED", kind: "outOfCombat" },
the_locum: { name: "OF COURSE", kind: "outOfCombat" },
lost_property: { name: "UNCLAIMED", kind: "outOfCombat" },
the_minutes: { name: "AS AGREED", kind: "outOfCombat" },
waiting_room: { name: "SHORTLY", kind: "outOfCombat" },
switchboard: { name: "PUTTING YOU THROUGH", kind: "outOfCombat" },
the_census: { name: "ENUMERATED", kind: "outOfCombat", why: "its own entry says the threat is not a combat one" },
the_yield: { name: "THE COUNT", kind: "outOfCombat" },
the_overwriter: { name: "THE CORRECTION", kind: "outOfCombat" },
the_arrears: { name: "THE PRINCIPAL", kind: "outOfCombat" },
/* The starter's two. Unclassified until check-powers began reading the shared spec
list rather than NPCS — they are scenario cast, so the guard had never seen them.
Both happen during a fight and neither can be expressed: the harness has no
documents to present and no doors to seal. */
bp_inspector: { name: "NO DESTINATION SHOWN", kind: "notSimulable",
why: "it will not strike anyone formally offering a document — a ticket, a warrant card held flat — "
+ "and the harness has nothing to offer and no notion of a round spent offering it" },
bp_conductor: { name: "ALL PASSENGERS REMAIN ABOARD", kind: "notSimulable",
why: "once per fight it seals every door in a carriage, and the harness has no doors, no carriage "
+ "and nowhere to be shut out of; opening one costs an action it also cannot spend" },
...POWERS_FOLKLORE,
...POWERS_WATER,
...POWERS_ANOMALOUS,
...POWERS_BEASTS
};
export const EFFECT_KINDS = ["defenceStacking", "damageFactor", "attackFactor"];
/** The fight effect for a creature, or null. Only `wired` powers have one. */
export function combatPowerFor(key) {
const p = POWERS[key];
return p && p.kind === "wired" ? p.effect : null;
}