AI agents author terrain as a JSON DAG of 82 node types — noise, shape, coordinate, pattern, sampling, math, filter, compose, mask, derivative, erosion, output. The server evaluates the graph deterministically, writes a 16-bit heightmap and RGBA8 splat weight maps, and pushes the result through a Landscape's edit layer. One call from "describe the terrain" to "ALandscape actor renders in the viewport with auto-material."
The Cycle
From an empty world to a textured Landscape actor in five MCP calls.
terrain_list_nodesEnumerate every node type and built-in preset for discovery.terrain_preset_getFetch one of 17 starter graphs as fully-mutable JSON.terrain_previewLow-res topographic + hillshaded PNG returned inline as a content block.terrain_buildEvaluate a graph end-to-end and write G16 heightmap + RGBA8 splat assets.terrain_apply_to_landscapePush the result through a Landscape edit layer; auto-derives heights from the actor's footprint.terrain_create_auto_materialGenerate a Landscape Material that consumes the splat weight map and assign it to the actor.texture_inspectRead back any UTexture2D and assert per-channel statistics for verification.Design
The recipe is a single saveable, diff-able artifact. An agent emits the whole graph in one tool call, mutates the graph between iterations, and never has to "drag wires" or hold node-editor state across rounds.
A monolithic generator can only express what its switches expose. A graph composes primitives — route this mountain's slope mask into that erosion's strength, share flow accumulation across a fluvial pass and a splat layer, stack three biomes through a layer-stack macro.
Every node carries an explicit or stable per-id seed. Same graph + same seed = bit-identical output across runs and machines. Adding or removing unrelated nodes does not shuffle other nodes' seeds.
ParallelFor inside each node, refcounted intermediate buffers, deterministic per-node RNG. 1009² runs in seconds, 4097² in tens of seconds. No RHI compute, no shader compilation hiccups.
texture_inspect reads back the generated assets and reports per-channel min/max/mean/stddev/distinct count + a 16-bucket histogram. Tests assert on observable terrain statistics — the framework catches "tool returned ok but the texture is flat" on every run.
From AI prompt to a colored, eroded Landscape actor: terrain_apply_to_landscape evaluates at the landscape's vertex resolution, auto-derives proportional heights from the footprint, and writes through the edit layer. terrain_create_auto_material generates and assigns a splat-driven Landscape Material in the same flow.
Node Taxonomy
Each node has a typed input set and produces a single buffer. Connect them however the terrain requires.
noise.fbm · fractal Brownian motionnoise.ridged · sharp peak ridgesnoise.billow · soft fluffy bumpsnoise.worley · cellular (cells / distance / edge)noise.domain_warp · twist any inputshape.cone · radial cone falloffshape.dome · smoothstep domeshape.crater · bowl + raised rimshape.plateau · flat-topped massshape.ridge_line · linear ridge between two UV pointsshape.island_falloff · radial maskshape.gradient · directionalshape.constant · solid valueimport.heightmap · existing G16/RGBA16/G8/BGRA8 texture
transform.remap · range remaptransform.curve · 1D LUT, sorted point listtransform.terrace · stepped plateaustransform.power · gamma curvetransform.clamp · hard clamptransform.invert · 1 − xfilter.blur · separable Gaussianfilter.normalize · percentile clip + remap to [0,1]derive.false_color_split · preview ramp channels as masks
compose.add · weighted sumcompose.multiply · per-pixel productcompose.subtract · clamped differencecompose.lerp · scalar or buffer-driven blendcompose.max / min · per-pixel envelopecompose.overlay / screen · Photoshop blend modescompose.mask_blend · the workhorse: lerp(a, b, mask)compose.layer_stack · macro: TerraForge3D-style biome stack
mask.altitude · height band with falloffmask.slope · steepness in degreesmask.curvature · concave (valleys) vs convex (ridges)mask.flow_accumulation · D8 routing → river-bed maskmask.combine · and / or / min / max / xor
erode.hydraulic · particle-based runofferode.thermal · talus-angle slope stabilisationerode.fluvial · flow-accumulation channel carvingerode.coastal · smooths only inside a sea-level band
output.heightmap · G16 UTexture2Doutput.splat · RGBA8 weight map (up to 4 layers)output.landscape_apply · direct to Landscape edit layeroutput.preview · base64 PNG returned inline
From Noise to Place
These are absent from monolithic noise+erosion tools. They turn a heightmap into a geological heightmap.
mask.curvature
Discrete Laplacian: negative = convex ridge tops, positive = concave valley floors. Routes splat layers correctly so snow lands on peaks and grass settles in concavities — not painted by altitude alone.
mask.flow_accumulation
D8 steepest-descent routing computes the volume that drains through each cell. Channels saturate to 1, ridges stay at 0. Drives river-bed splat layers, sediment darkening, and lush vegetation placement.
erode.fluvial
Uses flow accumulation to carve channels proportional to pow(flow, flow_power) × carve_strength. The result is real river valleys following the topography, not surface scratches from particle simulation. This is what makes terrain look geologically continuous.
output.splat
Even a great heightmap looks grey. output.splat composes up to 4 mask buffers into an RGBA8 weight map your Landscape Material samples for grass / rock / snow / sand blending. Sum normalised to 1.0 per pixel.
Built-in Presets
Each preset returns a complete graph the agent can mutate node-by-node before applying — change parameters, swap node types, add layers.
Radial falloff × ridged FBM, hydraulic + thermal + coastal erosion. Splat sand / grass / rock / snow.
Diagonal ridge line × domain-warped ridged FBM, fluvial erosion produces real river networks. Splat grass / rock / snow.
Plateau base, aggressive fluvial carving along noise-warped flow seeded from the high ground. Splat river / sandstone / floor / grass.
Worley-cellular islands blended with island falloff per cell, sea-level coastal smoothing. Splat sand / grass / rock / deep.
Ridged FBM with heavy thermal + light fluvial; high snowline, exposed cliff faces. Splat grass / rock / snow.
Directional billow with shallow gradient bias, minimal erosion to preserve dune crests. Splat lit-sand / shaded-sand / rock / dust.
Two opposing plateaus with a deep fluvial valley between them. Splat water / grass / rock / dirt.
End-to-End
The complete sequence an agent runs to produce a 2 km mountain landscape with auto-derived heights and a splat-driven material — every step verified.
Five MCP calls. No editor clicking. The agent never leaves the JSON-RPC loop, and every step is independently verifiable via texture_inspect.
Verification
Every generated heightmap and splat is round-trip-inspectable via texture_inspect. The shipped test suite asserts these statistics on every preset, every run.
| Preset (513² heightmap) | Range | StdDev | Distinct uint16 | Histogram bins ≠ 0 |
|---|---|---|---|---|
| island | 0.85 | 0.115 | 24,397 | 14 / 16 |
| mountain_range | 0.37 | 0.076 | 22,166 | 7 / 16 |
| canyon | 0.36 | 0.072 | 22,281 | 7 / 16 |
| archipelago | 0.86 | 0.225 | 44,326 | 14 / 16 |
| alpine | 0.38 | 0.094 | 24,907 | 7 / 16 |
| desert_dunes | 0.31 | 0.054 | 17,484 | 6 / 16 |
| river_valley | 0.39 | 0.073 | 24,668 | 7 / 16 |
Real terrain has high distinct-value count, non-trivial stddev, a wide range, and populates many histogram buckets. A flat field would score zeros across the board. The harness shipped at overnight/terrain_tests.py covers the presets plus determinism, custom-graph, and four error paths.