feat: wild family complete — cave, mountain, river, lake, valley theme packs

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>
This commit is contained in:
slaguru666
2026-08-05 19:22:01 +01:00
co-authored by Claude Opus 5
parent d22421efbd
commit 7af690205c
15 changed files with 11757 additions and 7 deletions
File diff suppressed because it is too large Load Diff
@@ -13,6 +13,12 @@
"geometry": "barrow",
"motifs": 4
},
{
"id": "cave",
"label": "Cave",
"geometry": "cave",
"motifs": 4
},
{
"id": "church",
"label": "Church",
@@ -31,12 +37,24 @@
"geometry": "forest",
"motifs": 4
},
{
"id": "lake",
"label": "Lake",
"geometry": "fen",
"motifs": 4
},
{
"id": "market",
"label": "Market",
"geometry": "village",
"motifs": 4
},
{
"id": "mountain",
"label": "Mountain",
"geometry": "cave",
"motifs": 4
},
{
"id": "palace",
"label": "Palace",
@@ -49,6 +67,12 @@
"geometry": "village",
"motifs": 4
},
{
"id": "river",
"label": "River",
"geometry": "fen",
"motifs": 4
},
{
"id": "temple",
"label": "Temple",
@@ -61,6 +85,12 @@
"geometry": "village",
"motifs": 4
},
{
"id": "valley",
"label": "Valley",
"geometry": "forest",
"motifs": 4
},
{
"id": "village",
"label": "Village",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -3
View File
@@ -22,7 +22,13 @@
import { arcRole } from './director.mjs';
/** Fills {conscript}-style slots. */
const fill = (tpl, vars) => tpl.replace(/\{(\w+)\}/g, (_, k) => vars[k] ?? `{${k}}`);
const fill = (tpl, vars) => tpl.replace(/\{(\w+)\}/g, (_, k) => vars[k] ?? `{${k}}`)
// Conscripts are plural noun phrases ("the climbers"), so a template's `{conscript}'s`
// renders "the climbers's". English takes a bare apostrophe on a plural already ending in s.
.replace(/s's\b/g, "s'");
/** Claimants may already carry their own article — "The Grey" must not become "The The Grey". */
const named = c => (/^(the|a|an) /i.test(c) ? c : `The ${c}`);
/** Facet order used when a facet is exhausted — borrow from a neighbour before repeating. */
const FACET_ORDER = ['institution', 'ritual', 'demand', 'wound', 'anchor'];
@@ -62,12 +68,12 @@ export function generateSkeleton({ pack, areas = 6, ending = 'authored', rng })
const prize = { kind: appetite.prize, ...(pack.prizes?.[appetite.prize] ?? {}) };
const transgression =
`The ${claimant} ${fill(accommodation.text, { conscript: appetite.conscript })}, ` +
`${named(claimant)} ${fill(accommodation.text, { conscript: appetite.conscript })}, ` +
`so that ${appetite.purpose}.`;
// The bottom problem is the claimant itself — a person in a tomb, an office in a town.
const bottomProblem = {
label: `The ${claimant}`,
label: named(claimant),
wantNow: appetite.want,
failureState: `is ${appetite.lack} now, and cannot bear it`,
handledBy: 'giving them what they want, at a price',