Unreleased. The warning that v0.6.4 deleted is restored, but not as the
string heuristic that caused both faults — rosters carry beforeInitiative
now, and the renderers print it. 23 rosters have it: the 18 whose harmedBy
restricts what works, plus 5 Troll rosters that carried the instruction in
prose and so had two conventions between them. The prose copies are gone,
so it is said exactly once wherever it is said.
validate-pack enforces both halves — ONLY implies the field, and the phrase
may not appear in prose — and core/test.mjs now runs the validator over all
sixteen packs, so a pack regression fails the suite rather than waiting for
someone to remember the tool. Both rules were confirmed by breaking a pack
deliberately.
enter()'s decisions move to module/stage.mjs behind injected effects, which
makes the failure paths runnable under plain node. That immediately found a
bug in the first version of the extraction: Promise.resolve(fx.stage())
does not catch a synchronous throw, so a Forge that threw rather than
rejected would have taken the whole of enter() down. Nineteen of the new
tests are that matrix — failed scene, failed population with and without a
roster, failed hoard, failed cards, async rejection, and a Forge that
returns nothing.
The contract those tests hold: the scene is the commit point. Fail there
and nothing was entered, so a retry is safe; succeed and the turn advances
before anything else can fail, so a retry cannot restage. Everything after
degrades with a warning that says what was actually lost — the encounter
failure no longer promises a roster the area does not have, and the hoard
failure is no longer swallowed to the console.
Also: game.delve.loadPack is the validating resolver, so raiseDungeon can
no longer stage from a pack with no forgeStageType.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The foe block broke twice in two releases, both times because each
surface decided for itself, in its own hand-written ternary, which of
the cases it was in. classifyFoes() in the new module/foes.mjs makes that
choice once — forged, planned, unavailable, none — and both surfaces
switch on it. foes.mjs is Foundry-free on purpose, so foundry-module/
test.mjs can run it under plain node with no shim. Fourteen tests, each
one a case that shipped broken or nearly did.
enter() now survives a Forge that throws. It had no catch, so a failure
left the scene raised, no cards posted and the turn not advanced, and a
retry would stage the area twice — while the comment above it claimed
the branch handled exactly that. A failed stage stops before the turn
advances, since there is nothing to run; a failed population keeps going,
because the scene is up and the planned roster stands in.
An area with combat heat but neither actors nor a roster used to render
nothing at all. It now says so and points at the decision and fallback.
The "say so before initiative" addendum is gone from all three renderers.
It was gated on the literal word ONLY, and the palace, port and village
battle rosters already end with that exact phrase, so it printed twice.
The packs that need the instruction carry it themselves.
Also: draft() spreads params before the pack, like raiseDungeon; and
packById validates the cached pack rather than trusting it, so the
fail-closed rule holds at boot too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GENERATOR_VERSION had sat at 0.1.0 while the emitted object gained the
authored layer and the content model moved under it, so a delve file
claimed a generator that no longer existed — both samples say 0.1.0 and
only one of them has an authored layer. Nothing gates on the stamp, so
this is informational only and old files still load. The comment on it
now says what it tracks and when to bump it, which is what stopped it
being bumped before.
test.mjs read ./content/barrow.json relative to the working directory, so
it only ran from inside core/ and crashed from the repo root. It now
resolves from the module file, matching build-index.mjs and
validate-pack.mjs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five wild-kernel themes, each 4 motifs:
cave dark · deep · echo · keeping (cave geometry)
mountain ascent · cold · pass · weight (cave)
river crossing · current · course · drowned (fen)
lake stillness · mirror · sunken · rising (fen)
valley ridges · shelter · bottom · weather (forest)
Two rendering fixes surfaced by the new claimants, both in skeleton.mjs:
- `{conscript}'s` rendered "the climbers's"; conscripts are plural noun
phrases, so fill() now collapses s's to s'.
- claimants carrying their own article rendered "The The Grey"; named()
only prefixes when one is absent.
All 16 themes validate; 39 tests pass. Module → 0.6.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Barrow and Castle share a kernel — a dead claimant whose appetite outlived them.
A market or a river has no corpse in it, so the other two families needed the
kernel to generalise, and it does: something wanted a thing, and something was
done to keep it that way. Only who "something" is changes.
TOWN, on village geometry. The appetite belongs to the place and the claimants
are offices — a Corporation, a Watch Committee, a Guild of Mercers. It wants to
be prosperous, respectable, safe or necessary, and it priced people out,
chartered them away or struck them from the rolls to stay that way. Motifs:
ledger, appearances, vigilance, thoroughfare.
FOREST, on forest geometry. The appetite belongs to the wood, and the claimants
are its own names for itself. It wants to be entered, fed, spread or left
uncounted, and it took people, grew through them, or let them wander. Motifs:
paths, tithe, reclaiming, trackless.
Settlements also needed their own naming rule. A barrow is named for its
occupant; a town is not — it has a name and the claimant merely runs it. Packs
declare placeNameStyle, so "The Town of Watch Committee" is now Cold Harrow, run
by the Watch Committee.
Three of fifteen requested themes are done. The remaining twelve are the same
job repeated rather than a design question, since all three kernels now exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Groundwork for many themes, plus the first new one.
A pack validator enforces every structural rule this project learned the hard
way: situations that render as broken English, decisions that gate progress with
no alternative, motifs too thin to fill six areas without repeating, rosters
that do not say what harms them. Fifteen packs cannot be hand-checked; this
checks them in a second.
Themes are now discovered from content/index.json rather than hardcoded, so the
CLI takes --theme and --themes, the Foundry dropdown fills itself, and packs
load on demand instead of all at once.
Castle: four appetites — to be feared, never to give ground, to be obeyed at
once, the name to go on — over barrow geometry, since a keep is rooms and
corridors. 168 kernels from its own claimants and accommodations.
Fixed a bug the second theme exposed: the question the bottom problem asks was
hardcoded against barrow's appetite ids, so every other theme fell through to a
generic "Well?". It lives on the appetite in the pack now, and the validator
requires it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eleven files carried "Private VANITY development material — not for sale or
distribution", which contradicted the repo the moment it went public.
Two replacements, because the files do two different jobs. core/render.mjs
*emits* its footer into every generated delve, so that one now says where the
document came from rather than restricting what can be done with it — a
generated dungeon belongs to whoever generated it. The project's own documents
— DESIGN.md, the paper delve, both playtests, the spike report — read as
development notes in a public repo.
Samples regenerated so they carry the new line. The worksheet and play sheet
never had a footer and still do not; they are the GM's own working documents.
Module to 0.1.1: the v0.1.0 zip vendors the old render.mjs and would stamp the
stale footer on everything it generated. Cutting a version rather than swapping
assets under the existing one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What was on git could not be installed. foundry-module/ referenced core/ rather
than containing it, so an installed copy had no generator at all — it only ever
worked here because the live module directory had core/ copied in by hand.
core/ is now vendored into foundry-module/module/core/, minus cli.mjs and
test.mjs which are desk tools. module.json points manifest and download at
release assets, the way mapwright distributes, so a remote server can install by
URL without reaching into the repo tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>