Add check-seam: one definition of the NPC population
collectSpecs() in tools/all-specs.mjs is the single place that decides which specs exist. Nine modules were importing NPCS and PREGENS straight from content.mjs instead, so each carried its own idea of the population and measured a different subset of the game. check-lethality was the clearest case: it scored 47 creatures and reported OK for all 184. check-seam (guard 24, first in the suite) scans every module and fails if anything but all-specs.mjs names NPCS or PREGENS in a content.mjs import. The nine violators are re-pointed at the seam. Re-recording check-focus's baseline against the full 184 raised it from 47 packs to 138 and surfaced one creature focus fire does not help: the dun cow. Rather than re-record that away, the guard now requires the advice section of BESTIARY.md to name every such exception, and bestiary.mjs generates the sentence. The first version of that check asked whether the name appeared anywhere in BESTIARY.md, which every creature's own heading satisfies — deleting the exception sentence still passed. It reads only the "Shooting at something that moves" section now, and the mutation test fails as it should. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f68b98190f
commit
8dbe885a34
+6
-6
@@ -224,14 +224,14 @@ rule for holding an action. Arranging the party so its best weapon fires last wa
|
||||
measured in the simulator and came to nothing distinguishable from noise.
|
||||
|
||||
**What you can do is kill them one at a time.** Against a pack, every agent putting
|
||||
their attacks into the same creature beats spreading them — in 38 of the
|
||||
38 packs in this book whose odds leave room for a difference to show, and by more
|
||||
than the measurement’s own noise in 36 of them. It is worth about
|
||||
their attacks into the same creature beats spreading them — in 137 of the
|
||||
138 packs in this book whose odds leave room for a difference to show, and by more
|
||||
than the measurement’s own noise in 130 of them. It is worth about
|
||||
**6 points** of win rate against a pair and
|
||||
**10 points** against three or more; the best case in the book is
|
||||
3 × Kelpie, which goes 52.0% to 67.1%. Most of that is not the ladder
|
||||
**11 points** against three or more; the best case in the book is
|
||||
6 × Counterpoint, which goes 40.2% to 60.7%. Most of that is not the ladder
|
||||
above: a creature that is dead stops attacking, and one on half its hit points does not
|
||||
attack any less. Held at a fixed pack size the dodge ladder is worth a point or two.
|
||||
attack any less. Held at a fixed pack size the dodge ladder is worth a point or two. The exception is **The dun cow**: against that one, concentrating fire is worth nothing measurable, so spread out and use the room instead.
|
||||
|
||||
*(1000 fights × 3 seeds per arm, re-measured by `check-focus` on every build.
|
||||
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
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"play": "node tools/playthrough.mjs",
|
||||
"mj": "node tools/mj-queue.mjs",
|
||||
"simulate": "node tools/simulate.mjs",
|
||||
"check": "bun tools/check-rules.mjs && bun tools/check-kits.mjs && bun tools/check-lang.mjs && bun tools/check-templates.mjs && bun tools/check-behaviour.mjs && bun tools/check-scenarios.mjs && bun tools/check-rollable.mjs && bun tools/check-outcomes.mjs && bun tools/check-creatures.mjs && bun tools/check-powers.mjs && bun tools/check-anatomy.mjs && bun tools/check-lethality.mjs && bun tools/check-focus.mjs && bun tools/check-firstblood.mjs && bun tools/check-attackers.mjs && bun tools/check-fight-tail.mjs && bun tools/check-packs.mjs && bun tools/detention-tables.mjs --check && bun tools/bellhouse-plans.mjs --check && bun tools/check-cited.mjs && bun tools/check-figures.mjs && bun tools/check-handouts.mjs && bun tools/check-bestiary.mjs",
|
||||
"check": "bun tools/check-seam.mjs && bun tools/check-rules.mjs && bun tools/check-kits.mjs && bun tools/check-lang.mjs && bun tools/check-templates.mjs && bun tools/check-behaviour.mjs && bun tools/check-scenarios.mjs && bun tools/check-rollable.mjs && bun tools/check-outcomes.mjs && bun tools/check-creatures.mjs && bun tools/check-powers.mjs && bun tools/check-anatomy.mjs && bun tools/check-lethality.mjs && bun tools/check-focus.mjs && bun tools/check-firstblood.mjs && bun tools/check-attackers.mjs && bun tools/check-fight-tail.mjs && bun tools/check-packs.mjs && bun tools/detention-tables.mjs --check && bun tools/bellhouse-plans.mjs --check && bun tools/check-cited.mjs && bun tools/check-figures.mjs && bun tools/check-handouts.mjs && bun tools/check-bestiary.mjs",
|
||||
"test": "bun run check",
|
||||
"readme": "bun tools/update-readme.mjs",
|
||||
"tokens": "python3 tools/make-tokens.py"
|
||||
|
||||
+20
-1
@@ -203,6 +203,24 @@ function focusParagraph() {
|
||||
const bestKey = Object.keys(focus.creatures).find(k => focus.creatures[k] === best);
|
||||
const bestName = nameByKey.get(bestKey) ?? bestKey;
|
||||
const every = helped === rows.length && clear === rows.length;
|
||||
|
||||
/* Name the exceptions, do not round them away.
|
||||
*
|
||||
* The page used to be allowed to say "in 137 of the 138 packs" and stop there, which is
|
||||
* true and useless: a GM reading "kill them one at a time" needs to know the one pack
|
||||
* where doing that is wrong. check-focus asserts that every creature focus fire does not
|
||||
* help against is named here by name, so the count and the exception cannot drift apart. */
|
||||
const failedKeys = Object.keys(focus.creatures ?? {})
|
||||
.filter(k => (focus.creatures[k]?.gain ?? 1) <= 0
|
||||
&& focus.creatures[k]?.gain !== undefined);
|
||||
const exceptions = failedKeys.map(k => nameByKey.get(k) ?? k);
|
||||
const exceptSentence = exceptions.length
|
||||
? ` The exception${exceptions.length === 1 ? " is" : "s are"} `
|
||||
+ `**${exceptions.map(esc).join("**, **")}**: against `
|
||||
+ `${exceptions.length === 1 ? "that one" : "those"}, concentrating fire is worth nothing `
|
||||
+ `measurable, so spread out and use the room instead.`
|
||||
: "";
|
||||
|
||||
return `**What you can do is kill them one at a time.** Against a pack, every agent putting\n`
|
||||
+ `their attacks into the same creature beats spreading them \u2014 in ${every ? "**every one**" : `${helped}`} of the\n`
|
||||
+ `${rows.length} packs in this book whose odds leave room for a difference to show`
|
||||
@@ -212,7 +230,8 @@ function focusParagraph() {
|
||||
+ `**${mean(packs.map(r => r.gain)).toFixed(0)} points** against three or more; the best case in the book is\n`
|
||||
+ `${best.n} \u00d7 ${esc(bestName)}, which goes ${best.spread.toFixed(1)}% to ${best.focus.toFixed(1)}%. Most of that is not the ladder\n`
|
||||
+ `above: a creature that is dead stops attacking, and one on half its hit points does not\n`
|
||||
+ `attack any less. Held at a fixed pack size the dodge ladder is worth a point or two.\n\n`
|
||||
+ `attack any less. Held at a fixed pack size the dodge ladder is worth a point or two.`
|
||||
+ `${exceptSentence}\n\n`
|
||||
+ `*(${focus.runs} fights \u00d7 ${(focus.seeds ?? []).length} seeds per arm, re-measured by \`check-focus\` on every build.\n`
|
||||
+ `A pack is measured when ${focus.admission ?? "its odds can move"}, so a fight nobody could lose\n`
|
||||
+ `and a fight nobody could win are both left out.)*\n\n`;
|
||||
|
||||
+14
-7
@@ -14,13 +14,21 @@ import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
SKILL_CATALOGUE, WEAPONS, ARMOURS, GEAR, VEHICLES, PREGENS, NPCS, DEVICES, TALENTS,
|
||||
SKILL_CATALOGUE, WEAPONS, ARMOURS, GEAR, VEHICLES, DEVICES, TALENTS,
|
||||
SITE_TABLE, HAZARD_TABLE, hitPoints, majorWound, damageModifier, styleFor
|
||||
} from "./content.mjs";
|
||||
import { BESTIARY_FOLKLORE } from "./bestiary-folklore.mjs";
|
||||
import { BESTIARY_WATER } from "./bestiary-water.mjs";
|
||||
import { BESTIARY_ANOMALOUS } from "./bestiary-anomalous.mjs";
|
||||
import { BESTIARY_BEASTS } from "./bestiary-beasts.mjs";
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
|
||||
/* The population, through the one door — see check-seam.mjs.
|
||||
*
|
||||
* Scenario casts are deliberately excluded HERE and only here: each scenario builds its
|
||||
* own pack further down this file, and taking them into the npcs pack as well would put
|
||||
* every ticket inspector in the compendium twice under the same derived id, where the
|
||||
* second write silently replaces the first. The label is the discriminator because
|
||||
* collectSpecs stamps one on every spec saying which file it came from. */
|
||||
const ALL = await collectSpecs();
|
||||
const NPCS = ALL.filter(r => r.kind === "npc" && !r.label.startsWith("scenario-")).map(r => r.spec);
|
||||
const PREGENS = ALL.filter(r => r.label === "content.mjs PREGENS").map(r => r.spec);
|
||||
import { SHIELD } from "../rules.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -686,8 +694,7 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
* calls one-definition-only "this repository's one standing law". Listing the arrays
|
||||
* here is the small fix; routing every consumer through collectSpecs is the real one,
|
||||
* and it is written up rather than done under a content drop. */
|
||||
for (const n of [...NPCS, ...BESTIARY_FOLKLORE, ...BESTIARY_WATER,
|
||||
...BESTIARY_ANOMALOUS, ...BESTIARY_BEASTS]) {
|
||||
for (const n of NPCS) {
|
||||
const { actorDoc, itemDocs } = buildActor(n, "npc");
|
||||
entries.push([`!actors!${actorDoc._id}`, actorDoc]);
|
||||
for (const it of itemDocs) entries.push([`!actors.items!${actorDoc._id}.${it._id}`, it]);
|
||||
|
||||
+53
-9
@@ -29,12 +29,17 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).filter(r => r.kind === "npc").map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, seedFor, makeRng } from "./simulate.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")), "..");
|
||||
const BASELINE = path.join(ROOT, "tools", "focus-baseline.json");
|
||||
const BESTIARY = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "docs", "BESTIARY.md");
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
const UPDATE = argv.includes("--update");
|
||||
@@ -192,6 +197,9 @@ if (base.runs !== RUNS || base.seeds?.join() !== SEEDS.join()
|
||||
|
||||
const problems = [], added = [], removed = [];
|
||||
let checked = 0, helped = 0, aboveNoise = 0;
|
||||
/* The creatures concentrating fire does not help against, by NAME, because that is what a
|
||||
reader of docs/BESTIARY.md would see and what the page must therefore contain. */
|
||||
const failedNames = [];
|
||||
|
||||
for (const spec of NPCS) {
|
||||
const was = base.creatures[spec.key];
|
||||
@@ -199,7 +207,7 @@ for (const spec of NPCS) {
|
||||
if (was.pinned !== undefined) continue; // recorded as unmeasurable; nothing to compare
|
||||
const now = measureAt(spec, was.n);
|
||||
checked++;
|
||||
if (now.gain > 0) helped++;
|
||||
if (now.gain > 0) helped++; else failedNames.push(spec.name);
|
||||
if (now.gain > now.noise) aboveNoise++;
|
||||
const same = now.spread === was.spread && now.focus === was.focus && now.gain === was.gain;
|
||||
if (!same) {
|
||||
@@ -227,11 +235,41 @@ if (problems.length || added.length || removed.length) {
|
||||
moves — R-264 dropped it from 31 of 31 to 28 of 30 and the page rewrote itself. What
|
||||
the page does assert outright is that concentrating fire helps, and that is what fails
|
||||
here. A failure means rewrite the advice; it is not a baseline to re-record. */
|
||||
if (helped !== checked) {
|
||||
console.error(`check-focus: FAILED — focus fire helped only ${helped} of ${checked} packs. `
|
||||
+ `docs/BESTIARY.md advises concentrating fire against every pack whose fight is in `
|
||||
+ `doubt, and that is no longer true. Rewrite the advice rather than re-recording.`);
|
||||
process.exit(1);
|
||||
/* R-312. This asserted helped === checked: focus fire must help EVERYWHERE, or the page is
|
||||
wrong. That held while the bestiary was 47 creatures and stopped holding the moment it
|
||||
was 184 — dun_cow is a pair of SIZ 32 chargers, and concentrating fire on one of them
|
||||
does not get it down before the other one arrives.
|
||||
*
|
||||
* The invariant was the wrong shape. "It always helps" is a claim about the game, and the
|
||||
* game is allowed to have an exception; what must never happen is the page advising a
|
||||
* tactic without saying where it fails. So the requirement is now that BESTIARY.md NAMES
|
||||
* every creature focus fire does not help against. That is strictly stronger than the old
|
||||
* count check — a page can no longer pass by quietly reporting "137 of 138". */
|
||||
{
|
||||
/* Scope to the advice, not the whole document.
|
||||
*
|
||||
* The first cut asked whether the name appeared in BESTIARY.md at all, and every
|
||||
* creature's name appears in its own entry heading — so it passed with the exception
|
||||
* sentence deleted. It was a guard that could never fire. Read only the section that
|
||||
* gives the advice, which is the only place naming the exception does any good. */
|
||||
const doc = existsSync(BESTIARY) ? await readFile(BESTIARY, "utf8") : "";
|
||||
const HEADING = "## Shooting at something that moves";
|
||||
const from = doc.indexOf(HEADING);
|
||||
const advice = from === -1 ? "" : doc.slice(from, (doc.indexOf("\n## ", from + 1) + 1) || doc.length);
|
||||
if (from === -1 && failedNames.length) {
|
||||
console.error(`check-focus: FAILED — docs/BESTIARY.md has no "${HEADING}" section, so the `
|
||||
+ `advice that must carry the exceptions cannot be found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
const unnamed = failedNames.filter(n => !advice.includes(n));
|
||||
if (unnamed.length) {
|
||||
console.error(`check-focus: FAILED — focus fire does not help against `
|
||||
+ `${failedNames.join(", ")}, and docs/BESTIARY.md does not name `
|
||||
+ `${unnamed.join(", ")}. The page advises concentrating fire; a reader who takes `
|
||||
+ `that advice against one of these is being told to do the thing that does not `
|
||||
+ `work. Name the exception in the advice — do not re-record.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* R-281. "Helps in all of them" is satisfied by a gain of 0.2, which is true and thin: it
|
||||
@@ -256,5 +294,11 @@ if (wasReliable && wasReliable.of > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`check-focus: OK — focus fire measured against ${checked} packs, helps in all of them`
|
||||
+ `, ${aboveNoise} of them by more than their own noise`);
|
||||
/* Say the real number. This line read "helps in all of them" unconditionally, which was
|
||||
true when the only way to reach it was helped === checked and became a lie the moment
|
||||
that assertion was replaced. A success message that states a fact it no longer checks is
|
||||
the same defect this guard exists to catch, one level up. */
|
||||
console.log(`check-focus: OK — focus fire measured against ${checked} packs, helps in `
|
||||
+ `${helped === checked ? "all of them" : `${helped} of them`}`
|
||||
+ `, ${aboveNoise} by more than their own noise`
|
||||
+ `${failedNames.length ? `; no measurable help against ${failedNames.join(", ")}, named in BESTIARY.md` : ""}`);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* check-seam — one list of creatures, and one way to reach it.
|
||||
*
|
||||
* `tools/all-specs.mjs` opens by calling it "this repository's one standing law" that a
|
||||
* definition may not exist twice, and then names the rule: a scenario cast is any exported
|
||||
* array whose entries carry characteristics, so a new scenario is covered the day it is
|
||||
* written rather than the day somebody remembers to add it to a list.
|
||||
*
|
||||
* Nothing enforced that. Importing `NPCS` from content.mjs still worked, and it kept
|
||||
* working right up to the moment the bestiary moved into its own files — at which point
|
||||
* every tool holding that import silently began describing 47 creatures out of 184.
|
||||
* Nothing failed. The tools carried on, and each one was wrong in its own way:
|
||||
*
|
||||
* build-packs.mjs built a compendium the 100 new creatures were not in, so they
|
||||
* validated, were drawn, had prompts written, and did not exist
|
||||
* in the game
|
||||
* check-lethality.mjs measured 47 and printed OK — a regression guard blind to two
|
||||
* thirds of what it guards
|
||||
* bestiary.mjs wrote a reference document missing the creatures
|
||||
* check-powers.mjs reconciled its counts against that document while reading a
|
||||
* different population, so it called the document wrong when the
|
||||
* document was right
|
||||
*
|
||||
* The individual fixes are each one line. They are also each a thing somebody has to
|
||||
* remember, which is what failed the first time. This is the guard that makes forgetting
|
||||
* loud: the population has one door, and all-specs.mjs is the only file allowed through
|
||||
* it.
|
||||
*
|
||||
* bun tools/check-seam.mjs
|
||||
*/
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const ROOT = path.join(path.dirname(new URL(import.meta.url).pathname), "..");
|
||||
|
||||
/* The bindings that ARE the population. A tool reading any of these from content.mjs is
|
||||
reading a subset and calling it the whole, which is the defect this exists to catch. */
|
||||
const POPULATION = ["NPCS", "PREGENS"];
|
||||
|
||||
/* all-specs.mjs is the door. It reads content.mjs precisely so that nothing else has to,
|
||||
and a guard that forbade it would forbid the fix as well as the fault. */
|
||||
const ALLOWED = new Set(["tools/all-specs.mjs"]);
|
||||
|
||||
/* Multi-line on purpose. build-packs.mjs spells its import across two lines and a
|
||||
single-line grep does not see it — which is exactly how it was missed when this set was
|
||||
counted by hand, and a guard that repeats the mistake it is checking for is no guard. */
|
||||
const IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*["'](\.{1,2}\/[^"']*content\.mjs)["']/gs;
|
||||
|
||||
async function* walk(dir) {
|
||||
for (const e of await readdir(dir, { withFileTypes: true })) {
|
||||
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
|
||||
const p = path.join(dir, e.name);
|
||||
if (e.isDirectory()) yield* walk(p);
|
||||
else if (e.name.endsWith(".mjs")) yield p;
|
||||
}
|
||||
}
|
||||
|
||||
const problems = [];
|
||||
let scanned = 0;
|
||||
|
||||
for await (const file of walk(ROOT)) {
|
||||
const rel = path.relative(ROOT, file);
|
||||
scanned++;
|
||||
if (ALLOWED.has(rel)) continue;
|
||||
const src = await readFile(file, "utf8");
|
||||
for (const m of src.matchAll(IMPORT_RE)) {
|
||||
const named = m[1].split(",").map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
||||
const hit = named.filter(n => POPULATION.includes(n));
|
||||
if (!hit.length) continue;
|
||||
const line = src.slice(0, m.index).split("\n").length;
|
||||
problems.push(`${rel}:${line} imports ${hit.join(", ")} from content.mjs — `
|
||||
+ `read collectSpecs() from ./all-specs.mjs instead, or this tool will describe `
|
||||
+ `content.mjs's creatures and call them the bestiary`);
|
||||
}
|
||||
}
|
||||
|
||||
if (problems.length) {
|
||||
console.error(`check-seam: FAILED — ${problems.length} tool(s) read the creature `
|
||||
+ `population directly instead of through all-specs.mjs`);
|
||||
for (const p of problems) console.error(" " + p);
|
||||
console.error(`\n all-specs.mjs is the only file allowed to import ${POPULATION.join(" or ")}. `
|
||||
+ `Everything else reads collectSpecs(), so a new source file reaches every tool at once.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`check-seam: OK — ${scanned} modules scanned, only all-specs.mjs reads `
|
||||
+ `${POPULATION.join("/")} from content.mjs`);
|
||||
@@ -21,7 +21,11 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, seedFor, makeRng } from "./simulate.mjs";
|
||||
import { castAndCut } from "./declared-cast.mjs";
|
||||
|
||||
@@ -35,7 +35,11 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, seedFor, makeRng } from "./simulate.mjs";
|
||||
import { castAndCut } from "./declared-cast.mjs";
|
||||
|
||||
@@ -33,7 +33,11 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, seedFor, makeRng } from "./simulate.mjs";
|
||||
import { castAndCut } from "./declared-cast.mjs";
|
||||
|
||||
+850
-2
@@ -15,8 +15,8 @@
|
||||
"seedMode": "per-creature",
|
||||
"admission": "its spread-fire odds stand clear of 0% and 100% by more than its own noise",
|
||||
"reliable": {
|
||||
"aboveNoise": 36,
|
||||
"of": 38
|
||||
"aboveNoise": 130,
|
||||
"of": 138
|
||||
},
|
||||
"creatures": {
|
||||
"quiet_neighbours_npc": {
|
||||
@@ -320,6 +320,854 @@
|
||||
"focus": 59.3,
|
||||
"gain": 14.1,
|
||||
"noise": 2.3
|
||||
},
|
||||
"hunt_master": {
|
||||
"n": 2,
|
||||
"spread": 6.3,
|
||||
"focus": 10.9,
|
||||
"gain": 4.6,
|
||||
"noise": 1.2
|
||||
},
|
||||
"gabriel_hounds": {
|
||||
"n": 5,
|
||||
"spread": 58.8,
|
||||
"focus": 69.1,
|
||||
"gain": 10.3,
|
||||
"noise": 2.6
|
||||
},
|
||||
"grey_mare": {
|
||||
"n": 2,
|
||||
"spread": 55.7,
|
||||
"focus": 65.1,
|
||||
"gain": 9.4,
|
||||
"noise": 0.9
|
||||
},
|
||||
"black_annis": {
|
||||
"n": 2,
|
||||
"spread": 7.5,
|
||||
"focus": 12,
|
||||
"gain": 4.5,
|
||||
"noise": 1.7
|
||||
},
|
||||
"cailleach": {
|
||||
"n": 6,
|
||||
"spread": 93.6,
|
||||
"focus": 96.7,
|
||||
"gain": 3.1,
|
||||
"noise": 0.7
|
||||
},
|
||||
"jenny_greenteeth": {
|
||||
"n": 4,
|
||||
"spread": 61.8,
|
||||
"focus": 71.8,
|
||||
"gain": 10,
|
||||
"noise": 3
|
||||
},
|
||||
"gwrach": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"hunky_punk": {
|
||||
"n": 2,
|
||||
"spread": 75.8,
|
||||
"focus": 81.5,
|
||||
"gain": 5.8,
|
||||
"noise": 3
|
||||
},
|
||||
"sin_eater": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"lych_waiter": {
|
||||
"n": 4,
|
||||
"spread": 66.3,
|
||||
"focus": 77,
|
||||
"gain": 10.7,
|
||||
"noise": 2.8
|
||||
},
|
||||
"crossroads_man": {
|
||||
"n": 3,
|
||||
"spread": 42.7,
|
||||
"focus": 58.8,
|
||||
"gain": 16.1,
|
||||
"noise": 0.5
|
||||
},
|
||||
"march_stone": {
|
||||
"n": 2,
|
||||
"spread": 2,
|
||||
"focus": 2.9,
|
||||
"gain": 0.8,
|
||||
"noise": 1
|
||||
},
|
||||
"foundation_child": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"bog_offering": {
|
||||
"n": 6,
|
||||
"pinned": 100
|
||||
},
|
||||
"hearth_hob": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"cauld_lad": {
|
||||
"n": 6,
|
||||
"pinned": 100
|
||||
},
|
||||
"the_teind": {
|
||||
"n": 5,
|
||||
"spread": 48.3,
|
||||
"focus": 63.1,
|
||||
"gain": 14.8,
|
||||
"noise": 2
|
||||
},
|
||||
"changeling": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"uninvited": {
|
||||
"n": 6,
|
||||
"pinned": 99.7
|
||||
},
|
||||
"dullahan": {
|
||||
"n": 2,
|
||||
"spread": 22.6,
|
||||
"focus": 30.7,
|
||||
"gain": 8.1,
|
||||
"noise": 2.5
|
||||
},
|
||||
"pooka": {
|
||||
"n": 2,
|
||||
"spread": 25.6,
|
||||
"focus": 32.9,
|
||||
"gain": 7.3,
|
||||
"noise": 1
|
||||
},
|
||||
"cu_sidhe": {
|
||||
"n": 2,
|
||||
"spread": 59.7,
|
||||
"focus": 68.1,
|
||||
"gain": 8.4,
|
||||
"noise": 3.6
|
||||
},
|
||||
"tatterfoal": {
|
||||
"n": 6,
|
||||
"spread": 49.8,
|
||||
"focus": 64.2,
|
||||
"gain": 14.3,
|
||||
"noise": 3
|
||||
},
|
||||
"dun_cow": {
|
||||
"n": 2,
|
||||
"spread": 0.1,
|
||||
"focus": 0,
|
||||
"gain": -0.1,
|
||||
"noise": 0
|
||||
},
|
||||
"boobrie": {
|
||||
"n": 2,
|
||||
"spread": 44.8,
|
||||
"focus": 55,
|
||||
"gain": 10.2,
|
||||
"noise": 4.3
|
||||
},
|
||||
"harbour_dog": {
|
||||
"n": 3,
|
||||
"spread": 63.8,
|
||||
"focus": 75.5,
|
||||
"gain": 11.7,
|
||||
"noise": 2.7
|
||||
},
|
||||
"sea_mare": {
|
||||
"n": 2,
|
||||
"spread": 25.1,
|
||||
"focus": 32.7,
|
||||
"gain": 7.6,
|
||||
"noise": 1.6
|
||||
},
|
||||
"tangie": {
|
||||
"n": 2,
|
||||
"spread": 42.5,
|
||||
"focus": 52.3,
|
||||
"gain": 9.7,
|
||||
"noise": 1.6
|
||||
},
|
||||
"selkie": {
|
||||
"n": 5,
|
||||
"spread": 50.9,
|
||||
"focus": 63.6,
|
||||
"gain": 12.7,
|
||||
"noise": 0.8
|
||||
},
|
||||
"wrack_gull": {
|
||||
"n": 6,
|
||||
"spread": 64.3,
|
||||
"focus": 74.9,
|
||||
"gain": 10.6,
|
||||
"noise": 0.1
|
||||
},
|
||||
"sea_crow": {
|
||||
"n": 6,
|
||||
"spread": 97.2,
|
||||
"focus": 98.3,
|
||||
"gain": 1.1,
|
||||
"noise": 1.1
|
||||
},
|
||||
"shellycoat": {
|
||||
"n": 4,
|
||||
"spread": 35.9,
|
||||
"focus": 50.1,
|
||||
"gain": 14.2,
|
||||
"noise": 2.2
|
||||
},
|
||||
"peg_powler": {
|
||||
"n": 3,
|
||||
"spread": 47.6,
|
||||
"focus": 61,
|
||||
"gain": 13.4,
|
||||
"noise": 2.9
|
||||
},
|
||||
"greenteeth": {
|
||||
"n": 3,
|
||||
"spread": 51,
|
||||
"focus": 62.8,
|
||||
"gain": 11.8,
|
||||
"noise": 5.4
|
||||
},
|
||||
"fen_hound": {
|
||||
"n": 4,
|
||||
"spread": 39.3,
|
||||
"focus": 53.1,
|
||||
"gain": 13.9,
|
||||
"noise": 6.6
|
||||
},
|
||||
"cut_walker": {
|
||||
"n": 4,
|
||||
"spread": 67.3,
|
||||
"focus": 77.4,
|
||||
"gain": 10.1,
|
||||
"noise": 3.6
|
||||
},
|
||||
"wrackman": {
|
||||
"n": 3,
|
||||
"spread": 31.6,
|
||||
"focus": 46.6,
|
||||
"gain": 15,
|
||||
"noise": 0.6
|
||||
},
|
||||
"net_mender": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"mudlark_child": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"hulk_haunt": {
|
||||
"n": 4,
|
||||
"spread": 61.6,
|
||||
"focus": 74.2,
|
||||
"gain": 12.6,
|
||||
"noise": 2.2
|
||||
},
|
||||
"drowned_crew": {
|
||||
"n": 6,
|
||||
"spread": 67.5,
|
||||
"focus": 78.2,
|
||||
"gain": 10.7,
|
||||
"noise": 2.5
|
||||
},
|
||||
"lock_keeper": {
|
||||
"n": 3,
|
||||
"spread": 48.6,
|
||||
"focus": 59.7,
|
||||
"gain": 11.1,
|
||||
"noise": 0.4
|
||||
},
|
||||
"the_undertow": {
|
||||
"n": 2,
|
||||
"spread": 1,
|
||||
"focus": 2,
|
||||
"gain": 0.9,
|
||||
"noise": 0.6
|
||||
},
|
||||
"the_sump": {
|
||||
"n": 2,
|
||||
"spread": 16.4,
|
||||
"focus": 23.2,
|
||||
"gain": 6.8,
|
||||
"noise": 1.9
|
||||
},
|
||||
"drowned_parish": {
|
||||
"n": 6,
|
||||
"pinned": 99.9
|
||||
},
|
||||
"the_fathom": {
|
||||
"n": 2,
|
||||
"pinned": 0.1
|
||||
},
|
||||
"eelmother": {
|
||||
"n": 2,
|
||||
"spread": 11.3,
|
||||
"focus": 19.1,
|
||||
"gain": 7.8,
|
||||
"noise": 1.6
|
||||
},
|
||||
"lamprey_host": {
|
||||
"n": 3,
|
||||
"spread": 49.7,
|
||||
"focus": 66.5,
|
||||
"gain": 16.8,
|
||||
"noise": 2.1
|
||||
},
|
||||
"washland_cattle": {
|
||||
"n": 2,
|
||||
"spread": 37.8,
|
||||
"focus": 47.7,
|
||||
"gain": 10,
|
||||
"noise": 2.3
|
||||
},
|
||||
"the_heron": {
|
||||
"n": 4,
|
||||
"spread": 46.8,
|
||||
"focus": 58.1,
|
||||
"gain": 11.3,
|
||||
"noise": 1.2
|
||||
},
|
||||
"foldwalker": {
|
||||
"n": 3,
|
||||
"spread": 48.8,
|
||||
"focus": 64.5,
|
||||
"gain": 15.6,
|
||||
"noise": 3
|
||||
},
|
||||
"anglesmith": {
|
||||
"n": 3,
|
||||
"spread": 51.7,
|
||||
"focus": 64.7,
|
||||
"gain": 13.1,
|
||||
"noise": 0.7
|
||||
},
|
||||
"halfpast": {
|
||||
"n": 4,
|
||||
"spread": 51.8,
|
||||
"focus": 65.2,
|
||||
"gain": 13.4,
|
||||
"noise": 1.4
|
||||
},
|
||||
"returned_surveyor": {
|
||||
"n": 6,
|
||||
"spread": 71.5,
|
||||
"focus": 83.1,
|
||||
"gain": 11.6,
|
||||
"noise": 0.9
|
||||
},
|
||||
"unmeasured": {
|
||||
"n": 6,
|
||||
"spread": 96.9,
|
||||
"focus": 98.3,
|
||||
"gain": 1.3,
|
||||
"noise": 1.5
|
||||
},
|
||||
"patient_grasp": {
|
||||
"n": 2,
|
||||
"spread": 24.1,
|
||||
"focus": 30.7,
|
||||
"gain": 6.6,
|
||||
"noise": 2.1
|
||||
},
|
||||
"the_tideline": {
|
||||
"n": 2,
|
||||
"spread": 72.2,
|
||||
"focus": 78.4,
|
||||
"gain": 6.2,
|
||||
"noise": 2.2
|
||||
},
|
||||
"glass_auditor": {
|
||||
"n": 3,
|
||||
"spread": 50.8,
|
||||
"focus": 60.4,
|
||||
"gain": 9.6,
|
||||
"noise": 1.3
|
||||
},
|
||||
"quorate": {
|
||||
"n": 6,
|
||||
"spread": 90.2,
|
||||
"focus": 95.7,
|
||||
"gain": 5.5,
|
||||
"noise": 2.8
|
||||
},
|
||||
"five_of_six": {
|
||||
"n": 2,
|
||||
"spread": 73.2,
|
||||
"focus": 81.3,
|
||||
"gain": 8.2,
|
||||
"noise": 1.4
|
||||
},
|
||||
"the_remainder": {
|
||||
"n": 6,
|
||||
"pinned": 99.7
|
||||
},
|
||||
"counterpoint": {
|
||||
"n": 6,
|
||||
"spread": 40.2,
|
||||
"focus": 60.7,
|
||||
"gain": 20.5,
|
||||
"noise": 2.2
|
||||
},
|
||||
"the_tally": {
|
||||
"n": 6,
|
||||
"spread": 99.8,
|
||||
"focus": 99.9,
|
||||
"gain": 0.1,
|
||||
"noise": 0
|
||||
},
|
||||
"many_hands": {
|
||||
"n": 4,
|
||||
"spread": 59.9,
|
||||
"focus": 71,
|
||||
"gain": 11.1,
|
||||
"noise": 2.4
|
||||
},
|
||||
"the_iteration": {
|
||||
"n": 3,
|
||||
"spread": 25.6,
|
||||
"focus": 35.5,
|
||||
"gain": 9.9,
|
||||
"noise": 1.2
|
||||
},
|
||||
"backscatter": {
|
||||
"n": 2,
|
||||
"spread": 7,
|
||||
"focus": 8.8,
|
||||
"gain": 1.9,
|
||||
"noise": 3.2
|
||||
},
|
||||
"afterimage": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"second_pass": {
|
||||
"n": 3,
|
||||
"spread": 54.3,
|
||||
"focus": 64.9,
|
||||
"gain": 10.6,
|
||||
"noise": 2.2
|
||||
},
|
||||
"turnback": {
|
||||
"n": 3,
|
||||
"spread": 59,
|
||||
"focus": 71.9,
|
||||
"gain": 12.8,
|
||||
"noise": 1.1
|
||||
},
|
||||
"the_overflight": {
|
||||
"n": 2,
|
||||
"spread": 13.9,
|
||||
"focus": 19.5,
|
||||
"gain": 5.6,
|
||||
"noise": 2.9
|
||||
},
|
||||
"recurrence": {
|
||||
"n": 3,
|
||||
"spread": 40.2,
|
||||
"focus": 53,
|
||||
"gain": 12.8,
|
||||
"noise": 1
|
||||
},
|
||||
"inside_out": {
|
||||
"n": 2,
|
||||
"pinned": 0
|
||||
},
|
||||
"latecomer": {
|
||||
"n": 2,
|
||||
"spread": 65.6,
|
||||
"focus": 73.8,
|
||||
"gain": 8.2,
|
||||
"noise": 3.5
|
||||
},
|
||||
"mirrorline": {
|
||||
"n": 2,
|
||||
"spread": 73.9,
|
||||
"focus": 81.2,
|
||||
"gain": 7.3,
|
||||
"noise": 5.1
|
||||
},
|
||||
"carried_over": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"bull_azure": {
|
||||
"n": 2,
|
||||
"spread": 8.8,
|
||||
"focus": 13.7,
|
||||
"gain": 4.9,
|
||||
"noise": 1.2
|
||||
},
|
||||
"enfield": {
|
||||
"n": 4,
|
||||
"spread": 31.6,
|
||||
"focus": 45,
|
||||
"gain": 13.3,
|
||||
"noise": 3.6
|
||||
},
|
||||
"yale": {
|
||||
"n": 2,
|
||||
"spread": 71.8,
|
||||
"focus": 79.3,
|
||||
"gain": 7.5,
|
||||
"noise": 1
|
||||
},
|
||||
"white_hart": {
|
||||
"n": 4,
|
||||
"spread": 67.3,
|
||||
"focus": 82,
|
||||
"gain": 14.7,
|
||||
"noise": 0.3
|
||||
},
|
||||
"man_tyger": {
|
||||
"n": 2,
|
||||
"spread": 49.7,
|
||||
"focus": 60.2,
|
||||
"gain": 10.6,
|
||||
"noise": 2.4
|
||||
},
|
||||
"melusine": {
|
||||
"n": 4,
|
||||
"spread": 57.4,
|
||||
"focus": 71.8,
|
||||
"gain": 14.4,
|
||||
"noise": 3.3
|
||||
},
|
||||
"eagle_displayed": {
|
||||
"n": 4,
|
||||
"spread": 34.2,
|
||||
"focus": 44.5,
|
||||
"gain": 10.3,
|
||||
"noise": 2.7
|
||||
},
|
||||
"gryphon": {
|
||||
"n": 2,
|
||||
"spread": 5.4,
|
||||
"focus": 8.8,
|
||||
"gain": 3.4,
|
||||
"noise": 1.3
|
||||
},
|
||||
"sign_wyvern": {
|
||||
"n": 2,
|
||||
"spread": 66.9,
|
||||
"focus": 75.8,
|
||||
"gain": 8.8,
|
||||
"noise": 1.1
|
||||
},
|
||||
"cockatrice": {
|
||||
"n": 6,
|
||||
"spread": 89.9,
|
||||
"focus": 93.6,
|
||||
"gain": 3.7,
|
||||
"noise": 0.7
|
||||
},
|
||||
"martlet": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"piety_pelican": {
|
||||
"n": 6,
|
||||
"spread": 91.7,
|
||||
"focus": 96.2,
|
||||
"gain": 4.5,
|
||||
"noise": 0.5
|
||||
},
|
||||
"drain_heron": {
|
||||
"n": 6,
|
||||
"spread": 80.5,
|
||||
"focus": 85.8,
|
||||
"gain": 5.4,
|
||||
"noise": 2.2
|
||||
},
|
||||
"talbot": {
|
||||
"n": 3,
|
||||
"spread": 56.7,
|
||||
"focus": 69.8,
|
||||
"gain": 13.1,
|
||||
"noise": 3
|
||||
},
|
||||
"gazehound": {
|
||||
"n": 6,
|
||||
"spread": 39.8,
|
||||
"focus": 53.7,
|
||||
"gain": 13.9,
|
||||
"noise": 5.7
|
||||
},
|
||||
"bearward_bear": {
|
||||
"n": 2,
|
||||
"spread": 28,
|
||||
"focus": 36.1,
|
||||
"gain": 8.1,
|
||||
"noise": 1.9
|
||||
},
|
||||
"heath_cow": {
|
||||
"n": 2,
|
||||
"spread": 0.7,
|
||||
"focus": 0.9,
|
||||
"gain": 0.2,
|
||||
"noise": 0.2
|
||||
},
|
||||
"tup": {
|
||||
"n": 3,
|
||||
"spread": 53.2,
|
||||
"focus": 65.5,
|
||||
"gain": 12.3,
|
||||
"noise": 3.6
|
||||
},
|
||||
"cade_lamb": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"hollow_hog": {
|
||||
"n": 2,
|
||||
"spread": 49.3,
|
||||
"focus": 60,
|
||||
"gain": 10.7,
|
||||
"noise": 2.3
|
||||
},
|
||||
"hinny": {
|
||||
"n": 4,
|
||||
"spread": 31.2,
|
||||
"focus": 46.2,
|
||||
"gain": 15,
|
||||
"noise": 3.2
|
||||
},
|
||||
"water_bull": {
|
||||
"n": 2,
|
||||
"pinned": 1.1
|
||||
},
|
||||
"moor_cat": {
|
||||
"n": 3,
|
||||
"spread": 60.8,
|
||||
"focus": 72.3,
|
||||
"gain": 11.4,
|
||||
"noise": 2.9
|
||||
},
|
||||
"gaunt_grey": {
|
||||
"n": 3,
|
||||
"spread": 69.8,
|
||||
"focus": 80.1,
|
||||
"gain": 10.3,
|
||||
"noise": 1.2
|
||||
},
|
||||
"menagerie_ape": {
|
||||
"n": 2,
|
||||
"spread": 76.3,
|
||||
"focus": 83.5,
|
||||
"gain": 7.2,
|
||||
"noise": 2.7
|
||||
},
|
||||
"tt_bewley": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_prosser": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_rance": {
|
||||
"n": 5,
|
||||
"spread": 63.5,
|
||||
"focus": 73.6,
|
||||
"gain": 10.1,
|
||||
"noise": 2.5
|
||||
},
|
||||
"tt_shard": {
|
||||
"n": 6,
|
||||
"spread": 81.8,
|
||||
"focus": 86.8,
|
||||
"gain": 5,
|
||||
"noise": 1
|
||||
},
|
||||
"tt_cordera_man": {
|
||||
"n": 6,
|
||||
"spread": 89.4,
|
||||
"focus": 94,
|
||||
"gain": 4.6,
|
||||
"noise": 2.1
|
||||
},
|
||||
"tt_tom_doyle": {
|
||||
"n": 6,
|
||||
"spread": 55.8,
|
||||
"focus": 65.5,
|
||||
"gain": 9.7,
|
||||
"noise": 1.9
|
||||
},
|
||||
"tt_kell": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_vane": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_bligh": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_reyes": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"tt_merritt": {
|
||||
"n": 5,
|
||||
"spread": 52.8,
|
||||
"focus": 66.2,
|
||||
"gain": 13.4,
|
||||
"noise": 1.3
|
||||
},
|
||||
"tt_ambrose": {
|
||||
"n": 6,
|
||||
"spread": 80.5,
|
||||
"focus": 87.5,
|
||||
"gain": 7,
|
||||
"noise": 2.1
|
||||
},
|
||||
"tt_coat": {
|
||||
"n": 6,
|
||||
"spread": 74.5,
|
||||
"focus": 82.8,
|
||||
"gain": 8.3,
|
||||
"noise": 1.9
|
||||
},
|
||||
"tt_writman": {
|
||||
"n": 6,
|
||||
"spread": 39.6,
|
||||
"focus": 52.5,
|
||||
"gain": 12.9,
|
||||
"noise": 3.9
|
||||
},
|
||||
"bp_leah": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"bp_martin": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"bp_inspector": {
|
||||
"n": 5,
|
||||
"spread": 54,
|
||||
"focus": 67.4,
|
||||
"gain": 13.4,
|
||||
"noise": 2.7
|
||||
},
|
||||
"bp_conductor": {
|
||||
"n": 3,
|
||||
"spread": 69.6,
|
||||
"focus": 81,
|
||||
"gain": 11.4,
|
||||
"noise": 3.4
|
||||
},
|
||||
"bp_nia": {
|
||||
"n": 5,
|
||||
"spread": 43.3,
|
||||
"focus": 57.2,
|
||||
"gain": 13.9,
|
||||
"noise": 0.5
|
||||
},
|
||||
"bp_callum": {
|
||||
"n": 2,
|
||||
"spread": 21.7,
|
||||
"focus": 27.2,
|
||||
"gain": 5.5,
|
||||
"noise": 3.4
|
||||
},
|
||||
"bp_mara": {
|
||||
"n": 4,
|
||||
"spread": 37.8,
|
||||
"focus": 50.6,
|
||||
"gain": 12.8,
|
||||
"noise": 1.6
|
||||
},
|
||||
"bp_owen": {
|
||||
"n": 6,
|
||||
"spread": 71.6,
|
||||
"focus": 80.3,
|
||||
"gain": 8.7,
|
||||
"noise": 2.8
|
||||
},
|
||||
"bp_elin": {
|
||||
"n": 6,
|
||||
"spread": 77.2,
|
||||
"focus": 84.9,
|
||||
"gain": 7.7,
|
||||
"noise": 2.6
|
||||
},
|
||||
"bp_sennen": {
|
||||
"n": 6,
|
||||
"spread": 59.7,
|
||||
"focus": 70.1,
|
||||
"gain": 10.4,
|
||||
"noise": 5.2
|
||||
},
|
||||
"la_enderby": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"la_sammy": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"la_marrow": {
|
||||
"n": 3,
|
||||
"spread": 56.8,
|
||||
"focus": 68.9,
|
||||
"gain": 12.1,
|
||||
"noise": 3.3
|
||||
},
|
||||
"la_edith": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"la_nowak": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"la_tunnicliffe": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"od_kearsley": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"od_odame": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"od_vasey": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"od_ferrand": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"od_bewley": {
|
||||
"n": 2,
|
||||
"pinned": 100
|
||||
},
|
||||
"so_warden": {
|
||||
"n": 6,
|
||||
"spread": 56.1,
|
||||
"focus": 67.4,
|
||||
"gain": 11.3,
|
||||
"noise": 3.6
|
||||
},
|
||||
"so_teague": {
|
||||
"n": 5,
|
||||
"spread": 55.5,
|
||||
"focus": 67.6,
|
||||
"gain": 12.1,
|
||||
"noise": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
*/
|
||||
import { writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, makeRng } from "./simulate.mjs";
|
||||
import { castAndCut } from "./declared-cast.mjs";
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NPCS } from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).map(r => r.spec);
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { runFight, makeRng } from "./simulate.mjs";
|
||||
|
||||
|
||||
+6
-2
@@ -54,7 +54,8 @@ import {
|
||||
exposedHideFor, naturalArmourOn, locationArmourFor, groundedPlanFor, remapLocationDamage
|
||||
} from "../rules.mjs";
|
||||
import { combatPowerFor } from "./powers.mjs";
|
||||
import { WEAPONS, ARMOURS, NPCS, PREGENS } from "./content.mjs";
|
||||
import { WEAPONS, ARMOURS } from "./content.mjs";
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
import { SCENARIOS } from "./all-specs.mjs";
|
||||
import { ROSTER } from "./roster.mjs";
|
||||
import { expandFromRegister } from "./expand-spec.mjs";
|
||||
@@ -513,7 +514,10 @@ export function measure(partySpecs, enemySpecs, { runs = 400, seed = 1 } = {}) {
|
||||
/* ---------------------------------------------------------------- the corpus */
|
||||
|
||||
const ALL_SPECS = new Map();
|
||||
for (const s of [...NPCS, ...PREGENS, ...ROSTER]) ALL_SPECS.set(s.key, s);
|
||||
/* Every spec in the repository, so `--creature <key>` resolves a bestiary creature and
|
||||
a scenario cast member as readily as one from content.mjs. This read NPCS, PREGENS
|
||||
and ROSTER, which silently excluded both. See check-seam.mjs. */
|
||||
for (const { spec } of await collectSpecs()) ALL_SPECS.set(spec.key, spec);
|
||||
/* The scenario list is all-specs.mjs's, so a new scenario's cast is fightable here the day
|
||||
it is registered there, rather than the day somebody finds this second copy. */
|
||||
for (const name of SCENARIOS) {
|
||||
|
||||
@@ -11,8 +11,13 @@ import { readFile, writeFile } from "node:fs/promises";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { SKILL_CATALOGUE, WEAPONS, ARMOURS, GEAR, VEHICLES, NPCS, TALENTS }
|
||||
import { SKILL_CATALOGUE, WEAPONS, ARMOURS, GEAR, VEHICLES, TALENTS }
|
||||
from "./content.mjs";
|
||||
/* The whole population, through the one door. Reading NPCS from content.mjs meant
|
||||
this tool could only see the 47 creatures that happen to live in that file, so a
|
||||
creature from tools/bestiary-*.mjs was simply "not found". See check-seam.mjs. */
|
||||
import { collectSpecs } from "./all-specs.mjs";
|
||||
const NPCS = (await collectSpecs()).filter(r => r.kind === "npc").map(r => r.spec);
|
||||
import { ROLES, TRADES } from "../postings.mjs";
|
||||
import { ICON_MAP } from "./icons.mjs";
|
||||
import { RULES_PAGES } from "./rules-text.mjs";
|
||||
|
||||
Reference in New Issue
Block a user