---
title: "Terrain and heightmaps procedural generation biomes and foliage"
description: "You want a natural outdoor world: rolling hills, mountains, rivers, biomes, and scattered vegetation. Engine terrain is a HEIGHTFIELD (a height per x,z sample), not a voxel volume. Every natural landscape verb is a terrain semantic patch op. For binding an existing terrain resource and basic sculpting, start at terrain-basics; this playbook covers the procedural and ecological ops."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/terrain-and-heightmaps/
---

# Terrain and heightmaps procedural generation biomes and foliage

## When to use

You want a natural outdoor world: rolling hills, mountains, rivers, biomes, and
scattered vegetation. Engine terrain is a HEIGHTFIELD (a height per x,z sample),
not a voxel volume. Every natural landscape verb is a terrain semantic patch op.
For binding an existing terrain resource and basic sculpting, start at
terrain-basics; this playbook covers the procedural and ecological ops.

## The recipe

1. Create a terrain resource asset: `project_create_asset` (`project.asset.create`)
   with `assetKind: "terrain"`. This backs the binding; the dedicated
   `terrain.resource.create` action is `planned` with no AI tool yet.
2. Shape the land with `project_apply_terrain_semantic_patch`
   (`terrain.patch.apply`). Emit a batch of ops (up to 1024) drawn from the nine:
   - `create` seeds base extent: `width`, `depth`, `maxHeight`, optional `seed`,
     optional `biome`.
   - `generate` reshapes the heights of a rectangle with a registered recipe:
     `regionKind` one of `heightfield_tile`/`mesh_chunk`/`procedural_recipe`,
     `min`/`max` (Vec2), `recipe`, optional `seed`. The schema lists `recipe` as
     optional, but the write seam refuses a generate without one and refuses a
     recipe name outside the terrain vocabulary registry (`TERRAIN_RECIPE_IDS` in
     `packages/content-schema/src/terrainVocabulary.ts`); the refusal lists the
     accepted ids and the landscape-and-terrain skill names them. You choose the
     recipe and seed; you cannot supply raw octave/frequency numbers.
   - `sculpt` (`mode` raise/lower/flatten/smooth at `x,z` with `radius`,
     `strength`) for hand shaping.
   - `biome` repaints the texture layers of a rectangle with a registered
     `biomeKey` from `TERRAIN_BIOME_IDS` (an unregistered name is refused with the
     accepted ids); `paint` (`layerIndex` 0..15) paints a texture layer.
   - `road` lays a `road`/`path`/`river` polyline (`points`, `width`, river
     `depth`).
   - `erosion` (`min`/`max`, `iterations`, `strength`) runs ONE deterministic
     thermal-style smoothing pass over the WHOLE terrain: it softens peaks and
     fills pits everywhere. The compiler reads only `iterations` and
     `strength`; `mode` (hydraulic/thermal), the `min`/`max` region and `seed`
     are accepted and not read, so it neither stays inside its rectangle nor
     carves water valleys.
   - `scatter` paints INSTANCED foliage (`foliageKey`, `density` per 100 sq
     units, `min`/`max`, scale jitter) over its whole rectangle. The schema
     accepts a `layerIndex` gate, but neither foliage producer reads it, so
     scatter is not confined to a painted layer. This is the
     one real instanced-content path in terrain: it bakes a
     `foliage_instance_buffer` artifact the renderer instances, so thousands of
     grass blades cost one batch, not one entity each. Real geometry exists
     only for the `grass`/`tall_grass` and `rock`/`boulder` keys; any other
     foliageKey, tree and flower keys included, renders as a small placeholder
     cone today.
   - `cutHole` removes a disc of surface (for a cave mouth or pit). It is a
     SURFACE hole, not a volumetric carve.
3. Build artifacts: `terrain_build_artifacts` (`terrain.artifacts.build`) turns
   the ops into the runtime meshes and collision (`height_tile`, `mesh_chunk`,
   `collision_proxy`, `nav_source_mesh`, `foliage_instance_buffer`).
4. Bind: attach `TerrainComponent`
   `{ "type":"TerrainComponent", "enabled": true, "terrainAssetId": <asset id> }`.
5. Sample from scripts with `rendering.v1`: `ctx.terrain.heightAt`,
   `ctx.terrain.normalAt`, `ctx.terrain.slopeAt`, `ctx.terrain.raycastDown`,
   `ctx.terrain.sampleLayerWeights` (place props on the surface, gate steep slopes).

## The voxel nuance (read before promising minecraft)

A blocky, mineable, chunk-rendered voxel world is NOT authorable through terrain
capabilities. The one path to a voxel world is instantiating the whole template
at PROJECT CREATION: `project_create_game` with
`request.creationIntent { "mode":"template", "templateId":"voxel_frontier" }`
(`voxel_frontier` is in `CREATION_TEMPLATE_IDS`). That template is scripts plus
runtime-spawned block entities, not terrain; you cannot edit its voxels as typed
capabilities afterward, and per-block entities breach the 1000-collider runtime
cap at real scale. If the user asks for minecraft, offer procedural HEIGHTFIELD
terrain plus mobs, inventory, and day-night (all expressible) and name the voxel
limit honestly rather than attempting per-block authoring.

## Pitfalls

- Terrain is a heightfield: there is no per-voxel set/place op and no volumetric
  cave carve. `cutHole` is a surface disc only.
- `generate` noise is recipe-plus-seed, not raw parameters. Name a registered
  recipe; a generate without one, or with an unregistered name, is refused.
- `erosion` ignores its `mode` and its region: hydraulic and thermal both run
  the same smoothing pass over the whole terrain. Expect softened, settled
  terrain everywhere, not carved drainage; carve valleys with `sculpt` or a
  river `road` op instead.
- `scatter` is a grass-and-rocks tool today: only `grass`/`tall_grass` and
  `rock`/`boulder` render real geometry, and a scattered tree foliageKey
  renders as a placeholder cone. Place trees as model entities (mind the
  runtime entity cap) and reserve scatter for ground cover.
- Importing a greyscale heightmap image is not an AI patch op; `heightmapAssetId`
  is a facet field with no AI verb that sets it. Author the field with
  `generate`/`sculpt`/`erosion` instead.
- Artifacts are stale until rebuilt: after any patch batch, run
  `terrain_build_artifacts` before expecting the change to render or collide.
- Nav agents need `collision_proxy`/`nav_source_mesh` built to path over terrain.

## Verify

- `project_get_graph_snapshot` to confirm the terrain asset carries the ops and
  the entity carries the `TerrainComponent` binding.
- `qa_capture_renderer_viewport` to confirm the landscape, biomes, and foliage
  read with elevation.
