diff --git a/tools/check-lethality.mjs b/tools/check-lethality.mjs index 626d81c..ad87df5 100644 --- a/tools/check-lethality.mjs +++ b/tools/check-lethality.mjs @@ -30,7 +30,7 @@ import { readFile, writeFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import path from "node:path"; -import { NPCS } from "./content.mjs"; +import { collectSpecs } from "./all-specs.mjs"; import { ROSTER } from "./roster.mjs"; import { measure, seedFor } from "./simulate.mjs"; @@ -77,9 +77,24 @@ const party = PARTY_KEYS.map(k => { return found; }); +/* WHAT IS MEASURED. + * + * Every npc-kind spec in the repository, read through collectSpecs() — the one list that + * check-creatures, make-portraits and mj-queue already share. This file used to import + * NPCS from content.mjs directly, which was the same thing until the 100-creature + * bestiary landed in its own files: after that it measured 47 of 147 and reported OK. + * A guard that cannot see two thirds of the creatures it guards is not a guard, and it + * failed silently, which is worse than failing loudly. + * + * Scenario casts come in with them. They were never measured before and they should have + * been: the Act Three standoff this repository publishes wipe rates for IS a scenario + * cast, and a rules change reaches a ticket inspector exactly as it reaches a barghest. + * 184 specs at roughly 47ms each is about nine seconds. */ +const SPECS = (await collectSpecs()).filter(r => r.kind === "npc").map(r => r.spec); + /** Solo, because a creature is the unit under test — counts are an encounter's business. */ const current = {}; -for (const spec of NPCS) { +for (const spec of SPECS) { const r = measure(party, [spec], { runs: RUNS, seed: seedFor(SEED, spec.key) }); current[spec.key] = { wipe: Number((r.wipeRate * 100).toFixed(1)), @@ -123,7 +138,7 @@ if (base.runs !== RUNS || base.seed !== SEED change reaches for Math.random or a Set iteration order, this says so instead of letting the whole guard degrade into noise-chasing. */ { - const probe = NPCS[0]; + const probe = SPECS[0]; const a = measure(party, [probe], { runs: RUNS, seed: seedFor(SEED, probe.key) }); const b = measure(party, [probe], { runs: RUNS, seed: seedFor(SEED, probe.key) }); const shape = r => JSON.stringify([r.wipeRate, r.downMean, r.roundsMedian, r.hurtMean]); diff --git a/tools/lethality-baseline.json b/tools/lethality-baseline.json index 70b4944..b6a3a3d 100644 --- a/tools/lethality-baseline.json +++ b/tools/lethality-baseline.json @@ -244,6 +244,691 @@ "wipe": 0, "down": 0.05, "rounds": 3 + }, + "hunt_master": { + "wipe": 22.4, + "down": 2.27, + "rounds": 11 + }, + "gabriel_hounds": { + "wipe": 0, + "down": 0.05, + "rounds": 2 + }, + "grey_mare": { + "wipe": 1.9, + "down": 0.89, + "rounds": 4 + }, + "black_annis": { + "wipe": 21.6, + "down": 2, + "rounds": 7 + }, + "cailleach": { + "wipe": 0, + "down": 0.01, + "rounds": 2 + }, + "jenny_greenteeth": { + "wipe": 0, + "down": 0.15, + "rounds": 2 + }, + "gwrach": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "hunky_punk": { + "wipe": 0.7, + "down": 0.68, + "rounds": 5 + }, + "sin_eater": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "lych_waiter": { + "wipe": 0, + "down": 0.13, + "rounds": 3 + }, + "crossroads_man": { + "wipe": 0.1, + "down": 0.41, + "rounds": 3 + }, + "march_stone": { + "wipe": 43.5, + "down": 2.72, + "rounds": 22 + }, + "foundation_child": { + "wipe": 0, + "down": 0, + "rounds": 3 + }, + "bog_offering": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "hearth_hob": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "cauld_lad": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "the_teind": { + "wipe": 0, + "down": 0.04, + "rounds": 4 + }, + "changeling": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "uninvited": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "dullahan": { + "wipe": 9.3, + "down": 1.52, + "rounds": 6 + }, + "pooka": { + "wipe": 9.7, + "down": 1.41, + "rounds": 4 + }, + "cu_sidhe": { + "wipe": 1.5, + "down": 0.91, + "rounds": 5 + }, + "tatterfoal": { + "wipe": 0, + "down": 0.03, + "rounds": 2 + }, + "dun_cow": { + "wipe": 82.9, + "down": 3.64, + "rounds": 10 + }, + "boobrie": { + "wipe": 4.5, + "down": 1.08, + "rounds": 4 + }, + "harbour_dog": { + "wipe": 0.1, + "down": 0.24, + "rounds": 3 + }, + "sea_mare": { + "wipe": 8.6, + "down": 1.36, + "rounds": 5 + }, + "tangie": { + "wipe": 3.1, + "down": 1.25, + "rounds": 7 + }, + "selkie": { + "wipe": 0, + "down": 0.07, + "rounds": 3 + }, + "wrack_gull": { + "wipe": 0, + "down": 0.01, + "rounds": 2 + }, + "sea_crow": { + "wipe": 0, + "down": 0.01, + "rounds": 1 + }, + "shellycoat": { + "wipe": 0.1, + "down": 0.23, + "rounds": 3 + }, + "peg_powler": { + "wipe": 0.1, + "down": 0.39, + "rounds": 3 + }, + "greenteeth": { + "wipe": 0.1, + "down": 0.4, + "rounds": 3 + }, + "fen_hound": { + "wipe": 0, + "down": 0.15, + "rounds": 3 + }, + "cut_walker": { + "wipe": 0, + "down": 0.13, + "rounds": 2 + }, + "wrackman": { + "wipe": 0.2, + "down": 0.52, + "rounds": 5 + }, + "net_mender": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "mudlark_child": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "hulk_haunt": { + "wipe": 0, + "down": 0.07, + "rounds": 4 + }, + "drowned_crew": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "lock_keeper": { + "wipe": 0.1, + "down": 0.41, + "rounds": 2 + }, + "the_undertow": { + "wipe": 52.8, + "down": 2.94, + "rounds": 14 + }, + "the_sump": { + "wipe": 12.5, + "down": 1.72, + "rounds": 8 + }, + "drowned_parish": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "the_fathom": { + "wipe": 88.5, + "down": 3.73, + "rounds": 14 + }, + "eelmother": { + "wipe": 15.6, + "down": 1.78, + "rounds": 8 + }, + "lamprey_host": { + "wipe": 0.1, + "down": 0.33, + "rounds": 3 + }, + "washland_cattle": { + "wipe": 4.7, + "down": 1.15, + "rounds": 5 + }, + "the_heron": { + "wipe": 0, + "down": 0.18, + "rounds": 2 + }, + "foldwalker": { + "wipe": 0.1, + "down": 0.34, + "rounds": 4 + }, + "anglesmith": { + "wipe": 0.1, + "down": 0.38, + "rounds": 4 + }, + "halfpast": { + "wipe": 0, + "down": 0.1, + "rounds": 3 + }, + "returned_surveyor": { + "wipe": 0, + "down": 0.03, + "rounds": 2 + }, + "unmeasured": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "patient_grasp": { + "wipe": 8, + "down": 1.76, + "rounds": 11 + }, + "the_tideline": { + "wipe": 0.9, + "down": 0.73, + "rounds": 6 + }, + "glass_auditor": { + "wipe": 0, + "down": 0.32, + "rounds": 6 + }, + "quorate": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "five_of_six": { + "wipe": 0.4, + "down": 0.65, + "rounds": 5 + }, + "the_remainder": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "counterpoint": { + "wipe": 0, + "down": 0, + "rounds": 3 + }, + "the_tally": { + "wipe": 0, + "down": 0, + "rounds": 3 + }, + "many_hands": { + "wipe": 0, + "down": 0.07, + "rounds": 4 + }, + "the_iteration": { + "wipe": 0.4, + "down": 0.53, + "rounds": 6 + }, + "backscatter": { + "wipe": 22.1, + "down": 2.42, + "rounds": 17 + }, + "afterimage": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "second_pass": { + "wipe": 0.1, + "down": 0.35, + "rounds": 4 + }, + "turnback": { + "wipe": 0.1, + "down": 0.25, + "rounds": 4 + }, + "the_overflight": { + "wipe": 14.1, + "down": 1.75, + "rounds": 6 + }, + "recurrence": { + "wipe": 0.2, + "down": 0.44, + "rounds": 5 + }, + "inside_out": { + "wipe": 91.9, + "down": 3.83, + "rounds": 10 + }, + "latecomer": { + "wipe": 0.8, + "down": 0.7, + "rounds": 5 + }, + "mirrorline": { + "wipe": 0.2, + "down": 0.62, + "rounds": 6 + }, + "carried_over": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "bull_azure": { + "wipe": 21.4, + "down": 1.98, + "rounds": 8 + }, + "enfield": { + "wipe": 0, + "down": 0.21, + "rounds": 3 + }, + "yale": { + "wipe": 1.4, + "down": 0.73, + "rounds": 4 + }, + "white_hart": { + "wipe": 0, + "down": 0.04, + "rounds": 4 + }, + "man_tyger": { + "wipe": 2.2, + "down": 1.03, + "rounds": 6 + }, + "melusine": { + "wipe": 0.1, + "down": 0.16, + "rounds": 3 + }, + "eagle_displayed": { + "wipe": 0.1, + "down": 0.24, + "rounds": 3 + }, + "gryphon": { + "wipe": 23.5, + "down": 2.15, + "rounds": 8 + }, + "sign_wyvern": { + "wipe": 1.2, + "down": 0.77, + "rounds": 5 + }, + "cockatrice": { + "wipe": 0, + "down": 0.01, + "rounds": 2 + }, + "martlet": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "piety_pelican": { + "wipe": 0, + "down": 0.01, + "rounds": 2 + }, + "drain_heron": { + "wipe": 0, + "down": 0.03, + "rounds": 2 + }, + "talbot": { + "wipe": 0.1, + "down": 0.34, + "rounds": 3 + }, + "gazehound": { + "wipe": 0, + "down": 0.04, + "rounds": 2 + }, + "bearward_bear": { + "wipe": 5.9, + "down": 1.41, + "rounds": 6 + }, + "heath_cow": { + "wipe": 64.2, + "down": 3.2, + "rounds": 10 + }, + "tup": { + "wipe": 0.1, + "down": 0.41, + "rounds": 3 + }, + "cade_lamb": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "hollow_hog": { + "wipe": 2.5, + "down": 1.01, + "rounds": 5 + }, + "hinny": { + "wipe": 0.1, + "down": 0.31, + "rounds": 3 + }, + "water_bull": { + "wipe": 63.2, + "down": 3.19, + "rounds": 13 + }, + "moor_cat": { + "wipe": 0.1, + "down": 0.24, + "rounds": 3 + }, + "gaunt_grey": { + "wipe": 0.1, + "down": 0.23, + "rounds": 3 + }, + "menagerie_ape": { + "wipe": 0.7, + "down": 0.6, + "rounds": 4 + }, + "tt_bewley": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "tt_prosser": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "tt_rance": { + "wipe": 0, + "down": 0.07, + "rounds": 2 + }, + "tt_shard": { + "wipe": 0, + "down": 0.03, + "rounds": 1 + }, + "tt_cordera_man": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "tt_tom_doyle": { + "wipe": 0, + "down": 0.06, + "rounds": 2 + }, + "tt_kell": { + "wipe": 0, + "down": 0, + "rounds": 2 + }, + "tt_vane": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "tt_bligh": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "tt_reyes": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "tt_merritt": { + "wipe": 0, + "down": 0.06, + "rounds": 2 + }, + "tt_ambrose": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "tt_coat": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "tt_writman": { + "wipe": 0, + "down": 0.06, + "rounds": 2 + }, + "bp_leah": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "bp_martin": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "bp_inspector": { + "wipe": 0, + "down": 0.07, + "rounds": 2 + }, + "bp_conductor": { + "wipe": 0, + "down": 0.25, + "rounds": 4 + }, + "bp_nia": { + "wipe": 0.1, + "down": 0.11, + "rounds": 2 + }, + "bp_callum": { + "wipe": 6.4, + "down": 1.4, + "rounds": 14 + }, + "bp_mara": { + "wipe": 0.1, + "down": 0.22, + "rounds": 2 + }, + "bp_owen": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "bp_elin": { + "wipe": 0, + "down": 0.01, + "rounds": 2 + }, + "bp_sennen": { + "wipe": 0, + "down": 0.02, + "rounds": 2 + }, + "la_enderby": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "la_sammy": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "la_marrow": { + "wipe": 0.1, + "down": 0.35, + "rounds": 3 + }, + "la_edith": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "la_nowak": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "la_tunnicliffe": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "od_kearsley": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "od_odame": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "od_vasey": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "od_ferrand": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "od_bewley": { + "wipe": 0, + "down": 0, + "rounds": 1 + }, + "so_warden": { + "wipe": 0, + "down": 0.04, + "rounds": 2 + }, + "so_teague": { + "wipe": 0, + "down": 0.07, + "rounds": 2 } } }