Terrain System
The terrain system generates procedural 3D terrain using multi-layer Perlin noise with support for flat zones under stations and buildings. The system uses worker-based computation for height and normal calculations to maximize performance.Location:
packages/shared/src/systems/shared/world/TerrainSystem.tsArchitecture
Worker-Based Computation
The terrain system offloads height and normal calculations to a Web Worker for optimal performance:- Zero noise calls on main thread for non-flat-zone tiles
- Parallel computation across multiple tiles
- Normals computed in worker with centered finite differences
- Main thread only handles flat zone blending
Height Parameter Synchronization
All terrain generation constants are centralized inTerrainHeightParams.ts to prevent parameter drift between main thread and worker:
TerrainSystemimports params as TypeScript constantsTerrainWorkerreceives params viabuildGetBaseHeightAtJS()injection- Single source of truth prevents parameter drift
- Worker and main thread produce identical heights
- All numeric constants baked into worker code at runtime
Worker Height Computation
The worker computes fully correct heights including shoreline adjustments:Normal Computation in Worker
Normals are computed in the worker using centered finite differences on an overflow grid:- Accurate normals at tile boundaries (no edge artifacts)
- Centered differences produce smoother lighting
- Worker handles all computation (zero main thread cost)
World Specs
Noise Layers
Terrain height is generated from multiple Perlin noise layers defined inTerrainHeightParams.ts:
Combined height is normalized to [0, 1], raised to power curve (1.1), then scaled by
MAX_HEIGHT (50m).
Coastline Variation:
Natural irregular shorelines created by sampling noise on a circle around the island:
- Large-scale: 3 octaves, weight 0.2
- Medium-scale: 3x frequency, 2 octaves, weight 0.08
- Small-scale: 8x frequency, weight 0.02
- Varies island radius by ±20% for organic coastlines
Flat Zones
Flat zones create level terrain under stations and buildings with smooth blending to procedural terrain.How It Works
Height Calculation Priority:- Flat zones checked before procedural terrain
- Core Flat Area: Inside the zone, terrain returns exact height value
- Blend Area: Within
blendRadiusof zone edge, smoothstep interpolation blends to procedural terrain - Spatial Indexing: Terrain tiles (100m) used for O(1) lookup
- Manifest-Driven: Stations with
flattenGround: trueautomatically create flat zones
Flat Zone Registration
When a station spawns,TerrainSystem registers a flat zone:
Dimensions calculated from:
- Station footprint (from model bounds)
flattenPadding(extra space around footprint)flattenBlendRadius(smooth transition zone)
- Procedural terrain at station center
- Ensures flat zone matches surrounding terrain elevation
Height Calculation
When terrain height is requested, flat zones are checked first:TerrainSystem API
Duel Arena Floor Fix (commits b8f56e81, 7a60135e, 51453da)
Players and agents were sinking ~0.4m into duel arena floors because flat zones were not being registered with the terrain system. This causedgetHeightAt() to return raw procedural terrain height instead of floor-level height, and also allowed grass to grow through floor surfaces.
Problem:
- Flat zones were not registered for duel arena floors
getHeightAt()returned procedural terrain height (~0.4m below arena floors)- Players/agents spawned at procedural height, sinking into visual floor meshes
- Grass system used procedural heights, rendering grass through floors
- Terrain mesh rendered at procedural height, creating z-fighting with floor geometry
DuelArenaVisualsSystem for all 8 floor areas (6 arenas + lobby + hospital):
-
Procedural Height Sampling: Uses
getProceduralTerrainHeight()to get the raw terrain height at each arena center, then adds 0.4m offset for player standing height -
Flat Zone Parameters:
height: Procedural terrain height + 0.4m (where players stand)blendRadius: 1.0m smooth transition to surrounding terraincarveInset: 1.0m inset from zone edges to preserve blend padding
-
Registration Timing: Flat zones are registered in
DuelArenaVisualsSystem.start()after terrain system is initialized but before arena meshes are created -
Cleanup: Flat zones are unregistered in
DuelArenaVisualsSystem.destroy()to prevent memory leaks
- 6 duel arenas (20m × 24m each)
- Lobby floor (40m × 25m)
- Hospital floor (30m × 25m)
- Terrain height queries now return correct floor-level values (procedural + 0.4m)
- Players/agents spawn at proper height (no sinking)
- Grass system respects flat zones (no grass through floors)
- Terrain mesh is carved under floor areas to prevent overdraw
- Visual floor meshes positioned 2cm above terrain mesh to prevent z-fighting
- Terrain tiles are automatically regenerated when flat zones are registered after initial tile generation
Console Logging
TerrainSystem logs flat zone activity:
Performance Characteristics
Worker-Based Computation:- Height calculation: ~0ms on main thread (worker handles all noise)
- Normal calculation: ~0ms on main thread (worker computes with overflow grid)
- Tile generation: ~5-10ms total (worker + transfer + geometry build)
- Flat zone tiles: ~15-20ms (requires main thread recomputation)
- Flat zone lookup: O(1) via tile-based spatial index
- Typical world: 5-10 flat zones, 10-20 tile keys
- Negligible memory overhead (~1KB per zone)
Related Systems
- Movement System - Uses terrain heights for collision
- Collision System - Integrates with flat zones
- World Generation - Terrain is part of world initialization