# Park zones 3D techniques

The Park (`environments/park`) is Al-Amr's first-party social-nature
Environment: a Garden Heart ringed by eight local gateways, each leading to a
gateway Zone. Three zones have shipped. Coral Bloom (`coral-bloom`) is a
golden-hour grove of coral blossom trees gathered around a still pond, with
petal drifts on the meadow and petals riding the breeze. Cloud Terrace
(`cloud-terrace`) is a two-level paradise forest: an upper plateau lifted 14 m
above a lower valley, a wide meandering river that pours over the level break
as a waterfall, and a plunge pool below it. Moon Grove (`moon-grove`) is a
moonlit birch grove under a clear indigo night — silver bark, a fog-veiled
outer band of dead trees, and an open glade at its heart. It is also the
project's first **model-driven** zone: its flora geometry arrives from CC0
Quaternius `.glb` packs through the shared `useGltfPack` loader instead of
procedural builders.

All zones reuse one shared scene stack — the sky, the post-processing owner,
the boot choreography, and the pure-model discipline — and differentiate
themselves entirely through data: a per-zone `ParkZoneDefinition` in
`environments/park/src/zones/registry.ts`. This document walks through every
technique that makes them work, citing the source modules so each claim can be
checked against code. The rendering profile they build on is described in
[the rendering runtime](/docs/architecture/rendering-runtime) and
[ADR-0048](../decisions/ADR-0048-three-r185-webgpu-first-rendering.md).

## Rendering architecture and canvas ownership

The graphics boundary is fixed and layered
(`EnvironmentRenderingGate -> AlAmrProvider -> AlAmrRuntimeGate ->
EnvironmentCanvas`), with exactly one canvas owning the renderer. Spawn, pose,
route, and `activeZoneId` state all live outside the canvas in
`ParkExperience.tsx`, so a renderer recovery (device loss, WebGPU to WebGL2
fallback) remounts the scene without resetting the visit.

The renderer is three's `WebGPURenderer` from `three/webgpu`, which prefers
WebGPU and falls back to its real internal WebGL2 backend on the same
renderer. Every custom material in both zones is a TSL `NodeMaterial` graph —
no handwritten GLSL anywhere — so the same graphs compile to WGSL or GLSL
depending on the backend. React Three Fiber composes the scene; quality is
resolved through `useRenderingQuality()` from `@al-amr/r3f`.

Zone scenes are registry-driven and code-split. Each `PARK_ZONES` entry
declares a `loadScene()` dynamic import, an art profile (`resolveSky` plus
optional `post` overrides), a navigation model (`sampleGroundHeight`,
`walkableRadiusM`, `flightCeilingM`, `resolveObstacles`), spawn and return
portal placements, render budgets, and owned semantic locations.
`zone-scene-loader.ts` wraps each zone's scene in `React.lazy` exactly once
per zone id — a stable lazy identity, because recreating the wrapper would
remount the scene and drop boot progress — and evicts the cached wrapper only
on the retry path, since `React.lazy` memoizes a rejected chunk import
forever. Travel is hub-and-spoke: gateway zones link back to `garden-heart`
only, through their `returnPortal`.

One deliberate structural rule: `ParkPostProcessing` is the single render
owner. It mounts once inside the canvas at the `ParkExperience` level, never
inside a zone scene, so a zone swap can replace the whole composition without
remounting the pipeline (pinned by an architecture test).

## Three quality tiers

Every zone scales real cost across the `enhanced` / `standard` / `compatible`
tiers — never just resolution. The `compatible` tier removes fragment work,
instance work, and passes outright:

| Aspect                              | enhanced                    | standard                    | compatible                       |
| ----------------------------------- | --------------------------- | --------------------------- | -------------------------------- |
| Sky cloud FBM octaves               | 6                           | 5                           | 3, no billow detail layer        |
| Post lane                           | MRT pipeline + bloom + FXAA | MRT pipeline + bloom + FXAA | direct render, no MRT/bloom/FXAA |
| Shadow map                          | 2048                        | 1024                        | 1024, no `castShadow` on flora   |
| Coral Bloom trees / grass / flowers | 760 / 22,000 / 20,000       | 520 / 15,000 / 14,000       | 210 / 6,500 / 6,000              |
| Cloud Terrace trees / grass         | 900 / 26,000                | 620 / 18,000                | 240 / 8,000                      |
| Moon Grove birches / grass          | 170 / 3,000                 | 110 / 1,800                 | 36 / 520                         |
| Terrain noise                       | `mx_noise_float` patches    | `mx_noise_float` patches    | pure sine bands                  |
| Ambient motion scale                | 1                           | 1                           | 0.35                             |

Draw-call counts never change between tiers; only instance totals and graph
cost do. Ambient clocks derive their speed from
`resolveParkMotionScale(quality, reducedMotion)` in
`environments/park/src/park-model.ts` — reduced motion freezes every clock to
0, `compatible` runs them at 0.35 — and `useReducedMotionPreference()` in
`environments/park/src/scenes/motion-preference.ts` is the one reactive reader
of the media query, so every clock agrees and stops together.

## Sky: an in-scene dome, not post-processing

The sky is geometry, not a screen effect. `ParkSkyDome`
(`environments/park/src/scenes/sky/ParkSky.tsx`) is a `BackSide`
`SphereGeometry(500, 48, 24)` mesh with a `MeshBasicNodeMaterial` whose entire
look is computed from `normalize(positionLocal)` — a direction field, so the
radius is cosmetic. Its fragment graph layers:

- a direction-based gradient (zenith easing to horizon, the horizon warmed on
  the sun side by `pow(saturate(cosSun), 3)`),
- FBM clouds projected onto an overhead plane (`direction.xz /
max(direction.y, 0.06)`), drifting on the built-in TSL `time`,
- an HDR sun disc and tight golden halo routed through `emissiveNode`, so the
  selective-bloom pass catches them while the wide glow stays beauty-only.

Three tricks carry the dome. First, the vertex node pins every fragment to the
far plane, making the dome independent of the camera's far distance:

```ts
material.vertexNode = Fn(() => {
  const clip = modelViewProjection as unknown as ClipSpaceNode;
  clip.z.assign(clip.w); // pin the dome to the far plane
  return clip;
})() as unknown as NodeMaterial["vertexNode"];
```

Second, the dome follows the camera each frame (`mesh.position.copy(
camera.position)` in `useFrame`), renders at `renderOrder = -1000` with
`depthWrite: false`, and has `frustumCulled = false`, so it always sits behind
every world mesh. Third, the cloud octave loop unrolls in plain JavaScript at
graph-build time — the octave count is a JS value, so `compatible` builds a
genuinely cheaper fragment graph (fewer `mx_noise_float` calls, no billow
layer) instead of merely lowering DPR. Because the FBM range grows with the
octave count, the accumulation is divided by `parkCloudAmplitudeSum(octaves)`
and re-centered on 0.5, so the coverage `smoothstep` cuts the same normalized
field on every tier: cloud coverage reads identically on all qualities.

Zones never rebuild this stack. Each zone's `*-sky-model.ts`
(`resolveCoralBloomSky`, `resolveCloudTerraceSky`, `resolveMoonGroveSky`)
returns the same
`ParkSkySpec` shape with its own palette, fog, sun visuals, and cloud budget —
Coral Bloom a fresh saturated blue that makes the coral canopy pop, Cloud
Terrace a luminous high-key heaven (`skyBrightness` 1.55, 62% cloud coverage,
near-white haze), Moon Grove a clear indigo night (`skyBrightness` 0.62, 16%
cloud coverage) whose HDR disc is re-graded from sun to silver moon. The sun
direction itself is shared
(`PARK_SUN_DIRECTION`, a warm late-afternoon sun at ~25 degrees elevation), so
the worlds stay consistent across the gateway veil — in Moon Grove the same
vector is the moon, and the sky disc, the shadow-casting light, and every
glint still agree. Linear scene `Fog` uses
exactly the palette's `hazeColor` — the fog IS the dome's below-horizon haze —
so the terrain rim never seams with the sky. Moon Grove leans on this hardest:
its fog (90–280 m) is a load-bearing part of the art, veiling the outer band
and letting the compatible tier shrink the tree population without ever
showing the world's edge.

## Lighting and shadows

One shadow-casting `directionalLight` (aligned with `PARK_SUN_DIRECTION`) plus
one `hemisphereLight` fill light the whole zone; the palette, intensities, and
shadow frustum all come from the zone's `ParkSkySpec`. The orthographic shadow
extent is authored per zone (`CORAL_BLOOM_SHADOW_EXTENT_M` 92,
`CLOUD_TERRACE_SHADOW_EXTENT_M` 104, `MOON_GROVE_SHADOW_EXTENT_M` 88) with a
compile-time guardrail constant
asserting the frustum always covers the zone disc. Shadow-map size is the only
tier knob (2048 enhanced, 1024 otherwise), and flora meshes only `castShadow`
off the `compatible` tier — `resolveParkRenderPolicy` drops scene shadows
there instead of lowering shadow resolution.

## Post-processing

`ParkPostProcessing`
(`environments/park/src/scenes/post/ParkPostProcessing.tsx`) owns the frame.
Its `useFrame` subscription at priority 1 disables R3F auto-render, so this
component renders every frame on every lane. On `standard` and `enhanced` it
runs a three `RenderPipeline`: the scene renders once into an MRT target with
`output` and `emissive` attachments, selective bloom is computed only from the
emissive attachment and added back over the beauty pass, then an explicit ACES
filmic + sRGB `renderOutput` tone-maps the composite, and FXAA runs last — the
pipeline's built-in output transform is disabled because FXAA needs
display-referred input:

```ts
const scenePass = pass(scene, camera);
scenePass.setMRT(mrt({ output, emissive }));
const bloomNode = bloom(scenePass.getTextureNode("emissive"), strength, radius, threshold);
pipeline.outputColorTransform = false; // FXAA needs display-referred input
pipeline.outputNode = fxaa(
  renderOutput(
    scenePass.getTextureNode("output").add(bloomNode),
    ACESFilmicToneMapping,
    SRGBColorSpace,
  ),
);
```

The `compatible` lane skips the entire pipeline: a direct
`gl.render(scene, camera)` with per-material ACES tone mapping — real cost
removed, not a cheaper variant of the same chain. Tone mapping is applied
exactly once per lane (`NoToneMapping` on the renderer while the pipeline's
explicit output transform owns it), and the pipeline rebuild key is quantized
to real drawing-buffer pixels so sub-pixel CSS resizes never churn render
targets.

Zone art direction arrives as props and is applied by mutating uniforms, never
by rebuilding the graph: `bloomNode.strength.value` and friends update each
frame, and exposure writes to `renderer.toneMappingExposure`, a live
`rendererReference` uniform both lanes read per frame. Coral Bloom lifts bloom
strength to 0.62; Cloud Terrace runs bloom 0.7 with exposure 1.1 over the
authored default of 1.04; Moon Grove stays milder at 0.55 so only the moonlit
edges, the flowers, and the return veil glow — never the whole night scene. This is how the luminous feel is achieved — the HDR
sun disc, water sun-glints, and waterfall sparkle all feed the emissive MRT
attachment, and bloom catches exactly those.

## Terrain

Each zone's ground is one displaced polar disc
(`createCoralTerrainGeometry`, `createCloudTerraceTerrainGeometry`), sampled
from the zone's own pure height function and tessellated per tier (Coral
Bloom 200x40 radial/ring segments on enhanced down to 88x18; Cloud Terrace
224x46 down to 96x20). The material is a layered meadow TSL graph with no
textures: deep/meadow/sunlit greens mixed by two scales of patch noise
(`mx_noise_float` on detailed tiers, swapped for pure sine bands on
`compatible` — real fragment cost removed), a blade-grain sine product for
close-up detail, sun-kissed crests by height, and a moist darkening ring near
the water, with `roughnessNode` modulated by the same grain.

Coral Bloom adds a petal-blush band around the grove heart — blossom fall
staining the grass between the pond basin and the mid-field. Cloud Terrace
adds two signature treatments: a moist riverbank band computed from a TSL twin
of the meandering centerline (`sin(z*0.016+0.35)*11 + sin(z*0.037-0.8)*5`,
coefficients copied from `cloudTerraceRiverCenterX` and marked must-stay-in-sync),
and a rocky grey-tan cliff tone where `normalLocal.y` reports a steep slope,
gated to the level-break band and cliff-face heights so the plateau meadows
never turn to stone. A third treatment animates caustic light over the
submerged riverbed and the plunge-pool floor: interference of two directional
sine sets plus a crossing weave, sharpened by a `pow` and masked by the river
distance, the plunge-pool basin, and a depth window against a TSL twin of
`cloudTerraceRiverWaterY`, so the pattern needs water overhead and fades in
the pool's deeper heart. Like the curtain's white water it is sine-only (no
`mx_noise`), so it stays on every tier and simply slows with the park motion
scale.

## Water

Both zones run portable TSL water on every backend — no real-time reflector
lane — which protects the draw-call budget and keeps WebGL2 parity exact. All
water animation lives inside the node graphs; `useFrame` only advances one
shared time uniform per material, scaled by the park motion rule.

The Coral Bloom pond (`CoralBloomPond.tsx`) is a single disc at
`CORAL_BLOOM_POND_WATER_Y`. Three layered directional sine waves build an
analytic normal field (cosine slopes, no finite differences) plus a gentle
physical swell in `positionNode` that sums to 0.041 m — well inside the basin
freeboard. Depth-graded body color warms to sunset shallows, a pow-5 fresnel
lifts the warm sky tint, an HDR sun glint (`pow(sunAmount, 340)`, boosted by a
twinkle term) and a caustic dapple feed `emissiveNode` for the bloom pass, and
a lapping foam lace breathes against the basin rim.

The Cloud Terrace river (`CloudTerraceRiver.tsx`) is four meshes on one clock:
two flat ribbons following the meandering centerline (upper terrace and lower
valley), a vertical waterfall curtain at the level break, and the plunge pool.
The ribbon builder authors `uv.v` in flow-metres, so both stretches scroll at
identical world speed from one graph:

```ts
// uv.v is authored in flow-metres by the ribbon builder, so both stretches scroll at one world speed
const p = vec2(uv().x.mul(riverWidth), uv().y);
const phaseA = dot(p, dirA).mul(0.55).sub(time.mul(1.7)); // dirA = (0, 1): straight downstream
```

The waterfall curtain warps its coordinate with a quadratic acceleration
(`v + v*v*0.9`) so stretch bands compress toward the plunge pool — falling
water accelerates — adds rivulet streaks, a two-band sine-product white-water
noise (deliberately no `mx_noise`, so the curtain costs the same on every
tier), a bright landing burst, and HDR sparkle in `emissiveNode`; opacity
feathers both banks and the lip. The plunge pool reuses the pond recipe and
adds an expanding foam ring radiating from the curtain's authored impact
point, drifting foam patches, and a mist-bright emissive sparkle inside the
ring.

Two small instanced systems ride the water from `CloudTerraceRiverEffects.tsx`
(one draw each on every tier — quality only shrinks instance counts). Drift
flecks — foam flecks and tiny fallen leaves — ride the upper river on the
surface: the vertex graph advances each fleck along a flow loop over the TSL
twin of the centerline, seats it on the twin of the upper water level plus
the river's own three-wave ripple, and spins the flat quad; the fragment cuts
the fleck ellipse and the loop envelope, so flecks fade in upstream and pour
over the falls lip before wrapping back. Waterfall impact mist is a handful
of soft camera-facing sprites at the plunge-pool impact point — derived from
the model's river math (`CLOUD_TERRACE_WATERFALL_IMPACT_*`), never hardcoded —
each billboarded quad rising, drifting outward, expanding, and fading on a
per-instance loop phase. Both follow the floating-petals idiom: identity
instance matrices, seeded per-instance attributes laid out on their own
xorshift32 sub-streams, all motion in the vertex graph from one
motion-scaled clock, `MeshBasicNodeMaterial` with normal blending and depth
writes off, and an explicit bounding sphere over the swept volume (the
gateway-motes idiom).

## Model-driven flora (Moon Grove)

Moon Grove is the project's first zone whose geometry comes from `.glb` files
instead of procedural builders. Five CC0 Quaternius packs
(`environments/park/public/models/PACK-{BirchTree,DeadTree,Bush,Flower,Grass}.glb`,
1.54 MB total) load through the shared `useGltfPack` hook from `@al-amr/r3f`
(ADR-0062) — vendored Basis/Draco decoders served from the Environment's own
origin (`public/al-amr/decoders/`, synced by a `predev`/`prebuild` hook),
bounded 20 s loads, per-renderer reference-counted caching, and
`AssetLoadError` failures that surface as application errors through the
zone's rendering error boundary. The packs carry zero animations and zero
skins, which is what makes plain instancing sufficient.

`MoonGroveFlora.tsx` turns each used variant (a named node such as
`BirchTree_3`) into one `InstancedMesh` per pack primitive — the birch's
bark+leaves pair is two draws, everything else one — sharing the pack's own
geometries, materials, and KTX2 textures without cloning; disposal releases
only the instance buffers because the pack cache owns the rest. The whole
system is 28 draws (birch 10, dead 5, bush 5, flower 5, grass 3) at every
tier; quality only shrinks the seeded population. Bounds are recomputed from
the written instance matrices (`computeBoundingSphere`) so frustum culling
stays correct, and a whisper of per-instance brightness variation through
`instanceColor` keeps the stand from reading stamped. The grove is still by
art direction — a calm night — so the module has no `useFrame` at all.

## Flora and wind

Flora is fully instanced: one draw per category, whatever the tier. Cloud
Terrace plants three tree species — a round broadleaf majority, a slim poplar
column, and a wide layered willow, each with its own instanced trunk and
canopy meshes and sway anchors, and grove members inheriting the grove's
species — plus grass and boulders (8 calls); Coral Bloom
adds petal drifts, floating petals, and meadow flowers. Per-instance tint,
scale, and stretch variation arrive through baked instanced attributes and
`InstancedMesh.instanceColor` (which `NodeMaterial` multiplies in after
`colorNode`, so the graph never touches a storage-backed attribute). The
hard-won rule, pinned by tests: every instanced attribute a TSL shader reads
is baked at full instance totals up front — an unbaked instanced attribute
renders silently black. The surface stack itself — the gradient/grain
material graph with its optional mottle/patch/rim/sunlit/moss terms, the
wind-swayed distance-strided position node, the dithered near-camera
dissolve, the throttled stride-camera clock, and the bounds-inflation
finalize — is shared by both zones from
`scenes/vegetation/park-flora-surface.tsx`, parameterized by each zone's
palette, stride bands, and sway constants.

Wind sway is TSL vertex displacement. A traveling gust field over
instance-transformed `positionLocal` plus a per-instance flutter phase
(`hash(strideId + phaseSalt)`) is weighted between the species' `swayBaseYM`
and `swayTopYM` anchors in geometry space (`positionGeometry.y`), normalized
so amplitude scales per instance; trunk and canopy share one `strideId` stream
so a plant sways and thins as one. Because the GPU displaces vertices the CPU
never sees, instanced bounds are recomputed from the static matrices and then
inflated by `*_MAX_WIND_DISPLACEMENT_M` (0.85/0.9 m), so frustum culling stays
correct while canopies sway.

Distance-based density stride culls far instances inside the shader, with the
camera uniform updates throttled (`STRIDE_CAMERA_MIN_INTERVAL_S` 0.25 s) and
hysteresis-gated (`STRIDE_CAMERA_HYSTERESIS_M` 5 m). Culled instances collapse
to a point far below the world rather than branching out:

```ts
const keepHalf = step(mod(strideId, float(2)), float(0.5));
const keepQuarter = step(mod(strideId, float(4)), float(0.5));
const visible = inNear.add(inMid.sub(inNear).mul(keepHalf)).add(inFar.sub(inMid).mul(keepQuarter));
material.positionNode = mix(vec3(0, -500, 0), displaced, visible);
```

A dithered near-camera dissolve (screen-door discard, no transparency, so
sorting and blending stay untouched) keeps the first-person lens from filling
with a black frame when the visitor pushes through a trunk or low canopy.
Bark and rock surface detail comes from DOM-guarded procedural canvas
textures: a deterministic pure-trig height field whose Sobel central
differences derive the matching normal map, generated once per resource
lifetime; headless test and SSR lanes get `null` and the material falls back
to its procedural grain. Canopy geometries are built by welding polyhedra
(`mergeVertices`), displacing shared vertices by a seeded hash, and
recomputing smooth normals — watertight input stays watertight, so the
silhouettes turn organic without cracks; geometry tests pin each canopy's
bounding box against the species' sway anchors.

## Boot and loading

Mounting every scene module in one commit would compile all NodeMaterials
inside a single synchronous task — the visible boot freeze. Each zone scene
instead mounts one boot stage per rendered frame via `useAfterRenderFrame`:
sky + terrain first, then water + return portal, then flora (the heaviest, so
its shader compiles stay inside their own frame). Boot progress is reported to
`useEnvironmentBoot` per painted stage, and `onReady` fires only after the
full composition has actually painted 3 frames — the ready signal means real
pixels, not mounted components. Refs keep the loop allocation-free and
idempotent across StrictMode replays. The render owner is deliberately not a
boot stage: it lives at canvas level and paints stage one's sky and terrain
into its first frame.

Moon Grove adds one wrinkle: its flora stage loads five glTF packs, so it
mounts inside its own `Suspense` boundary — the moonlit sky and terrain keep
painting while the packs arrive — and the zone's readiness is gated on the
instanced grove having actually mounted, so the ready signal never precedes
the models.

## Collision and movement grounding

The same height function that displaces the terrain mesh also grounds the
movement rig (`navigation.sampleGroundHeight`) and seats every placement —
one source of truth, so feet, flora, and mesh never disagree. Static collision
is a list of disc obstacles resolved per quality tier by
`resolveObstacles(quality)`: trunk and boulder discs rebuilt from the same
layout stream the active tier renders. This matters because layout tiers are
deliberately not prefix-nested (grove targets scale with the requested count),
so only a tier-matched obstacle list keeps collision authoritative — every
visible obstacle blocks, no invisible disc ever does, and collision never
follows decorative LOD. Pebbles below knee height stay walkable-over; the pond
and river need no discs because their banks are walkable-shallow by design.

## Deterministic layout and seeds

Every zone `*-model.ts` is GPU-free pure math, unit-tested without a renderer.
Terrain height is trig plus `smoothstep` masks: rolling hills faded in by a
radius mask, a flattened spawn clearing, a rim rise closing the vista, and the
water carves. Cloud Terrace adds the two-level world — an upper plateau
lifted 14 m across a 5 m smoothstep blend band, a river channel carved along
the meandering sine centerline, and the signature trick: the water surface
drops discontinuously at the falls while the channel floor blends continuously
across the break (a steep cascade bed under the curtain), so the heightfield
never tears at the cliff. The plunge pool digs its basin with the same masked
`min` blend, and a low bank lip rings both water bodies so the waterline reads
as a real bank.

Randomness is a seeded `xorshift32` with independent sub-streams per flora
category (`seed ^ 0x7eee5` for trees, `seed ^ 0x6a55` for grass, and so on;
Cloud Terrace's river drift flecks and impact mist draw from their own
streams, `seed ^ 0xf1ec5` and `seed ^ 0x9157`), so changing one category's
count never reshuffles another. Scatter is a
spacing-grid acceptance loop: groves (a fraction of the population) plus
singles, with a coarse cell-hash (`hashCell` over 16 m cells, its own seed)
driving hue indices so neighboring trees, petal patches, and flower drifts
share leaf and petal hues and merge into colored drifts. Petal patches anchor
to seeded trees, so fallen petals read as blossom fall, never random
confetti. Layout exclusions keep the pond basin, river channel and banks,
waterfall corridor, portal pad, spawn clearing, and the south reveal trail
open — the first walk is an unobstructed vista reveal by construction.

## Disposal and memory

Every GPU resource — geometry, material, pipeline, texture — is created in a
`useMemo` and released through `useDisposableRenderResource` from
`@al-amr/r3f`, with idempotent `dispose()` implementations. Zone swaps and
tier changes remount scene modules, and the `revisitGrowth` budget ceilings
(1 geometry, 1 texture) exist precisely to catch a remounted scene leaking GPU
resources. The sky dome disposes its geometry and material on unmount; the
vista haze restores the previous `scene.fog`; the post owner restores the
renderer's previous `toneMapping` and `toneMappingExposure` on unmount.

## Budgets and verification

Each zone carries fresh render budgets in the shared `PARK_RENDER_BUDGETS` row
shape (`gardenWebGpuStandard`, `gardenWebGl2`, `gardenCompatible`,
`revisitGrowth`, plus an initial-JS gzip cap). The WebGL2 rows are measured on
the software-rendered Playwright e2e lane at the zone spawn vista plus ~15%
headroom — Coral Bloom 30 calls / 955,900 triangles measured (budget 38 /
1,100,000), Cloud Terrace 37 calls / 824,688 measured plus the estimated
drift-fleck and mist draws (budget 45 / 950,000) — and the WebGPU rows keep a
lane-to-lane margin awaiting real-GPU validation. Moon Grove's rows are
authored differently and labeled as such: the triangle ceilings are grounded
in the seeded layout itself (the packs' exact per-variant triangle sums per
tier — compatible 387,510, standard 1,217,730 — pinned by the model test
against the committed GLBs) plus headroom, while the call, geometry, and
texture rows are structural estimates awaiting their first measured run on
the e2e lanes.
Pure models are unit-tested for determinism and invariants; an architecture
test bans hardcoded world radii outside `world-constants.ts` (each zone's own
model module plays that role for its world); and e2e journeys — enter the
zone, wait for ready, capture budgets, return through the veil — run under
software rasterization so they hold on every machine.

## Lessons and tricks worth stealing

- Pin sky fragments to the far plane with `clip.z = clip.w`; follow the camera
  and render first with `depthWrite: false`.
- Route HDR accents through `emissiveNode` and bloom only the emissive MRT
  attachment — selective glow without thresholding the whole frame.
- Unroll noise octave loops in JS at graph-build time, and normalize FBM by
  the amplitude sum so coverage thresholds read identically on every tier.
- Mutate uniforms for art-direction changes; never rebuild the pipeline for a
  zone swap.
- Make the linear fog color exactly the dome's haze color so the world rim
  can never seam with the sky.
- One height function grounds the rig, displaces the mesh, and seats every
  placement — never three approximate copies.
- Keep a discontinuous water surface but a continuous channel floor, so the
  heightfield never tears at a waterfall.
- Anchor every water-adjacent effect (foam ring, impact mist, drift loop) to
  the zone's own river math — never hardcode world coordinates in a shader.
- Author ribbon `uv.v` in flow-metres so separate water stretches scroll at
  one world speed.
- Bake every instanced attribute a TSL shader reads at full instance totals —
  unbaked attributes render silently black.
- Inflate instanced bounds by the maximum wind displacement so frustum culling
  survives GPU vertex displacement.
- Collapse stride-culled instances to a point instead of branching in the
  shader, and dither-dissolve near the camera instead of blending.
- Resolve collision discs from the same tier-matched layout stream the
  renderer draws — collision must never follow decorative LOD.
- Instance a glTF pack variant per primitive and share the pack's geometry
  and material outright — clone nothing, dispose only the instance buffers,
  and recompute instanced bounds after the matrix writes.
- Mount one boot stage per rendered frame so shader compiles never freeze the
  boot frame; fire ready only after the full composition has painted.
