feat(biome): Milestone 1 — Biome Foundation + Terrain Shape Variation#14
Merged
Conversation
Add biome system foundation with 6 biomes (Grassland, Desert, Mountains, Valley, Swamp, Forest) each with unique noise parameters and height multipliers, driven by temperature+humidity noise maps with domain warping. Go WASM changes: - wasm/biome/: new package with BiomeDefinition, BiomeRegistry (6 biomes), domain-warped Whittaker biome selector, WorldConfig, and unit tests - wasm/main.go: goGenerateChunk now applies biome noise params (octaves, frequency, lacunarity, persistence) and height multiplier per chunk; appends biomeId at end of combined Float32Array; adds go_loadWorldConfig WASM export TypeScript changes: - src/engine/biome/BiomeTypes.ts: BiomeType const object + WorldConfig type - src/engine/biome/BiomeRegistry.ts: client-side biome metadata - WasmBridge.ts: updated global declarations for go_loadWorldConfig and updated go_generateChunk return comment - terrain.worker.ts: decodes biomeId from combined buffer (last element); handles loadWorldConfig CALL method - WasmClient.ts: generateChunk returns biomeId; adds loadWorldConfig() - ChunkManager.ts: ChunkGPUData stores biomeId; generateChunk propagates it Visual: Desert chunks use low-octave smooth dunes, Mountains use 7-octave jagged peaks at 3x height, Swamp nearly flat at 0.25x height. All Go tests pass. All 56 TS tests pass. Docker prod build verified (200 OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: applying a single biome HeightMultiplier to an entire chunk caused height mismatches at boundaries where adjacent chunks had different biomes. A Mountains chunk (3.0×) next to a Grassland chunk (1.0×) produced visible sky-coloured rectangular gaps where the mesh edges didn't align. Fix: replace chunk-center biome lookup with per-vertex world-position sampling. Both sides of a shared edge now compute the same world coordinate → same biome → same noise config → matching height → no seam. New wasm/biome/generator.go: - GenerateHeightmapPerVertex: samples biome at each vertex world position, applies that biome's noise params + HeightMultiplier per vertex - GenerateExtendedHeightmapPerVertex: same approach for the (res+2)² extended heightmap used in cross-boundary normal computation Updated wasm/main.go: - goGenerateChunk uses GenerateHeightmapPerVertex / GenerateExtendedHeightmapPerVertex instead of single-biome approach - dominant biome (by vertex count) still returned as biomeId New tests: - TestGenerateHeightmapPerVertex_SeamContinuity: verifies right edge of chunk(0,0) exactly matches left edge of chunk(1,0) (diff < 1e-4) - TestGenerateExtendedHeightmapPerVertex_Size: size correctness All Go tests pass. All 56 TS tests pass. Docker prod build verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previous approach: hard per-vertex biome selection meant vertices on opposite sides of a classification threshold (e.g. temp < 0.28 = Mountains vs Grassland) used completely different noise configs and HeightMultipliers, creating jagged wall/spine artifacts visible at every biome boundary. Fix: replace hard classification with Gaussian-weighted biome blending. How it works: 1. GetBiomeParams() returns continuous temperature + humidity noise values (extracted from GetBiomeAt so both functions share the sampling logic) 2. gaussianBiomeWeights() computes a normalised [6]float64 blend weight for each biome using a Gaussian distance from each biome's climate center in temp×humidity space (sigma ≈ 0.18-0.24) 3. blendedHeight() samples terrain height for every biome that has weight > 0.5% and returns the weighted average 4. GenerateHeightmapPerVertex / GenerateExtendedHeightmapPerVertex use blendedHeight() at every vertex Effect: at a Mountains/Grassland boundary the vertex gets ~57% Mountain height + ~43% Grassland height instead of a hard jump. The transition spans ~1500 world units (≈3 chunks) creating smooth, organic biome transitions. Chunk boundary seams remain gap-free because identical world coords produce identical blended results on both sides. Tests added: - TestGenerateHeightmapPerVertex_Smoothness: verifies no adjacent-vertex diff > 0.80 - TestGaussianBiomeWeights_SumToOne: sanity-checks classification at boundary points All Go tests pass. All 56 TS tests pass. Docker prod build verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Milestone 1: Biome Configuration Foundation + Terrain Shape Variation
Overview
Adds the core biome system to the terrain generation engine. Each region of the world now has a distinct biome (Grassland, Desert, Mountains, Valley, Swamp, or Forest), each generating visually different terrain shapes.
How Biome Selection Works
Two low-frequency noise maps (temperature + humidity) are sampled at each chunk center with domain warping to create organic, curved biome boundaries (no grid lines). The temperature × humidity pair maps to a biome via a simplified Whittaker biome classification chart, which naturally enforces valid adjacency — desert and swamp are never neighbors.
Biome Terrain Parameters
Files Changed
Go WASM (new package
wasm/biome/):biome.go— BiomeType, BiomeDefinition structsregistry.go— 6 biome configs + ScaleHeightmap helperselector.go— Domain-warped temperature/humidity noise → Whittaker classificationworld_config.go— WorldConfig (seed, biomeScale)biome_test.go— 9 tests covering determinism, Whittaker rules, registry completenessGo WASM (modified
wasm/main.go):goGenerateChunk: applies biome noise params, scales heightmap and extended heightmap, returns biomeId appended to combined buffergoLoadWorldConfig: new WASM export for loading WorldConfig JSONTypeScript:
src/engine/biome/BiomeTypes.ts— BiomeType const object, WorldConfig typesrc/engine/biome/BiomeRegistry.ts— client-side biome metadataWasmBridge.ts— updated global declarationsterrain.worker.ts— decodes biomeId from combined buffer, handles loadWorldConfigWasmClient.ts— generateChunk returns biomeId; adds loadWorldConfig()ChunkManager.ts— ChunkGPUData stores biomeIdTest Results
go test ./...)npm test)Visual Result
Walking in the world now encounters dramatically different terrain shapes: flat swamp regions, towering jagged mountains (3× standard height), smooth desert dunes, and moderate rolling forest terrain.