41 statblocks carry a POWER in their tactics and simulate.mjs read none of them, so a redcap that ignores the cumulative defence penalty has been measured as a creature that tires -- in check-lethality, in check-focus, and in every figure published about it. The defect was not that the powers were unimplemented, it was that nothing said they were not. powers.mjs classifies all 41: 2 wired, 14 notSimulable with a stated reason, 25 not fight rules. check-powers refuses an unclassified POWER and refuses a notSimulable without a reason -- and it does not test that the harness imports a power, it fights the creature with and without and requires the two to disagree. Moved: the courier 9.4% to 1.0% wiped (it attacks at half while carrying), the redcap 0.7% to 0.9% (small, because these fights rarely spend a second defence). ARGENT AND GULES was wired and then un-wired: it tripled the supporter's wipe rate to 75.2% because the harness has no ground and applied the borough-ground condition unconditionally. Same reason THE PULL is not wired. I had wired one and refused the other on identical facts. Lethality and focus re-recorded, bestiary regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
6.5 KiB
JavaScript
95 lines
6.5 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
|
|
*/
|
|
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" }
|
|
};
|
|
|
|
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;
|
|
}
|