---
title: "Scenery composition terrain-first workflow, sampling height, and visual verification"
description: "A run once authored a beautiful meadow, exactly as a landscape artist would, and the delivered screenshot showed floating placeholder cones over muddy ground with invisible foliage, while the model reported the scene \"verified\" from graph data. Every gap was a composition-and-verification gap, not an authoring-verb gap. This playbook is the craft that closes them: build in the right order, read th"
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/scenery-composition/
---

# Scenery composition terrain-first workflow, sampling, and verification

## Why this playbook exists

A run once authored a beautiful meadow, exactly as a landscape artist would, and
the delivered screenshot showed floating placeholder cones over muddy ground with
invisible foliage, while the model reported the scene "verified" from graph data.
Every gap was a composition-and-verification gap, not an authoring-verb gap. This
playbook is the craft that closes them: build in the right order, read the ground
before you place on it, compose with intent, and LOOK at the result.

terrain-and-heightmaps and terrain-basics teach the op grammar. This playbook is
about assembling those ops into a scene that reads.

## The terrain-first order (and the BUILD that makes it real)

Author the ground before the things that sit on it, in this order, so each pass
operates on the result of the last:

1. `create` the terrain field (width, depth, maxHeight, optional biome + seed).
2. `sculpt` the macro form: raise hills, lower a valley, then a `smooth` pass to
   knock off the terracing so slopes read natural.
3. `erosion` AFTER sculpting: the current pass is a deterministic smoothing
   (both `mode` values run the same thermal-style pass), settling slopes and
   softening peaks, which is what makes hand-sculpted ground stop looking
   hand-sculpted. It does NOT carve drainage valleys; carve those with `sculpt`
   or a river `road` op. Erode the macro form, then paint and scatter onto the
   eroded result.
4. `paint` the material layers: base ground, then a second pass where slope or
   elevation changes the surface (rock on steep faces, sand at the waterline). Two
   deliberate passes beat one flat wash; one layer everywhere reads as mud.
5. `scatter` instanced foliage over the painted, eroded ground.

Then BUILD ARTIFACTS. The op-log is durable but materializes NOTHING on its own:
the deterministic build turns the height edits into heightfield tiles and the
scatter into foliage instance buffers, and until it runs the ground does not
collide and the foliage does not render. `project_apply_terrain_semantic_patch`
(`terrain.patch.apply`) now TRIGGERS that build in the SAME call (cost follows
change) and receipts it as `artifacts built: N tiles, M foliage buffers`;
`terrain_build_artifacts` (`terrain.artifacts.build`) stays as the explicit
re-bake if you need to force one. If you author terrain and see zero tiles in the
receipt, the surface will render as nothing, so treat that line as your proof the
pass landed.

### Scatter density is instances per 100 square units, not a fraction

`scatter`'s `density` is UE/Unity-style: INSTANCES PER 100 SQUARE WORLD-UNITS,
max 10000. It is NOT a 0-to-1 coverage fraction. A density of 0.8 means fewer
than one blade per 100 sq units and reads as INVISIBLE, which is exactly how a
meadow ends up bare. Typical grass is 200-2000; dense ground cover up to ~5000.
Use density as a COMPOSITION tool: high density in the meadow foreground, thinning
toward the tree line, so the ground has a gradient instead of a uniform carpet.
The tool emits a sub-1 density advisory, but author the right number the first
time.

## Sample the ground before you place a prop on it

The floating-prop failure is placing a tree at a hand-guessed `y` over sculpted
ground: five trees, five different guesses, all wrong, all hovering or sunk. The
fix is to READ the surface first.

`world_sample_terrain` (`world.sample.terrain`) samples the real surface at up to
64 `(x, z)` points and returns, for each, the `y` a prop rests on, the surface
`normal`, the `slope` in degrees, and `covered` (false where no terrain reaches
that point). It reuses the EXACT height math the runtime draws and clamps to
(`resolveTerrainGroundHeight` + the `ctx.terrain.*` query host), so there is zero
drift between the height you read and the height the ground renders at.

```json
{
  "game_id": "<uuid>",
  "world_id": "<optional world key or id>",
  "points": [
    { "x": -40, "z": 12 },
    { "x": 0, "z": 0 },
    { "x": 55, "z": -30 }
  ]
}
```

The workflow: decide WHERE props go (in x/z, from your composition), sample those
points in one batched call, then set each prop's `TransformComponent.position.y`
to the returned `y` (drop the base exactly on the ground, or a hair below to seat
it). Read the extras it gives you:

- `slope` gates placement: skip a tree where slope is steep (it would grow out of
  a cliff); a rock is fine there. Steep-slope gating is composition, not just
  correctness.
- `normal` lets you tilt a prop to follow the ground instead of standing plumb on
  a hillside.
- `covered: false` means no terrain there; do not place a grounded prop at that
  point, or it will float over a hole or past the field edge.

Sample BEFORE placing, always after the terrain build, because you are reading the
materialized surface.

## Compose with intent, not uniform fill

A scene is a picture. Three moves separate a composed scene from scattered props:

- ONE focal element. A scene needs a subject the eye lands on: a lone old oak on
  a rise, a ruined arch, a boulder outcrop. Place it deliberately (often off the
  exact center, near a thirds line), scale it larger than the fill, and let
  everything else support it. A field of identical trees has no focal point and
  reads as texture, not place.
- DEPTH LAYERS. Compose foreground, midground, and background. Foreground props
  frame the view and give scale; the midground carries the focal element; the
  background (a tree line, distant hills, fog) closes the space. Sample terrain
  height for each layer's props so all three sit on real ground. Atmosphere
  (haze/fog thinning distant layers) sells the depth; see
  atmosphere-sky-and-lighting.
- DENSITY GRADIENTS. Vary density across the scene rather than blanketing it: a
  dense grass foreground thinning to sparse near a path, a cluster of rocks that
  scatters out into singles. Gradients read as natural distribution; uniform
  density reads as a fill tool. Drive this with the `scatter` `density` per region
  and with how tightly you cluster placed props.

For the props themselves: compose multi-part props (a tree, a rock cluster, a
bench) from primitives as reusable prefabs (see composed-props-from-primitives),
then instance them across the scene with per-instance scale/rotation jitter so
copies do not look stamped; for hundreds of one simple mesh use an instanceSet
(see asset-to-instances). Reserve generation for the hero focal element.

## Verify by LOOKING, not by reading the graph

The trap that produced the floating-cone meadow: the model called scene-graph
observers, saw the right entity COUNT and structure, and declared the scene
verified. A graph digest is blind to fidelity. It cannot see that a tree is a
placeholder cone, that a prop floats a meter off the ground, or that two paint
layers read as one muddy smear. Only a rendered frame shows those.

`world_observe_entity_view` (`world.observe.entity_view`), THE EYE, is the look
tool. For up to 8 entities it resolves each one's world-space bounds from current
head and computes the F-key "frame selected" camera (fit the bounding sphere,
3/4 view). Where the posed render lane is wired, the rendered PNG rides back as an
IMAGE content part so you actually SEE the framed entity; the receipt also carries
each entity's `bounds` and `camera` plus a compact scene digest for context.

```json
{ "game_id": "<uuid>", "entity_keys": ["entity.focal_oak", "entity.foreground_rock"], "width": 1024, "height": 768 }
```

Frame your FOCAL element and a representative prop from each depth layer and look
at them: is the tree a real canopy or a cone, does the base meet the ground, do
the materials read as distinct layers. Use `world_observe_scene`
(`world.observe.scene`) for the structural digest (entity counts, bounds), but
never let the graph digest stand in for the render check on a scenery brief.

### When the render is unavailable, the tool says so, and so must you

`world_observe_entity_view` is honest by construction: where no posed render lane
is wired in the running server, it does NOT fake a frame. `renderAvailable` is
false and `renderUnavailable` names the exact missing seam (`reason`, `code`,
`missingSeam`); each entity's `render.status` is `unavailable`. In that state you
still get bounds and the computed camera, but you have NOT seen the scene.

Do not report a scene as visually verified off bounds and counts alone. If the
render came back unavailable, say in your summary that the composition is
structurally in place but was not visually confirmed, and name what a render
check would still need to catch (floating props, placeholders, muddy paint). This
is the same disclosure norm the placeholder playbooks carry: an unseen scene is an
unverified scene, and the honest summary says which it is.

## Pitfalls

- Authoring terrain and never seeing a build receipt: the surface will not render
  or collide. Confirm `artifacts built: N tiles` is nonzero.
- Placing props before sampling, or sampling before the build: sample the
  MATERIALIZED surface, then place on the returned `y`.
- Reading `density` as a 0-1 fraction: sub-1 is invisible. Instances per 100 sq
  units; grass is hundreds to thousands.
- Uniform everything: same density, same prop, same scale, no focal point. That
  is fill, not composition.
- Declaring the scene verified from `world_observe_scene` graph data alone. Look
  with THE EYE, and if the render is unavailable, disclose that it was not seen.
- Scattering a tree or flower foliageKey and expecting a canopy: only
  `grass`/`tall_grass` and `rock`/`boulder` have real scatter geometry; other
  keys render as placeholder cones. Place trees as model entities and reserve
  scatter for ground cover.
