Terrain Height Cache Fix (February 2026)
Commit: 21e0860993131928edf3cd6e90265b0d2ba1b2a7Author: Ting Chien Meng (@tcm390)
Summary
Fixed a consistent 50m offset in terrain height lookups caused by incorrect tile index calculation and grid coordinate mapping. The bug affected pathfinding, resource spawning, and player positioning.Symptoms
- Players floating ~50m above ground
- Resources (trees, rocks) spawning in mid-air
- Pathfinding failures (incorrect walkability checks)
- Incorrect terrain color lookups
Root Cause
getHeightAtCached() had two bugs:
Bug 1: Tile Index Calculation
[-50, +50], but Math.floor(worldX / TILE_SIZE) assumes origin at [0, 0].
Example:
- World position:
x = 25(should be tile 0) - Broken calculation:
Math.floor(25 / 100) = 0✅ (accidentally correct) - World position:
x = -25(should be tile 0) - Broken calculation:
Math.floor(-25 / 100) = -1❌ (wrong tile!)
Bug 2: Grid Index Calculation
[-50, +50], but grid indices are [0, 100]. The formula needs to add halfSize to shift the range.
Example:
- Local position:
x = 0(center of tile, should be grid index 50) - Broken calculation:
Math.floor(0) = 0❌ (wrong index!) - Correct calculation:
Math.floor(0 + 50) = 50✅
Fix
Canonical Helper Functions
worldToTerrainTileIndex() - Convert world coordinates to tile indices:Updated getHeightAtCached()
Updated getTerrainColorAt()
Also fixed comma-vs-underscore key typo:Impact
Before Fix
- Height lookups: ~50m offset (consistent error)
- Color lookups: Always returned null (key mismatch)
- Pathfinding: Incorrect walkability (wrong height data)
- Resource spawning: Mid-air placement
After Fix
- Height lookups: Accurate to terrain mesh
- Color lookups: Correct biome colors
- Pathfinding: Correct walkability checks
- Resource spawning: Ground-level placement
Migration
For Users
No migration needed - fix is automatic on update. If you see floating/sinking issues after update:- Clear browser cache
- Reload page
- Terrain cache will rebuild with correct calculations
For Developers
Use canonical helpers for all terrain coordinate conversions:Math.floor(worldX / TILE_SIZE)- doesn’t account for centered geometryMath.floor(localX)- doesn’t account for PlaneGeometry range
Testing
Test Cases
packages/shared/src/systems/shared/world/tests/TerrainSystem.test.ts:Visual Verification
Related Systems
TerrainSystem
File:packages/shared/src/systems/shared/world/TerrainSystem.ts
Uses Height Cache For:
getHeightAt()- Primary height query (falls back to procedural if cache miss)getTerrainColorAt()- Biome color lookup- Flat zone blending
- Grass exclusion
PathfindingSystem
File:packages/shared/src/systems/shared/movement/BFSPathfinder.ts
Impact: Walkability checks now use correct heights, preventing:
- Paths through “air” (where terrain was actually solid)
- Blocked paths (where terrain was actually walkable)
ResourceSystem
File:packages/shared/src/systems/shared/entities/ResourceSystem.ts
Impact: Resources now spawn at correct ground level:
- Trees no longer float
- Rocks sit on terrain surface
- Fishing spots at water level
Performance
Cache Performance
Before Fix:- Cache hit rate: ~95% (but wrong data)
- Fallback to procedural: ~5%
- Cache hit rate: ~95% (correct data)
- Fallback to procedural: ~5%
- Performance: Unchanged (same cache hit rate)
Coordinate Conversion Cost
worldToTerrainTileIndex(): ~5 arithmetic operations localToGridIndex(): ~3 arithmetic operations Negligible overhead compared to procedural height calculation (~1000 operations).Known Limitations
Cache Key Format
Cache keys use underscore separator:${tileX}_${tileZ}
Don’t use:
- Comma separator:
${tileX},${tileZ}(won’t find tiles) - Colon separator:
${tileX}:${tileZ}(won’t find tiles)
Grid Size Assumptions
Helpers assume:- PlaneGeometry is centered at origin
- Grid size is even (e.g., 100, 200)
- Tile size matches geometry size
References
- TerrainSystem.ts - Implementation
- PlaneGeometry - Three.js geometry
- Terrain Height Cache - Cache structure