Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Mod-owned terrain recipes

The shipping terrain path is a server declaration in the WASM game manifest. Flagship uses the same declaration and host loader available to other mods. The engine owns the evaluator, validation, exact/coarse generation, caching and persistence; the mod owns its recipe, biomes, material names and default choice.

Authoring

In the guest’s Plugin::build:

app.add_terrain_recipe(
    "overworld",
    include_str!("../assets/terrain/world.ron"),
    Some(include_str!("../assets/terrain/baked.ron")),
    true,
);

The arguments are a local provider id, procedural recipe RON, optional baked heightfield recipe RON, and whether it is the default. A terrain-only plugin is valid. Declarations appear only in the server manifest. Manifest format 11 requires rebuilding existing WASM mods; older versions fail with a rebuild message rather than being decoded under a different layout.

The host prefixes the local id with the authenticated package id, regardless of a guest’s claimed plugin namespace. For Flagship the key is flagship-game:overworld. Duplicate keys fail. A nonempty catalog needs exactly one default unless the host selects a key explicitly; multiple defaults fail instead of choosing by load order.

Native hosts discover directory and .mod archive providers alongside bundled mods, honoring directory overrides and package dependency/collision checks. Disk providers use the ordinary game-module sandbox; manifest reads do not fire world-ready hooks. Terrain preflight errors are fatal, and a selected provider that is later disabled by the orchestrator prevents world startup.

Native hosts support the MANIFOLD_TERRAIN_RECIPE=package:local-id override. Browser singleplayer selects the catalog default. Empty catalogs are permitted for native engine harnesses; they use the existing flat test world. A requested missing provider is an error.

The current evaluator is the versioned scripted Cartesian terrain implementation. Recipes are limited to 1 MiB, four to 64 biomes, seven validated stage kinds, bounded spawn radii/heights and feature spacing/chance. Material names resolve against the final block registry and unknown names fail at boot. The v1 evaluator uses biome slots 0–3 as lowland, shore, snow and exposed terrain for baked fields; procedural trees use slot 0. These are evaluator semantics, not Flagship block names. Use the game-owned recipes in game/flagship-game/assets/terrain/ as complete examples. Semantic recipe/biome ids inside RON contribute to content hashing; they do not grant registration authority in that namespace.

This contract installs data into the existing WorldGenerator path, including its exact generation, certified coarse pages and semantic identities. It does not execute arbitrary guest callbacks for each voxel. New bounded stage operators, composable contributions and richer biome rules remain extensions of the terrain design; do not bypass the shared evaluator or introduce a Flagship-only hook.

Saves and compatibility

Native and browser OPFS saves bind terrain_recipe_v1 in world_meta to UTF-8 <provider-key>\n<resolved-recipe-identity-hex>. The identity includes the seed, recipe parameters and material names; baked worlds also include the artifact digest. Reopening with changed content/provider fails before generation. Restore the original mod or create a new world; do not delete this binding as a migration. Flat native harness saves receive a distinct binding too.

A save without a provider binding adopts the installed recipe on first open; subsequent opens enforce it. That save may have been generated with different settings. Back up the world before changing its content set. Frozen fixtures verify procedural and baked recipe identities and generated output.

MANIFOLD_TERRAIN_BAKE imports an immutable field only into a new native world, which persists it in terrain_bake_v1. Reopening uses those saved bytes and the mod’s baked palette. Browser singleplayer reads the same seed, baked bytes and recipe binding from its OPFS save; there is still no browser UI for importing a new bake. Multiplayer browser clients consume the server’s streamed world.

Diagnostic tools take the recipe explicitly: lod-oracle --recipe FILE and baked_probe <artifact> <blocks-dir> <recipe.ron> [--chunks repetitions].

Regression coverage

  • A terrain-only guest emits its declaration through the ordinary manifest path.
  • A non-Flagship provider installs and generates with a non-Flagship palette.
  • Unknown materials, missing/default conflicts, duplicate ids and namespace spoofing fail; explicit selection is independent of declaration order.
  • The compiled Flagship WASM guest is decoded by the real loader and compared with frozen recipe identities and generated chunks.
  • Existing exact/coarse and baked terrain corpora remain compatibility checks.
  • Save tests check idempotent binding, reopen and rejected recipe/provider changes.

Run the focused integration gates from the repository root:

cargo test -p flagship --test suite terrain_mod::
cargo test -p manifold-wasm-host-core --lib terrain_fold
cargo test -p manifold-mod-substrate --test terrain_recipe
cargo test -p manifold-saves --test suite roundtrip::terrain_recipe_binding
# After the normal browser build and e2e npm install:
node crates/manifold-host-web/e2e/assert-terrain-mod.mjs

Optional water networks

Water-enabled baked worlds attach their persisted water network through TerrainCatalog::install_with_water_network. The selected mod still owns the baked recipe and material palette; the water path does not substitute an engine-owned recipe. A network requires a matching baked field, and the resolved generator identity includes the network. Native save reopen validates the stored network before binding the provider and recipe identity.