Overview
Hyperscape uses a hierarchical quadtree LOD system for infinite terrain rendering with dynamic chunk splitting based on camera distance. The system provides:- 5 LOD levels (depth 0-4) from 1600m root chunks to 100m leaf chunks
- Uniform 32x32 vertex resolution across all LOD levels
- Skirt geometry (15m drop) to hide LOD seams
- Dynamic splitting/unsplitting based on player position
- Client-only visual system (server uses flat 100m tile grid)
Added in commits 82a5365 and 6c14c8e (March 12, 2026). This is a client-only visual system. Server and gameplay logic still use the flat 100m tile grid (
TerrainTile). getHeightAt() is unaffected.TerrainQuadTree
TheTerrainQuadTree class manages the hierarchical quad-tree of terrain chunks.
Configuration
LOD Levels
Split/Unsplit Logic
Split Condition:unsplitMultiplier (1.2) creates a 20% buffer zone to prevent rapid split/unsplit cycles at LOD boundaries.
Usage
Performance Optimizations
Numeric Grid Coordinates (Commit 6c14c8e):- Uses numeric grid coordinates instead of string keys
- Eliminates per-frame string allocation and GC pressure
- Compares
gridX === lastGridX && gridZ === lastGridZinstead of string comparison
- Set
truewhenever tree structure changes (split/unsplit) - Skips neighbor resolution when tree is stable
- Cleared after
updateAllNeighbours()completes
- Only generates terrain when all 4 neighbors are resolved
- Prevents seam artifacts from missing neighbor data
terrainNeedsUpdateflag tracks pending generation
TerrainQuadNode
Individual nodes in the quad-tree representing square terrain regions.Properties
Methods
Integration with TerrainVisualManager
TheTerrainVisualManager listens to quad-tree events and generates/destroys terrain geometry:
Skirt Geometry
Skirts hide LOD seams by extending terrain geometry downward at chunk edges.Implementation
Why Skirts Work
- Overlap: Skirts from adjacent chunks overlap underground
- Hidden: Skirts are below terrain surface, invisible to player
- Seamless: Prevents gaps between LOD levels from being visible
Performance Characteristics
Memory Usage
Per-Node Overhead:- Node object: ~200 bytes
- Children map: ~100 bytes
- Neighbours map: ~100 bytes
- Total: ~400 bytes per node
- 3×3 root chunks (9 nodes at depth 0)
- Average 2-3 subdivisions per root
- ~500-1000 total nodes
- Total memory: ~200-400 KB
CPU Usage
Per-Frame Operations:- Grid coordinate comparison (numeric, not string)
- Neighbor resolution (only when structure changes)
- Terrain generation requests (only for new final nodes)
- No structure change: ~0.1ms
- Structure change: ~1-2ms (neighbor resolution)
GPU Usage
Draw Calls:- One draw call per visible terrain chunk
- Typical: 20-40 chunks visible
- Total: 20-40 draw calls (vs 100+ without LOD)
Debugging
Debug Stats
Visualization
Enable terrain LOD visualization in dev tools:Best Practices
Match minSize to TILE_SIZE
Match minSize to TILE_SIZE
Set
minSize: 100 to match the server’s 100m tile grid. This ensures terrain chunks align with gameplay tiles.Use hysteresis for stability
Use hysteresis for stability
Set
unsplitMultiplier > 1.0 (recommended: 1.2) to prevent rapid split/unsplit cycles at LOD boundaries.Uniform resolution across LODs
Uniform resolution across LODs
Use the same vertex resolution (32x32) for all LOD levels. This simplifies shader code and ensures consistent visual quality.
Add skirts to hide seams
Add skirts to hide seams
Set
skirtDrop: 15 to extend terrain geometry downward at chunk edges. This hides gaps between LOD levels.Lazy generation for seamless terrain
Lazy generation for seamless terrain
Only generate terrain when all 4 neighbors are resolved. This prevents seam artifacts from missing neighbor data.
Related Systems
Biome System
Biome-specific terrain generation with per-biome tree configs
Tree Instancing
Multi-variant tree rendering with BatchedMesh
Terrain Shaders
TSL-based terrain materials with biome blending
World System
World management and tile streaming