Folklore of the isles, water and the drowned, anomalous far-side, and beasts and heraldry — 25 each, in four source files wired into collectSpecs(). check-creatures passes at 204 specs across 13 sources and the full 23-guard suite is green. The population this was meant to fix: non-human body plan 4 -> 48 (29 quadruped, 15 winged) vesh / cadence 5/5 -> 13/13 creatures 47 -> 147 CREATURE_FORGE_PLAN.md called the unused location tables the reason to build any of this; the quadruped and winged plans now carry a bestiary rather than three dogs and a heraldic beast. Art is procedural: 102 SVG portraits composed from each creature's own body geometry, so a cadence portrait has five headless bodies because its location table has five. The 17 hand-made portraits are untouched. BESTIARY_ART.md carries 204 derived Midjourney prompts for the ones worth illustrating properly later. Known and unfixed, reported by three of the four authors independently: WEAPONS has no bite, claw, gore, hoof or talon, so every creature that kills with its mouth carries `grasp` and states its real damage in prose. simulate.mjs measures the grasp. Next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 lines
2.8 KiB
JavaScript
66 lines
2.8 KiB
JavaScript
/**
|
|
* Every actor spec in the repository, collected once.
|
|
*
|
|
* Three things now need the same list — check-creatures validates it, make-portraits
|
|
* draws it, and mj-queue writes prompts for it — and this repository's one standing law
|
|
* is that a definition may not exist twice. The collection rule is a definition: 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. That rule belongs in one place.
|
|
*
|
|
* `kind` is what buildActor will make of the spec: "agent" becomes a character actor,
|
|
* "npc" becomes an npc actor. It decides which portrait family a creature is drawn in
|
|
* and which prompt template it gets, so it is carried here rather than re-derived.
|
|
*/
|
|
import { NPCS, PREGENS } from "./content.mjs";
|
|
import { ROSTER } from "./roster.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";
|
|
|
|
/** Scenario modules whose casts join the list. */
|
|
export const SCENARIOS = ["throughtrain", "starter", "lastadmission", "openday", "standingorder"];
|
|
|
|
/**
|
|
* @returns {Promise<Array<{label: string, kind: "agent"|"npc", spec: object}>>}
|
|
* every spec, in a stable order, each tagged with where it came from.
|
|
*/
|
|
export async function collectSpecs() {
|
|
const sources = [
|
|
{ label: "content.mjs PREGENS", kind: "agent", specs: PREGENS },
|
|
{ label: "content.mjs NPCS", kind: "npc", specs: NPCS },
|
|
{ label: "roster.mjs ROSTER", kind: "agent", specs: ROSTER },
|
|
{ label: "bestiary-folklore.mjs", kind: "npc", specs: BESTIARY_FOLKLORE },
|
|
{ label: "bestiary-water.mjs", kind: "npc", specs: BESTIARY_WATER },
|
|
{ label: "bestiary-anomalous.mjs", kind: "npc", specs: BESTIARY_ANOMALOUS },
|
|
{ label: "bestiary-beasts.mjs", kind: "npc", specs: BESTIARY_BEASTS }
|
|
];
|
|
|
|
for (const s of SCENARIOS) {
|
|
const mod = await import(`./scenario-${s}.mjs`);
|
|
for (const [name, value] of Object.entries(mod)) {
|
|
if (Array.isArray(value) && value.some(x => x && typeof x === "object" && x.ch)) {
|
|
sources.push({ label: `scenario-${s}.mjs ${name}`, kind: "npc", specs: value });
|
|
}
|
|
}
|
|
}
|
|
|
|
const out = [];
|
|
for (const { label, kind, specs } of sources) {
|
|
for (const spec of specs) out.push({ label, kind, spec });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** The same thing grouped, for callers that report per source. */
|
|
export async function collectSources() {
|
|
const flat = await collectSpecs();
|
|
const byLabel = new Map();
|
|
for (const row of flat) {
|
|
if (!byLabel.has(row.label)) byLabel.set(row.label, { label: row.label, kind: row.kind, specs: [] });
|
|
byLabel.get(row.label).specs.push(row.spec);
|
|
}
|
|
return [...byLabel.values()];
|
|
}
|