Skip to content

Recipes, scenarios, and simulation

Recipes describe board intent. Scenarios add game content. Simulation scripts exercise the same public API a game loop would use.

A recipe is the smallest useful serialized authoring format. It can contain:

  • a board shape and seed.
  • explicit tile or placement steps.
  • layout archetypes local to the recipe.
  • generated layoutFills for built-in archetypes.
  • generated pieceDeclarations and pieceFills for custom packs.
  • manifest validation requirements.

Use recipes when a board needs to be saved, generated by tools, checked into a game repository, or reviewed in documentation.

import {
createGameboardPlanFromRecipe,
inspectGameboardRecipe,
} from 'declarative-hex-worlds/recipe';
import { freeManifest } from 'declarative-hex-worlds/manifest/free';
const preflight = inspectGameboardRecipe(recipeJson, {
plan: { assetCatalog: freeManifest },
});
const recipeErrors = preflight.violations.filter((issue) => issue.severity === 'error');
if (recipeErrors.length > 0) {
throw new Error(recipeErrors.map((issue) => issue.message).join('\n'));
}
const plan = createGameboardPlanFromRecipe(recipeJson);

Recipe generation should be deterministic. Use one seed for board terrain and a separate seed namespace for layout fills, piece fills, spawn groups, or patrols when a game needs stable diffs.

Layout fills are the core random-board primitive. They combine a placement role, candidate-site criteria, density or count, and deterministic scoring.

Common cases:

  • trees use scatter or tree archetypes, often with fill, maxCount, maxPerTile, and a shared slot group.
  • harbors use coast tiles adjacent to water.
  • landmarks and towers use larger footprints and occupancy reservations.
  • units use spawn-like criteria and usually remain low-density.
  • loose props can allow occupied tiles if they use a non-blocking slot group.

Piece fills are the custom-pack version of layout fills. They select reusable piece declarations by id, asset id, role, source, tag, or local-only state, then expand the selection into deterministic layout rules.

Run analysis before committing generated placements into a plan or runtime:

import { analyzeGameboardLayoutFill } from 'declarative-hex-worlds/layout';
import { inspectSeededGameboardPieceFills } from 'declarative-hex-worlds/rules';
const layoutReport = analyzeGameboardLayoutFill(plan, layoutFill);
const pieceReport = inspectSeededGameboardPieceFills(plan, registry, pieceFills, {
seed: 'campaign-01:custom-pieces',
});

Warnings are useful, not cosmetic. A low candidate count, clamped fill count, or large footprint rejection usually means the authored board cannot support the requested density.

A scenario combines a recipe with gameplay objects:

  • named spawn groups.
  • patrol route plans.
  • actors, including players, NPCs, enemies, props, blockers, and interactives.
  • movement agents and movement profiles.
  • patrol agents.
  • quests.
  • renderer source URL maps for local custom pieces.

Scenarios are the preferred test fixture for integration and browser coverage because they force consumers to use the package the way a game does.

import { createGameboardRuntimeFromScenario } from 'declarative-hex-worlds/runtime';
import { validateGameboardScenario } from 'declarative-hex-worlds/scenario';
const validation = validateGameboardScenario(scenarioJson, {
plan: { assetCatalog: manifest },
});
const scenarioErrors = validation.filter((issue) => issue.severity === 'error');
if (scenarioErrors.length > 0) {
throw new Error(scenarioErrors.map((issue) => issue.message).join('\n'));
}
const runtime = createGameboardRuntimeFromScenario(scenarioJson);

Scenario validation should fail duplicate ids, unresolved spawn groups, duplicate spawn-location claims, missing patrol routes, broken actor references, broken quest references, missing manifest assets, and invalid runtime placement requests.

Simulation scripts are deterministic headless game-loop tests. They can run:

  • movement commands.
  • run-systems ticks for patrols, movement, commands, actor targets, and quests.
  • command previews and command execution.
  • actor-target inspections and chosen-target dispatch.
  • actor spawn, update, and removal mutations.
  • placement spawn, update, move, and removal mutations.
  • expectation checks against actors, quests, movement, events, commands, and final placement records.
import {
runGameboardScenarioSimulationScript,
validateGameboardScenarioSimulationScript,
} from 'declarative-hex-worlds/simulation';
const scriptValidation = validateGameboardScenarioSimulationScript(scriptJson, {
scenario: scenarioJson,
});
const simulationErrors = scriptValidation.filter((issue) => issue.severity === 'error');
if (simulationErrors.length > 0) {
throw new Error(simulationErrors.map((issue) => issue.message).join('\n'));
}
const result = runGameboardScenarioSimulationScript(scenarioJson, scriptJson);

The SimpleRPG fixtures in the package are the canonical acceptance shape. They spawn a player, traverse a fixed board, classify props and enemies through public actor APIs, advance quests, and exercise seeded content through the same scenario and simulation layer. The packaged usage example also exposes summarizeSimpleRpgGuidePublicApiExercises(), which joins every current guide-facing public API to SimpleRPG evidence so app smoke tests can fail on missing or stale guide/API representation. Evidence modes are memberships, not exclusive buckets, so one API can be executable smoke and also prove seeded generation, blueprint compilation, manifest packaging, compatibility adapters, or visual coverage. Its executable helper smoke directly invokes 40 guide-facing helper APIs and checks the 404 KayKit public treatment records plus all 19 decomposed guide pages, so catalog coverage is exercised through package imports as well as the generated ledger.

Use the CLI to preflight serialized content outside a test runner:

Terminal window
declarative-hex-worlds validate-recipe \
--recipe docs/examples/generated-piece-scenario.recipe.json \
--outPlan /tmp/generated-piece-scenario.plan.json
declarative-hex-worlds validate-scenario \
--scenario <your-scenario.json> \
--manifest assets/free/manifest.json \
--outPlan /tmp/simple-rpg-plan.json
declarative-hex-worlds validate-simulation \
--scenario <your-scenario.json> \
--script <your-simulation-script.json> \
--manifest assets/free/manifest.json
declarative-hex-worlds simulate-scenario \
--scenario <your-scenario.json> \
--script <your-simulation-script.json> \
--manifest assets/free/manifest.json \
--out /tmp/simple-rpg-simulation.json \
--outInterop /tmp/simple-rpg-interop.json

For browser E2E, render the same scenarios rather than creating private renderer-only fixtures. That keeps screenshots tied to the public contract.