feat: SurfaceWorld complete — biome blending wired, guard removed

The two biome checks added last commit printed nothing on success, so a passing run
was indistinguishable from a block that never executed — the exact flaw I flagged
twice this session and then wrote myself. Both now report their coverage.

Step 2c closes SurfaceWorld:

- FSurfaceColumnSource takes per-biome params and an OWNED IVoxelBiomeField. Empty
  params leaves the original path bit-for-bit unchanged.
- The field is owned by the stack rather than borrowed: the adapter points at
  GetDensityAt's thread_local biome context and cache, and the stack is itself
  thread_local rebuilt in the same refetch block, so all three live and die together.
  Structural ownership beats a convention the next reader has to infer.
- The overhang amp blends across biomes — Lerp(Amp(PD), Amp(PN), W) with slope and
  threshold from the dominant only, as ComputeSurfaceColumn does. Interpolating the
  slope would be meaningless; it measures the terrain rather than configuring it.
- FGeneratorBiomeField lives in VoxelGenerator.cpp, on the side that knows the
  generator. The op sees a capability, never an owner — which is what lets it become
  an asset in Phase 3.
- The no-biome guard is removed from UsesOperatorStackForChunk.

Also: the two constructors now delegate to one body with one id counter. The first
draft had two competing counters, one tagged with a high bit to avoid collision,
which is a smell rather than a design.

5 of 8 archetypes ported: Maze, FlatPlain, CrystalChamber, SurfaceWorld.

UNVERIFIED: not compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-07-27 16:51:18 +02:00
1 parent 1a3f6b6a72
commit c277931a08
6 files changed
+267 -58

No files matched your search

@@ -576,19 +576,12 @@ bool UVoxelStrateManager::UsesOperatorStackForChunk(const FIntVector& ChunkCoord
case ECaveGeneratorType::CrystalChamber: return true; // UNE seule pile (BuildSlabStack)
case ECaveGeneratorType::SurfaceWorld:
// ⚠️ PORTÉ, MAIS PAS AVEC LES BIOMES. `BuildSurfaceStack` évalue UN jeu de params ; le
// chemin d'origine évalue le biome dominant puis interpole les HAUTEURS vers le voisin
// dans la bande de frontière (le combiner `Mask`, prototype de la Phase 3 — §5). Sans lui,
// une strate à biomes perdrait ses transitions : pas un décalage subtil, une couture nette
// à chaque frontière de biome.
//
// Donc la garde est ici, dans la MÊME fonction que la liste des archétypes portés, plutôt
// que dispersée dans `GetDensityAt` : « cette strate peut-elle prendre la pile ? » reste
// une seule question, posée à un seul endroit.
// Ported, but NOT with biomes: the stack evaluates ONE param set, while the original blends
// heights toward the neighbouring biome. Without the Mask combiner a biome strate would lose
// its transitions — a hard seam at every biome border, not a subtle shift.
return Def->Biomes.Num() == 0;
// ✅ La garde « pas de biomes » est TOMBÉE (étape 2c) : le combiner `Mask` existe, donc une
// strate à biomes mélange bien ses hauteurs comme le chemin d'origine. Les trois archétypes
// du dessus plus celui-ci font 5 des 8 portés.
// The no-biome guard is GONE: the Mask combiner exists, so a biome strate blends its heights
// exactly as the original path does.
return true;
default: return false;
}