Terrain and heightmaps procedural generation biomes and foliage
Use this for
Not for
Pairs with: Terrain basics ground heightmap and sculpting, Atmosphere sky lighting fog and time of day, NPC and enemy navigation chase and patrol behavior
The recipe
- Create a terrain resource asset:
project_create_asset(project.asset.create) withassetKind: "terrain". This backs the binding; the dedicatedterrain.resource.createaction isplannedwith no AI tool yet. - Shape the land with
project_apply_terrain_semantic_patch(terrain.patch.apply). Emit a batch of ops (up to 1024) drawn from the nine:createseeds base extent:width,depth,maxHeight, optionalseed, optionalbiome.generatereshapes the heights of a rectangle with a registered recipe:regionKindone ofheightfield_tile/mesh_chunk/procedural_recipe,min/max(Vec2),recipe, optionalseed. The schema listsrecipeas optional, but the write seam refuses a generate without one and refuses a recipe name outside the terrain vocabulary registry (TERRAIN_RECIPE_IDSinpackages/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(moderaise/lower/flatten/smooth atx,zwithradius,strength) for hand shaping.biomerepaints the texture layers of a rectangle with a registeredbiomeKeyfromTERRAIN_BIOME_IDS(an unregistered name is refused with the accepted ids);paint(layerIndex0..15) paints a texture layer.roadlays aroad/path/riverpolyline (points,width, riverdepth).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 onlyiterationsandstrength;mode(hydraulic/thermal), themin/maxregion andseedare accepted and not read, so it neither stays inside its rectangle nor carves water valleys.scatterpaints INSTANCED foliage (foliageKey,densityper 100 sq units,min/max, scale jitter) over its whole rectangle. The schema accepts alayerIndexgate, 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 afoliage_instance_bufferartifact the renderer instances, so thousands of grass blades cost one batch, not one entity each. Real geometry exists only for thegrass/tall_grassandrock/boulderkeys; any other foliageKey, tree and flower keys included, renders as a small placeholder cone today.cutHoleremoves a disc of surface (for a cave mouth or pit). It is a SURFACE hole, not a volumetric carve.
- 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). - Bind: attach
TerrainComponent{ "type":"TerrainComponent", "enabled": true, "terrainAssetId": <asset id> }. - 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.
cutHoleis a surface disc only. generatenoise is recipe-plus-seed, not raw parameters. Name a registered recipe; a generate without one, or with an unregistered name, is refused.erosionignores itsmodeand 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 withsculptor a riverroadop instead.scatteris a grass-and-rocks tool today: onlygrass/tall_grassandrock/boulderrender 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;
heightmapAssetIdis a facet field with no AI verb that sets it. Author the field withgenerate/sculpt/erosioninstead. - Artifacts are stale until rebuilt: after any patch batch, run
terrain_build_artifactsbefore expecting the change to render or collide. - Nav agents need
collision_proxy/nav_source_meshbuilt to path over terrain.
Verify
project_get_graph_snapshotto confirm the terrain asset carries the ops and the entity carries theTerrainComponentbinding.qa_capture_renderer_viewportto confirm the landscape, biomes, and foliage read with elevation.