Files
RingBRP/docs/CREATURE_FORGE_PLAN.md
T
slaguru666andClaude Opus 5 10f588b666 Plan: the creature forge
Five parts - schema and validator, lethality harness, forge CLI, procedural portraits, Foundry importer - built on three findings: the species machinery (vesh, cadence) is fully implemented and completely unused, the published measured lethality tables cannot be reproduced by anything in the repo, and RINGBRP.figures already holds the per-species geometry procedural portraits would need.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 21:08:36 +01:00

239 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CREATURE FORGE — plan
A tool for making NPCs and monsters that are **correct by construction, measured for
lethality, and never baseline-human by default**. Plus art that costs nothing and cannot
contradict the statblock.
Status: plan only. Nothing below is built.
---
## Where things actually stand
| | |
|---|---|
| Statblocks in repo | 76, across `tools/content.mjs` (NPCS) and four scenario `*_CAST` arrays |
| Species profiles the engine supports | 3 — `baseline`, `vesh`, `cadence` |
| Statblocks using anything but `baseline` | **0** |
| Combat simulator | **none committed** |
| NPC portraits | **none** — `art/portraits/` holds the 16 PC portraits only |
| Validation | `check-kits` and `check-scenarios`, each with its own ad-hoc resolve logic |
Three findings drive the whole design.
**1. The monster machinery is built and unused.** `RINGBRP.locationTables` defines
`vesh` (bracing limbs, grasping limbs, a ridge instead of a head) and `cadence` (five
bodies, no head, `frac: 0.28` each — a swarm that takes damage as a collective). Both
have full hit-location tables and paper-doll figures. Every creature in the game is a
`baseline` human anyway. The tool's job is not to make statblocks faster — it is to make
*non-human* statblocks reachable.
**2. The measured lethality tables cannot be reproduced.** THROUGH TRAIN Act Three
publishes "MEASURED, 400 runs each, four duty-roster agents against the six" with a wipe
rate of 24%. Nothing in the repo can produce that number. It is currently a claim, not a
result, and the same is true of the starter's tables.
**3. The art substrate already exists.** `RINGBRP.figures` holds per-species body
geometry as SVG primitives — `head: { t: "ellipse", cx: 60, cy: 21, rx: 15, ry: 17 }` and
so on — because the combat tab draws a paper doll from it. Portraits generated from that
same data **cannot disagree with the creature's hit locations**, because they are the
same numbers.
---
## Shape
Five parts. Each ships alone and is useful alone.
```
tools/creature-schema.mjs the shape of a creature, and the only validator
tools/simulate.mjs the lethality harness
tools/forge.mjs the authoring CLI
tools/make-portraits.mjs procedural art
ringbrp.mjs (+ a template) the in-Foundry importer
```
The CLI is the source of truth: it writes spec objects into the repo, `npm run build`
bakes them into packs, and the Foundry side only ever *reads* built packs. Nothing can be
authored in the world and get lost outside git.
---
## Part 1 · The schema, and one definition of valid
`tools/creature-schema.mjs` describes every field of a creature spec — the shape already
in use, formalised, not changed. No churn to the 76 existing entries.
What makes it worth doing is where the allowed values come from: they are **read live**
from `SKILL_CATALOGUE`, `WEAPONS`, `ARMOURS`, `GEAR`, `TALENTS` and
`RINGBRP.locationTables`. A skill or kit key that does not exist cannot be described as
valid, because the catalogue is the enumeration.
```js
export function validate(spec) // -> [] or [{ path, problem, nearest }]
export function describe(field) // -> allowed values, for the CLI and for errors
```
`nearest` matters: a typo should come back as `unknown skill "stelth" — did you mean
"stealth"?`, not as a stack trace three files away.
**Then rewire the existing guards to call it.** `check-kits` and `check-scenarios` each
carry their own resolve logic today; both become thin callers. One definition of valid,
enforced in one place, which is the same argument `rules.mjs` already makes for the rules
journal.
*Ships as:* no behaviour change, all six guards still green, 76 creatures validate clean.
If any of them do not, that is a bug found for free.
---
## Part 2 · The lethality harness
`tools/simulate.mjs`. The centrepiece, and the reason to do this at all.
**It must import the real rules, never reimplement them.** A simulator with its own copy
of the combat loop measures a different game than the one being played, and it will drift
silently. Everything comes from `rules.mjs` and `ringbrp.mjs`:
- d100 roll-under, banded critical / special / success / failure / fumble, 96–99 always
fail, 00 always fumbles, 1% floor
- Reaction Lanes: `3 × max(⌈DEX/3⌉, ⌈readiness/10⌉)` less carried load, plus d6,
**re-rolled every round including the first**
- Graded defences: a defence stops a blow of its own quality or worse outright; against
something better it reduces by as many steps as it was outclassed by; only a critical
stops a critical
- Hit locations from the creature's own species table; major wounds; damage modifier
- Fire modes — slow shoots better then costs you, rapid bursts, beams held on target
```
node tools/simulate.mjs --creature keepers --party 4 --runs 400 --seed 11
node tools/simulate.mjs --creature keepers --party holloway,okonkwo,finch,rahimi
node tools/simulate.mjs --sweep tier --target-wipe 0.10
```
Seeded PRNG throughout, so a creature + party + seed always yields the same numbers.
That is what makes a published table checkable rather than remembered.
Output is the shape the scenarios already use — fraction hurt, fraction down, median
rounds, wipe rate — so the existing tables can be **regenerated and corrected**, and
`--sweep` answers the question the GM actually has: *what tier makes this a fight they
survive four times in five?*
Then `tools/check-lethality.mjs` joins the guards: every creature declares a band
(`nuisance` / `dangerous` / `lethal`), and the build fails if it simulates outside it. A
creature cannot quietly become a party-wiper between versions.
*Ships as:* a seventh guard, plus regenerated measured tables in THROUGH_TRAIN and the
starter. Expect the published numbers to move. That is the point.
---
## Part 3 · The forge CLI
`tools/forge.mjs`. Deliberately **not** a form-filler — speed of typing is not the
problem. It is a derivation tool, and its output is explainable.
```
node tools/forge.mjs new --species vesh --size large --role ambusher --tier 3
node tools/forge.mjs explain keepers
node tools/forge.mjs write <key> --into tools/scenario-throughtrain.mjs
```
`new` derives characteristics from the species profile and a size band, distributes
skills from a role template, and picks kit that resolves — then validates its own output
before printing. It emits a spec object as **source**, formatted to match the file it is
going into, because the repo's whole posture is that content lives in code.
`explain` prints which rule produced each number: *`STR 15` — large band (13–17), vesh
median +2*. A statblock you cannot interrogate is one you cannot tune.
Role templates and size bands live beside the schema so they are data, not code, and a
`--tier` bump is a single multiplier a GM can reason about.
---
## Part 4 · Procedural portraits
`tools/make-portraits.mjs`, modelled directly on `tools/make-icons.mjs` — which already
generates all 292 icons as SVG with no external art.
A portrait is composed, not drawn:
- **Silhouette** from `RINGBRP.figures[species].parts`, scaled by SIZ. A `cadence`
creature gets five bodies because its location table has five; a `vesh` gets a ridge
instead of a head for the same reason.
- **Tile and era treatment** reusing the icon set's existing four — antique notched,
modern plain, future cut corner with a scan line, anomalous broken rim that does not
close. A far-side thing looks far-side at 32px in an actor list.
- **Detail layer** keyed on the creature's own data: limb count from the location table,
plating from `naturalArmour`, a weapon silhouette from its primary weapon.
- **Hue** from the icon set's category families, so a creature portrait sits beside the
icons without clashing.
Deterministic from the creature key, so the same creature always yields the same file and
art does not churn in git. Output SVG, matching `icons/` — scalable, tiny, and diffable.
```
npm run portraits
```
**The honest trade.** These will read as diagrammatic — a species-accurate silhouette on
a treated tile, not an illustration. They will not look like the 16 Midjourney PC
portraits, and putting the two side by side will show it. Two ways out, neither needed on
day one: regenerate the PC portraits procedurally too so the whole cast matches, or have
the tool also emit a Midjourney prompt per creature so any individual one can be upgraded
by hand later. The second is about an hour's work on top and worth taking.
---
## Part 5 · The Foundry importer
A GM-only panel in the system module. Small on purpose.
- Lists creatures from the built packs, filtered by species, tier and era
- **Forge a variant**: apply a tier or size modifier to a pack creature and drop it into
the current scene as an unlinked actor, for when the table goes somewhere the prep did
not
- Shows the simulated lethality band against the current party, read from the data the
harness baked in
It reads built packs only and writes nothing back to the repo, so it cannot diverge from
the CLI. A variant forged at the table is a throwaway by design; if it earns a place, it
gets authored properly in the morning.
---
## Order of work
| Phase | Deliverable | Why here |
|---|---|---|
| 1 | Schema + validator, guards rewired | Pure safety net. No new content, no risk, pays off immediately. |
| 2 | Simulator + `check-lethality`, tables regenerated | Turns published claims into results. Biggest single win. |
| 3 | Forge CLI | Needs 1 to validate against and 2 to tune against. |
| 4 | Procedural portraits | Independent of 1–3; could move earlier if wanted. |
| 5 | Foundry importer | Needs built packs carrying lethality data, so it goes last. |
Phases 1 and 2 are worth doing even if 3–5 never happen.
---
## Risks
- **The simulator is the whole bet.** If it reimplements rules instead of importing them,
it measures a fiction. Mitigation: import from `rules.mjs` only, and cross-check the
harness against the 67 existing behavioural tests before trusting a single number.
- **`vesh` and `cadence` have never been exercised.** No creature has ever used them, so
the first non-baseline creature is also the first test of those code paths, in the
engine *and* in the sheet rendering. Budget for engine bugs, not just tool bugs.
- **Regenerated tables will contradict published ones.** THROUGH_TRAIN's 24% wipe rate
may not survive measurement. Better found now than at a convention table, but the
scenario prose will need revisiting.
- **Portrait families will not match** until the PCs are regenerated or the prompt export
is built. Covered above.
- **Scope drift toward a full monster designer.** The chosen pillars are lethality,
validation and range. Authoring speed was explicitly *not* one — resist the pretty form.
---
*Private convention play materials — not for sale or distribution.*