World Recipe Authoring Guide
What a recipe is
A World Recipe is the declarative authoring contract for a mapv10 continent. It describes the geomorphology family the author wants the generator to produce: which orogens belong on which coasts, which cratons fill which interiors, what rainfall pattern governs the climate, how many basins and rivers of each kind the continent should carry, and which named features the author wants the generator to place. The generator runs the recipe under a chosen seed and emits a concrete continent that belongs to the requested family.
A recipe is seed-agnostic. The same recipe replayed against different seeds yields different concrete continents that share the same morphology. A South-America-like recipe under seed 1, seed 2, seed 7 produces three distinct continents, each with a coast-parallel western cordillera, an eastern shield craton, and a foreland basin between them.
A recipe is scale-agnostic. The scale preset (province-slice,
regional-slice, realm-slice, continent) is owned by GeneratorConfig,
not by the recipe. The same recipe runs at every scale preset; the resulting
continent has the same morphology family at every preset.
A recipe is deterministic. The four-tuple (seed, recipe, intent-directives, vocabulary) produces a byte-identical content_hash
across runs. The canonicalization block on every recipe makes this
contract explicit: sorted JSON keys, shortest round-trip f64 representation,
LF line endings, UTF-8 NFC encoding without a BOM.
A recipe is declarative. It does not encode procedural steps. It says "I want a continent with these orogens, these cratons, this rainfall, and these named features." The generator decides how to satisfy the request and reports satisfaction back to the author.
File location and CLI invocation
Recipes live under examples/map/mapv10/recipes/. The four starter presets
ship there:
preset-south-america-like.jsonpreset-australia-like.jsonpreset-europe-like.jsonpreset-africa-like.json
Each preset's companion intent-directives file lives in
examples/map/mapv10/recipes/intents/ with the same base name and an
.intents.json suffix.
The bundled vocabulary instance lives at
examples/map/mapv10/vocabulary/default-world-vocabulary.json. Recipes
reference it implicitly when vocabularyRef is omitted, or explicitly when
they want to ship their own vocabulary.
The CLI invocation that consumes a recipe is added in M11c. The planned shape:
mapv10-generator \
--scale-preset continent \
--seed 42 \
--recipe examples/map/mapv10/recipes/preset-south-america-like.json \
--intents examples/map/mapv10/recipes/intents/preset-south-america-like.intents.json \
--vocabulary examples/map/mapv10/vocabulary/default-world-vocabulary.json \
--out continent-lod6
Wave S commits the schemas, the bundled vocabulary, and the four starter presets. The CLI flags are deferred to M11c.
Top-level fields tour
Every recipe carries these top-level fields:
schemaVersion(const "mapv10-world-recipe-v1"). The schema version pin. Future revisions ship beside v1; recipes never silently migrate.id. A short slug, lower-kebab-case, three to sixty-four characters. Used in manifests, validation output, and capture filenames.displayName. Author-facing label up to 120 characters.description. Optional prose explaining the recipe's intent.defaultSeed. Optional integer suggestion for the canonical seed; the CLI is free to override.vocabularyRef. Optional relative path to a vocabulary instance. When omitted, the generator uses its bundled default vocabulary.archetype. The geomorphology declaration. See Archetype below.counts. Top-level count constraints with priorities. See Counts below.intentDirectivesRef. Optional relative path to a companion intent- directives file.satisfactionReporting. Configures how satisfaction is reported. Three booleans:emitUnsatisfiedHardFails(always true; recipes cannot opt out),emitSoftSatisfactionScores,haltOnHardFail.canonicalization. The determinism contract. Four const-valued fields; recipes cannot opt out.
Archetype
The archetype field is the heart of the recipe. It carries seven sub-fields:
orogens[]. A list of orogen declarations. Each declaration names anorogenKindsvocabulary token (e.g.active-margin,collisional,rift-flank,residual-eroded,transform-margin,hotspot-volcanic-arc,passive-margin), a placement mode, and per-belt parameters: length, height, age band, asymmetry factor, priority.cratons[]. A list of craton declarations. Each names acratonKindsvocabulary token (e.g.shield,platform,accreted-terrane), a placement mode, an age band, an area fraction range, a residual-highland policy, an optional interior-basin policy, and a priority.rainfall. A rainfall declaration naming arainfallPatternstoken (e.g.zonal-temperate,zonal-tropical,monsoonal,orographic-dominant,arid-continental,rift-driven), an orographic-strength range, an optional rain-shadow azimuth, and an optional priority.basinPolicy. Three sub-policies, one per basin class:forelandBasins,interiorBasins,endorheicBasins. Each carries a count range, a depth-meters range, and a priority.coastPolicy. Coast-shape policy:jaggednessrange,fjordLikelihoodrange, anislandPoliciestoken, anisthmusPoliciestoken, an optional priority.customAnchorPolicy. How directives conflict with the archetype:conflictResolutionis one ofhard-directive-overrides-archetype,archetype-overrides-soft-directive,directives-suggest-only, ordirectives-forbidden.maxDirectiveCountcaps the number of directives the recipe will accept.latitudeBandDegrees. The absolute geographic latitude band the continent spans in degrees.{ min, max }with both in[-90, 90],min < max, andmax - min >= 5°. Stage 5 (biomes + materials) projects each per-cell world-y onto this band to drive the Köppen- Geiger climate-zone proxy (tropical / subtropical / temperate / boreal / polar). Sign convention:y = 0(north edge of the world rectangle) maps tomax;y = world_height_km(south edge) maps tomin. The field is recipe geometry, not a soft request — the validator hard- fails the recipe at load time if the bounds are absent, swapped, outside[-90, 90], or span less than 5°. There is nopriorityfield because the band cannot be partially satisfied.
The four shipped presets declare bands that span their real-world counterparts:
| Preset | min | max | Climate range at y = 0..1 |
|---|---|---|---|
| preset-south-america-like | -55 | +12 | tropical (north) → subpolar (south) |
| preset-australia-like | -43 | -10 | tropical south → temperate south |
| preset-europe-like | +35 | +70 | temperate (south) → polar (north) |
| preset-africa-like | -35 | +38 | temperate north → subtropical south |
Placement modes
Orogens use three placement modes:
coast-parallel. The orogen runs along a fraction of a named coast edge (north,east,south,west). The recipe declarescoastFraction.fromFractionandcoastFraction.toFractionto constrain which segment of the coast hosts the belt. An optionaloffshoreOffsetKmrange pushes the belt slightly inland (positive) or offshore (negative, for island arcs).interior-axis. The orogen runs along an interior axis at a constrained azimuth. The recipe declaresazimuth.fromDegandazimuth.toDeg(0 deg is north, 90 deg is east, 180 deg is south, 270 deg is west). An optionalanchorFractionpoint anchors the axis at a specific fractional coordinate on the continent.archetype-derived. The generator chooses placement based on the rest of the archetype. Used when the author wants the generator to make the call.
Cratons use two placement modes: archetype-derived and anchor-fraction.
The latter pins the craton at a fractional coordinate.
Counts
The counts block carries seven count constraints, each on a different
morphological class:
primaryMountainBelts- the high, named ranges (typically the orogens declared inarchetype.orogens[]).secondaryRanges- smaller ranges, residual ridges, branched sub-belts.residualHighlands- low residual relief on the cratons.forelandBasins,interiorBasins,endorheicBasins- basin classes.majorRivers- the river-network trunks. Strahler order three or higher.
Each count constraint carries a range (with min and max), an optional
exact value, and a priority. When exact is set, the validator
hard-fails if the generated count is not exactly that number; otherwise the
count must fall within the range.
Satisfaction reporting
The satisfactionReporting block has three fields:
emitUnsatisfiedHardFails: const true. Hard-fail records are always emitted; the field isconst trueto prevent recipes from opting out.emitSoftSatisfactionScores. When true, the generator emits asatisfactionScorein[0, 1]for each soft request.haltOnHardFail. When true, the generator stops the run on the first hard-fail. When false, the run completes and the manifest carriesvalidationStatus: "fail".
Canonicalization
The canonicalization block has four const fields:
sortedKeys: const true.floatRepresentation: const "shortest-roundtrip-f64".lineEndings: const "lf".encoding: const "utf-8-nfc-no-bom".
These are not author choices; they are determinism invariants every recipe declares to make its conformance visible.
Hard / soft / suggest priorities
Three priorities govern every count, every basin class, every directive:
hard. The request must be satisfied. If it cannot be, the generator emits a satisfaction record withstate: unsatisfied, incrementsvalidation.satisfactionSummary.hardFailCount, setsmanifest.validationStatustofail, and (whenhaltOnHardFailis true) halts the run. A hard unsatisfied request is a generator-emitted failure, never a silent default.soft. The generator best-efforts the request and emits asatisfactionScorein[0, 1].stateissatisfied(score 1.0),soft-satisfied(between 0 and 1), orunsatisfied(score 0.0). The run continues;manifest.validationStatusis unaffected.suggest. The generator best-efforts the request and records satisfaction but does not emit a score. State issatisfiedorunsatisfied. The run continues;manifest.validationStatusis unaffected.
The validation.unsatisfied[] list inside terrainGenesis.json and
hydrology.json carries per-request reasons. Authors read it to understand
why a soft or hard request did not land.
Example walkthrough: preset-south-america-like.json
The South-America-like preset is the simplest case of an active-margin recipe. Walk through its blocks:
"archetype": {
"orogens": [
{
"id": "western-cordillera",
"kind": "active-margin",
"placement": {
"mode": "coast-parallel",
"coastFraction": { "edge": "west", "fromFraction": 0.05, "toFraction": 0.95 },
"offshoreOffsetKm": { "min": 40, "max": 120 }
},
...
}
],
...
}
The recipe declares a single primary orogen, western-cordillera, of kind
active-margin. It runs along the west coast from 5% to 95% of that edge,
40 to 120 km inland. Length is between 2,000 and 3,200 km; peak elevation is
between 3,500 and 5,500 m; age is young; asymmetry is between 2.5 and 4.0
(steep ocean flank, gentler interior flank). The priority is hard - the
generator must satisfy this declaration or fail loudly.
"cratons": [
{
"id": "eastern-shield",
"ageBand": "ancient",
"areaFraction": { "min": 0.40, "max": 0.60 },
"placement": {
"mode": "anchor-fraction",
"anchorFraction": { "xFraction": 0.65, "yFraction": 0.50 }
},
...
}
]
A single ancient shield craton occupies 40-60% of the continent area and is
anchored at 65% east, 50% north. The shield carries residual-ranges as its
residual-highland policy and wet-interior as its interior-basin policy. The
priority is hard.
"rainfall": {
"pattern": "orographic-dominant",
"orographicStrength": { "min": 0.65, "max": 0.85 },
"rainShadowAzimuth": { "fromDeg": 80, "toDeg": 120 },
"priority": "soft"
}
Rainfall is orographic-dominant: the dominant control is the western
cordillera's rain shadow. Strength is 0.65 to 0.85. Rain shadow azimuth is
between 80 deg and 120 deg (east-ish), meaning winds come from the west and
the cordillera blocks them. Priority is soft; the generator best-efforts
but does not hard-fail if the chosen rainfall pattern is slightly different.
"basinPolicy": {
"forelandBasins": { "count": { "min": 1, "max": 1 }, "depthMeters": { "min": 100, "max": 400 }, "priority": "hard" },
"interiorBasins": { "count": { "min": 1, "max": 2 }, "depthMeters": { "min": 50, "max": 250 }, "priority": "soft" },
"endorheicBasins": { "count": { "min": 0, "max": 1 }, "depthMeters": { "min": 200, "max": 800 }, "priority": "suggest" }
}
Exactly one foreland basin (hard); one or two interior basins (soft); zero or one endorheic basin (suggest). The foreland basin priority is hard because the AAA reference (Andes + Brazilian Shield) always has a foreland basin between the active-margin orogen and the shield.
"counts": {
"primaryMountainBelts": { "range": { "min": 1, "max": 1 }, "exact": 1, "priority": "hard" },
...
"majorRivers": { "range": { "min": 2, "max": 4 }, "priority": "soft" }
}
The top-level counts enforce: exactly one primary mountain belt (hard); one to three secondary ranges (soft); two to four major rivers (soft).
"intentDirectivesRef": "intents/preset-south-america-like.intents.json"
The recipe defers to its companion intents file for custom-anchor requests.
That file declares one directive: a soft custom-river-mouth near the east
coast (Amazon analog) requesting mustBeNavigable: true.
Composing a new recipe from scratch
Five-step cookbook:
-
Pick orogen kinds. Decide which mountain types belong on the continent. Look at the AAA references in
examples/map/mapv10/vocabulary/default-world-vocabulary.jsonto choose tokens. Active-margin for Andean-like; collisional for Alpine-like; rift-flank for African-Rift-like; residual-eroded for old continental highlands; transform-margin for California-coast-like. -
Place cratons. Decide which craton kinds anchor the interior. Shield for ancient peneplain interiors; platform for sediment-covered interiors; accreted-terrane for collision-formed mosaic interiors.
-
Declare rainfall. Choose a
rainfallPatternstoken that matches the latitude and dominant wind direction. Set orographic strength to control the rain-shadow contrast. Set rain-shadow azimuth to align with the wind direction. -
Set basin counts. Decide how many basins of each kind belong. An active-margin recipe wants exactly one foreland basin. An old craton recipe wants multiple endorheic basins. A collisional recipe wants foreland basins on both flanks.
-
Check satisfaction. Run the recipe under several seeds. Read the
validation.unsatisfied[]list to see what the generator could not satisfy. Adjust priorities: soft-fail requests that should be optional; hard-fail requests that are truly required; suggest-only requests that are aspirational.
Determinism contract
The canonicalization block declares what makes a recipe deterministic. The contract:
- The same
(seed, recipe, intent-directives, vocabulary)four-tuple produces a byte-identicalcontent_hashacross runs. - Changing the seed produces a different continent but the same morphology family.
- Changing the recipe (any field) changes the content hash. Renaming a recipe id changes the content hash.
- Changing the directives file changes the content hash even if no directive resolves differently.
- Changing the vocabulary instance changes the content hash even if no token is added or removed (because the registry serialization changes).
What NOT to change in an existing recipe:
- The
idfield. Renaming the id breaks the manifest cross-reference. - The
schemaVersion. Bumping the version means a new schema is in play. - Any
const-valued field incanonicalization. The fields are const for a reason; the validator rejects recipes that override them.
Common authoring mistakes
- Soft-vs-hard miscalibration. Marking too many counts as
hardproduces recipes that almost always hard-fail because the generator cannot satisfy every constraint simultaneously. As a rule of thumb, mark one or two foundation declarations as hard (the primary orogen, the primary craton, the foreland basin if the AAA reference always has one); mark everything else as soft. Usesuggestfor aspirational features the recipe does not need. - Impossible coast-parallel arcs. A
coast-parallelplacement withcoastFraction.fromFractionclose to 0.0 andtoFractionclose to 1.0 asks for an orogen that spans nearly the entire coast. If the coast is short (smallscalePreset), the orogen may not fit. Use moderate fractions (0.1-0.9) until the recipe is validated at every scale preset. - Over-constrained counts. Setting
exacton a count constraint that the generator typically cannot satisfy produces guaranteed hard-fails. Useexactonly for foundation declarations where the AAA reference is unambiguous (e.g. "exactly one primary belt for an active-margin recipe"). - Conflicting directives and archetype. A directive with placement
inside an archetype-declared orogen runs the
customAnchorPolicy.conflictResolutionpolicy. Mark the conflictResolution explicitly; do not assume the default. - Forgetting
intentDirectivesRef. A recipe that references directives must point at them withintentDirectivesRef. The schema does not enforce the cross-reference (it is a relative path), but the validator script does.