diff --git a/OPSTACK-PROGRESS.md b/OPSTACK-PROGRESS.md index d2569c0..01b8c27 100644 --- a/OPSTACK-PROGRESS.md +++ b/OPSTACK-PROGRESS.md @@ -942,3 +942,51 @@ a FlatPlain or CrystalChamber strate for the visual A/B. Expect the floor/ceilin changed from the Z-term removal; the question is whether it still reads as the same *kind* of place. --- + +## 2026-07-27 — SlabEquivalence GREEN. The §3.1 payoff is measured: 36 and 40 of 60 tiles. + +``` +FlatPlain box verdicts over 60 tiles: 36 proved uniform, 24 Mixed +CrystalChamber box verdicts over 60 tiles: 40 proved uniform, 20 Mixed +both: 52 of 20000 samples differ, ALL at ULP scale, 0 cross the isosurface +``` + +**This is the number the whole §3.1 question was about, and it is better than Maze's.** Maze proved +23 of 60; the slab archetypes prove **36 and 40** — 60-67 % of tiles, where `ClassifyTile` proves +**zero** for these two today. The reason is structural rather than lucky: a slab strate is mostly +solid rock below its floor, and now that both surfaces are XY-pure their Z bands are known exactly, +so "this tile is entirely below the floor band" is a comparison rather than a sample. + +**The ULP residue is the known C10 floor** — 52/20000, 0 isosurface crossings, worst delta 2⁻¹⁸ +(exactly 1 ULP at a density magnitude of ~32). **Not investigated, on purpose.** Same shape as C10: +deterministic, ULP-scale, zero consequence, and six builds were already spent proving that shape is +not worth chasing. + +### The output exposed a real weakness in my own test — fixed + +Both archetypes reported **the same 52 and the same worst delta**. The explanation is in the +fixture: `FTestWorld::Build` sets only `GeneratorType`, so **FlatPlain and CrystalChamber both get +DEFAULT `FSlabGenerationParams`.** They are the same configuration at two depths. + +So the test's claim #2 was overstated. It demonstrated "the slab stack works at two depths", not +"one op serves two archetypes with different defaults" — **`CeilingRoughness`, the one field that +actually distinguishes CrystalChamber, was never varied.** The differing tile counts (36 vs 40) +come from the two slots' Z ranges, not from the archetypes differing. + +**A third pass added: `CrystalChamber(tuned)`** — `CeilingRoughness` 6 → 20, plus a rougher floor +and 3× the columns. It varies what actually matters, and it is deliberately the **worst case for +`ClassifyBox`**: a large `CeilingRoughness` widens the ceiling band and makes the +`Max(CeilZ - noise, FloorSurface + 2)` clamp far more likely to bind, which is exactly where a +false verdict — a HOLE — would appear. The default params were too gentle to stress that bound. + +**UNVERIFIED:** the third pass. Its box-verdict brute force is the part that matters; expect fewer +tiles proved uniform than the gentle passes (wider bands ⇒ more Mixed), and **zero** unsound +verdicts. If `NumUnsound > 0` here, the `CeilHi` bound in `FSlabVoidSource::ClassifyBox` is the +first suspect, not the noise contract. + +**Next single action:** rebuild, confirm the tuned pass is green, then the visual A/B on a FlatPlain +or CrystalChamber strate. The floor/ceiling shape WILL differ from before (the Z-term removal); the +question is whether it still reads as the same kind of place. Then `SurfaceWorld` +(OPSTACK-DECOMPOSITION §5) — biggest payoff, most care. + +--- diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp index 76b8b50..4c0598b 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp @@ -10,6 +10,11 @@ // 2. UN OPÉRATEUR, DEUX ARCHÉTYPES — la MÊME pile est vérifiée contre FlatPlain ET // CrystalChamber. `GetSlabDensity` ne les distingue par aucun branchement ; si la pile a // besoin d'en faire un, la fusion est fausse et ce test le dit. +// ⚠️ La fixture ne règle que `GeneratorType`, donc les deux slots portent des params PAR +// DÉFAUT : à eux seuls ils exécutent la même configuration à deux profondeurs. C'est la +// TROISIÈME passe (`CrystalChamber(tuned)`, `CeilingRoughness` 6 → 20) qui fait réellement +// varier ce qui distingue les deux archétypes — et qui sert en même temps de pire cas aux +// bornes d'amplitude de `ClassifyBox`. Voir le bloc en bas de fichier. // 3. LE VERDICT DE BOÎTE — et c'est ici que §3.1 se paie. `ClassifyTile` prouve ZÉRO tuile pour // FlatPlain et CrystalChamber aujourd'hui. Depuis que les deux surfaces sont XY-PURES, leurs // bornes en Z sont connues exactement (contrat [-1,1] de FBM), donc toute tuile entièrement @@ -76,21 +81,10 @@ bool FVoxelForgeOpStackSlabTest::RunTest(const FString& Parameters) //========================================================================= // Exécutée à l'identique sur FlatPlain et CrystalChamber. Si les deux passent avec la MÊME // pile et la MÊME fabrique, la fusion des deux archétypes est démontrée plutôt qu'affirmée. - auto RunForSlot = [&](int32 SlotIndex, const TCHAR* SlotName) + auto RunBattery = [&](const FSlabGenerationParams& SlabParams, + int32 TopVoxelZ, int32 BottomVoxelZ, + int32 SlotIndex, const TCHAR* SlotName) { - int32 TopVoxelZ = 0, BottomVoxelZ = 0; - if (!World.GetSlotVoxelZRange(SlotIndex, TopVoxelZ, BottomVoxelZ)) - { - AddError(FString::Printf( - TEXT("The fixture layout has no %s slot. Check FTestWorld::Build's Archetypes[] ") - TEXT("against FTestWorld::Slot%s."), SlotName, SlotName)); - return; - } - - const int32 MidChunkZ = ((TopVoxelZ + BottomVoxelZ) / 2) / CHUNK_SIZE; - const FSlabGenerationParams SlabParams = - World.StrateManager->GetSlabParamsForChunk(FIntVector(0, 0, MidChunkZ)); - // `GetSlabDensity` court-circuite sur une strate dégénérée (`return 1.0f`). Cette garde // appartient à la fonction d'archétype, pas à un opérateur ; la pile suppose une strate // valide, et `GetDensityAt` retombe sur le `switch` dans ce cas. @@ -303,8 +297,66 @@ bool FVoxelForgeOpStackSlabTest::RunTest(const FString& Parameters) } }; - RunForSlot(FTestWorld::SlotFlatPlain, TEXT("FlatPlain")); - RunForSlot(FTestWorld::SlotCrystalChamber, TEXT("CrystalChamber")); + //========================================================================= + // LES TROIS PASSES + //========================================================================= + auto ResolveSlot = [&](int32 SlotIndex, const TCHAR* SlotName, + FSlabGenerationParams& OutParams, int32& OutTop, int32& OutBottom) -> bool + { + if (!World.GetSlotVoxelZRange(SlotIndex, OutTop, OutBottom)) + { + AddError(FString::Printf( + TEXT("The fixture layout has no %s slot. Check FTestWorld::Build's Archetypes[] ") + TEXT("against FTestWorld::Slot%s."), SlotName, SlotName)); + return false; + } + const int32 MidChunkZ = ((OutTop + OutBottom) / 2) / CHUNK_SIZE; + OutParams = World.StrateManager->GetSlabParamsForChunk(FIntVector(0, 0, MidChunkZ)); + return true; + }; + + FSlabGenerationParams FlatParams, CrystalParams; + int32 FlatTop = 0, FlatBottom = 0, CrystalTop = 0, CrystalBottom = 0; + + if (ResolveSlot(FTestWorld::SlotFlatPlain, TEXT("FlatPlain"), FlatParams, FlatTop, FlatBottom)) + { + RunBattery(FlatParams, FlatTop, FlatBottom, FTestWorld::SlotFlatPlain, TEXT("FlatPlain")); + } + + if (ResolveSlot(FTestWorld::SlotCrystalChamber, TEXT("CrystalChamber"), + CrystalParams, CrystalTop, CrystalBottom)) + { + RunBattery(CrystalParams, CrystalTop, CrystalBottom, + FTestWorld::SlotCrystalChamber, TEXT("CrystalChamber")); + + //===================================================================== + // LA PASSE QUI FAIT VRAIMENT LA DÉMONSTRATION + //===================================================================== + // ⚠️ La fixture ne règle QUE `GeneratorType` : FlatPlain et CrystalChamber y reçoivent des + // `FSlabGenerationParams` PAR DÉFAUT, donc identiques. Les deux passes ci-dessus exécutent + // en réalité la même configuration à deux profondeurs — ce qui est un test utile, mais qui + // ne démontre PAS « un opérateur, deux jeux de défauts » : `CeilingRoughness`, la seule + // chose qui distingue réellement CrystalChamber, n'y varie jamais. + // + // Cette passe-ci fait varier ce qui compte, et elle est aussi le PIRE CAS pour les bornes + // d'amplitude de `ClassifyBox` : un `CeilingRoughness` élevé élargit la bande du plafond et + // rend le clamp `Max(CeilZ - bruit, FloorSurface + 2)` beaucoup plus susceptible de mordre. + // Si un verdict de boîte est faux quelque part, c'est ici qu'il apparaît. + // + // The fixture only sets GeneratorType, so both slots get DEFAULT slab params — the two + // passes above are the same configuration at two depths. This pass varies what actually + // distinguishes CrystalChamber, and is simultaneously the worst case for the ClassifyBox + // amplitude bounds: a large CeilingRoughness widens the ceiling band and makes the + // FloorSurface + 2 clamp far more likely to bind. + FSlabGenerationParams Tuned = CrystalParams; + Tuned.CeilingRoughness = 20.0f; // vs 6.0 par défaut — de vraies stalactites + Tuned.CeilingRoughnessFrequency = 0.09f; + Tuned.FloorRoughness = 9.0f; + Tuned.ColumnDensity = 0.25f; // beaucoup plus de colonnes ⇒ FillOnly plus souvent + Tuned.ColumnMaxRadius = 11.0f; + + RunBattery(Tuned, CrystalTop, CrystalBottom, 64, TEXT("CrystalChamber(tuned)")); + } return true; }