feat: SDF per-pixel ground blend + expanded biomes on a 2x world #221

Closed
panda wants to merge 1 commit from feat/world-new-biomes into main
Collaborator

Summary

The old dual-grid tile transitions between biomes looked bad — checkerboarding, dark seams and stair-stepped borders where two biomes met. I wanted the biomes to melt into each other instead of showing tile edges.

This PR replaces the discrete dual-grid ground with a per-pixel Signed-Distance-Field blend running in a shader, expands the biome set from the cleaned E:\Asset art, and doubles the world to 768×576 so the biomes and the titanic redwood forest have room to breathe.

Rendering — SDF ground blend (client/IsoMmo.Client.Core/TerrainBlendField.cs, GroundBlendRenderer.cs, Content/ground_blend.fx):

  • Per-biome signed distance fields (exact EDT) → smoothstep weights; 12 slots (three RGBA texture sets) so busy junctions never drop a biome.
  • Global graph-colouring of the biome co-occurrence graph → each biome uses one slot map-wide, so bilinear weight interpolation stays valid across chunk borders (kills the chunk seams / bright streaks).
  • Wrap-guttered biome atlas (no tile-boundary bleed), low-frequency domain-warp on the tiling (breaks the per-cell grid), linear-space mixing.
  • A light Gaussian blur on the SDFs rounds boundary corners, so there are no sharp dark spikes where a jagged biome pokes into a lighter one.

Content:

  • New biomes from E:\Asset: the four redwood floor types (mossy / bark / humus / mixed), dirt + snow roads (snow only over arctic ground), typed beaches; scripts/genland.py turns the raw art into the repo's seamless tile format.
  • World doubled to 768×576; the coastal volcano now sits on solid land (was half-submerged); redwood giants scattered with a minimum spacing so their crowns don't overlap into a wall.

Fix:

  • Static flicker: the cull margin now accounts for tall/wide sprites, so a titanic redwood's canopy stays drawn when its foot scrolls off-screen.

Screenshots / recording

Captured by piloting the client via the debug harness (login test, /tp <x> <y> through chat, screenshot):

Before — dual-grid tiles After — per-pixel SDF blend (2× world)
before after

Redwood forest — the titanic giants spread with gaps and clearings (not an overlapping wall):

forest

A biome boundary that used to show a hard dark spike — now rounded:

boundary

The coastal volcano, now on solid, walkable land:

volcano

How it was tested

  • New unit tests against the pure field logic: TerrainBlendFieldTests + EdtTests (interior purity, border blend, ∑weights = 1, cross-chunk slot consistency, overflow, determinism). Client.Core 130 green.
  • dotnet test IsoMmo.slnx — whole solution green.
  • Whole solution builds with 0 warnings; dotnet csharpier check . clean.
  • Drove the client to a classic beach, the multi-biome coast, the redwood forest, the volcano and the worst 8-biome junction; screenshotted each (attached).

Checklist

  • just lint passes (CSharpier + analyzers, zero warnings)
  • just test is green
  • The whole solution builds (client and tools included)
  • Multi-platform preserved (MonoGame DesktopGL + .NET; the shader is ps_3_0/GLSL via MGCB — no OS-specific deps)
  • Tests added/updated for this change
  • Linked the related issue — no single issue; the transition system was designed and iterated live

Notes / follow-ups (before merge)

  • docs/architecture.md still to document the SDF ground blend.
  • The old DrawDualTile path is kept behind a flag (GroundBlend != null) as a fallback; can be removed.
  • scripts/genscatter.py (redwood/arena prop re-scatter) still lives in a scratchpad; should move into scripts/ and be decoupled from the git-HEAD baseline.
  • The 768² field + blur adds a few seconds to client startup — candidate for optimisation.
## Summary The old dual-grid tile transitions between biomes looked bad — checkerboarding, dark seams and stair-stepped borders where two biomes met. I wanted the biomes to melt into each other instead of showing tile edges. This PR replaces the discrete dual-grid ground with a **per-pixel Signed-Distance-Field blend** running in a shader, expands the biome set from the cleaned `E:\Asset` art, and **doubles the world to 768×576** so the biomes and the titanic redwood forest have room to breathe. **Rendering — SDF ground blend** (`client/IsoMmo.Client.Core/TerrainBlendField.cs`, `GroundBlendRenderer.cs`, `Content/ground_blend.fx`): - Per-biome signed distance fields (exact EDT) → smoothstep weights; **12 slots** (three RGBA texture sets) so busy junctions never drop a biome. - **Global graph-colouring** of the biome co-occurrence graph → each biome uses one slot map-wide, so bilinear weight interpolation stays valid across chunk borders (kills the chunk seams / bright streaks). - Wrap-guttered biome atlas (no tile-boundary bleed), low-frequency **domain-warp** on the tiling (breaks the per-cell grid), **linear-space** mixing. - A light Gaussian blur on the SDFs rounds boundary **corners**, so there are no sharp dark spikes where a jagged biome pokes into a lighter one. **Content**: - New biomes from `E:\Asset`: the four redwood floor types (mossy / bark / humus / mixed), dirt + snow roads (snow only over arctic ground), typed beaches; `scripts/genland.py` turns the raw art into the repo's seamless tile format. - World doubled to **768×576**; the coastal volcano now sits on **solid land** (was half-submerged); redwood giants scattered with a **minimum spacing** so their crowns don't overlap into a wall. **Fix**: - Static **flicker**: the cull margin now accounts for tall/wide sprites, so a titanic redwood's canopy stays drawn when its foot scrolls off-screen. ## Screenshots / recording Captured by piloting the client via the debug harness (`login test`, `/tp <x> <y>` through chat, `screenshot`): | Before — dual-grid tiles | After — per-pixel SDF blend (2× world) | |---|---| | ![before](https://git.homelab.devncode.it/attachments/e2c47898-2d31-4249-9dc7-245ee509587a) | ![after](https://git.homelab.devncode.it/attachments/7ea11c97-6ec2-4c3a-b234-fb9d3c8c7461) | **Redwood forest** — the titanic giants spread with gaps and clearings (not an overlapping wall): ![forest](https://git.homelab.devncode.it/attachments/29bfa4d8-b56d-408d-a5b9-6cf2dcd65b92) **A biome boundary that used to show a hard dark spike — now rounded:** ![boundary](https://git.homelab.devncode.it/attachments/212f5cce-ded3-4eae-8c51-9d853af81890) **The coastal volcano, now on solid, walkable land:** ![volcano](https://git.homelab.devncode.it/attachments/dc1d5d27-9b98-4301-bcfd-25ce84abe3ec) ## How it was tested - New unit tests against the pure field logic: `TerrainBlendFieldTests` + `EdtTests` (interior purity, border blend, ∑weights = 1, cross-chunk slot consistency, overflow, determinism). **Client.Core 130 green.** - `dotnet test IsoMmo.slnx` — whole solution green. - Whole solution builds with **0 warnings**; `dotnet csharpier check .` clean. - Drove the client to a classic beach, the multi-biome coast, the redwood forest, the volcano and the worst 8-biome junction; screenshotted each (attached). ## Checklist - [x] `just lint` passes (CSharpier + analyzers, zero warnings) - [x] `just test` is green - [x] The whole solution builds (client and tools included) - [x] Multi-platform preserved (MonoGame DesktopGL + .NET; the shader is `ps_3_0`/GLSL via MGCB — no OS-specific deps) - [x] Tests added/updated for this change - [ ] Linked the related issue — no single issue; the transition system was designed and iterated live ## Notes / follow-ups (before merge) - `docs/architecture.md` still to document the SDF ground blend. - The old `DrawDualTile` path is kept behind a flag (`GroundBlend != null`) as a fallback; can be removed. - `scripts/genscatter.py` (redwood/arena prop re-scatter) still lives in a scratchpad; should move into `scripts/` and be decoupled from the git-HEAD baseline. - The 768² field + blur adds a few seconds to client startup — candidate for optimisation.
feat(assets): import authored biome props as statics, scattered per biome (#137)
All checks were successful
ci / Lint & Test (pull_request) Successful in 2m50s
a801f97077
feat(content): 66 authored biome props, forests, and a 256x192 biome world (#137)
All checks were successful
ci / Lint & Test (pull_request) Successful in 1m40s
89d2478c23
feat: SDF per-pixel ground blend + expanded biomes on a 2x world
Some checks failed
ci / Lint & Test (pull_request) Failing after 1m44s
1756a3d68b
panda force-pushed feat/world-new-biomes from 1756a3d68b
Some checks failed
ci / Lint & Test (pull_request) Failing after 1m44s
to 2c77adac19
Some checks failed
ci / Lua content lint (pull_request) Successful in 10s
ci / Lint & Test (pull_request) Failing after 2m41s
2026-07-29 19:43:44 +02:00
Compare
panda force-pushed feat/world-new-biomes from 2c77adac19
Some checks failed
ci / Lua content lint (pull_request) Successful in 10s
ci / Lint & Test (pull_request) Failing after 2m41s
to e402f7ac6a
All checks were successful
ci / Lua content lint (pull_request) Successful in 11s
ci / Lint & Test (pull_request) Successful in 6m28s
2026-07-29 20:02:13 +02:00
Compare
Author
Collaborator

Chiudo, superata, Shag succhiami il cazzo

Chiudo, superata, Shag succhiami il cazzo
panda closed this pull request 2026-08-02 00:23:30 +02:00
Author
Collaborator

Superseded. The SDF per-pixel ground blend is already on main (via the later blend/fluids path), and main now carries 29 terrain biomes vs the 21 on this branch — main is ahead, nothing unique left to salvage. Closing rather than rebasing 125 files / +8.3k lines of overlap.

Superseded. The SDF per-pixel ground blend is already on `main` (via the later blend/fluids path), and `main` now carries **29** terrain biomes vs the **21** on this branch — main is ahead, nothing unique left to salvage. Closing rather than rebasing 125 files / +8.3k lines of overlap.
All checks were successful
ci / Lua content lint (pull_request) Successful in 11s
ci / Lint & Test (pull_request) Successful in 6m28s
Required
Details

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo!221
No description provided.