From 8117d118ebdd0f82fe6bf37d898a0efc2046e012 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 15:56:19 +0000 Subject: [PATCH] refactor(opstack): the operator stack is the only density path Every strate now generates through its operator stack: authored definitions, cooked-season entries (recipe or native) and editor composer candidates alike. The per-strate opt-in and the archetype switch it guarded are gone. - Remove UVoxelStrateDefinition::bUseOperatorStack and the season wiring that set it; UsesOperatorStackForChunk now only says whether the slot has a known archetype. - GetDensityAt always builds a stack (composer, then recipe, then the native archetype stack); a degenerate zero-height strate stays air. - Remove the startup opt-in warning, the operator_stack startup-trace field and the four "Cave Bail Not Op Stack" stats (the remaining bail is counted by Cave Bail No Stack). - Explore commandlet: drop -opstack and the operator_stack fields; VoxelForgeTest.ps1 no longer passes -opstack=1. - Tests: FTestWorld::Build loses its bool parameter; legacy-vs-stack comparisons (MazeSeam, VerticalShaftSeam, TerrainDetailLiveness, WorldEdgeSeal, TilePostReach) collapse to the single stack path. This changes terrain for any strate asset that did not already have the opt-in ticked. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01URDGLsu7c4nunqNFPJEFPU --- .../Tests/VoxelForgeClassifyTileTest.cpp | 59 ++- .../VoxelForgeComposerCorpusFreeTest.cpp | 2 +- .../VoxelForgeComposerParameterRollTest.cpp | 183 ++++---- .../Tests/VoxelForgeComposerPromotionTest.cpp | 2 +- .../Tests/VoxelForgeComposerShowcaseTest.cpp | 2 +- .../VoxelForgeComposerStructureRollTest.cpp | 2 +- .../Tests/VoxelForgeCorrectnessRoundTest.cpp | 6 +- .../VoxelForgeDensityBlockEquivalenceTest.cpp | 6 +- .../Tests/VoxelForgeDescentCorridorTest.cpp | 5 +- .../Tests/VoxelForgeLateralRegionsTest.cpp | 8 +- .../Private/Tests/VoxelForgeMazeSeamTest.cpp | 65 +-- .../Tests/VoxelForgeOpStackChannelTest.cpp | 5 +- .../Tests/VoxelForgeScaleDiagnosisTest.cpp | 3 +- .../Tests/VoxelForgeSealedSolidProofTest.cpp | 3 +- .../VoxelForgeStrateConnectivityTest.cpp | 74 ++-- .../Private/Tests/VoxelForgeTestFixture.h | 15 +- .../Tests/VoxelForgeTilePostReachTest.cpp | 72 ++-- .../Tests/VoxelForgeWorldEdgeSealTest.cpp | 54 +-- Source/VoxelForge/Private/VoxelGenerator.cpp | 395 ++++++------------ Source/VoxelForge/Private/VoxelStats.cpp | 4 - .../VoxelForge/Private/VoxelStrateManager.cpp | 112 +---- Source/VoxelForge/Public/VoxelDensityOp.h | 53 +-- .../VoxelForge/Public/VoxelDensityOpStack.h | 30 +- Source/VoxelForge/Public/VoxelGenerator.h | 2 +- Source/VoxelForge/Public/VoxelStats.h | 4 - .../VoxelForge/Public/VoxelStrateDefinition.h | 23 - Source/VoxelForge/Public/VoxelStrateManager.h | 11 +- .../Private/VoxelForgeExploreCommandlet.cpp | 28 +- Tools/VoxelForgeTest.ps1 | 1 - 29 files changed, 412 insertions(+), 817 deletions(-) diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeClassifyTileTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeClassifyTileTest.cpp index 65a5d54..d63ecae 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeClassifyTileTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeClassifyTileTest.cpp @@ -39,12 +39,8 @@ #include "Misc/AutomationTest.h" #include "VoxelForgeTestFixture.h" -// Inclus ici DÉLIBÉRÉMENT : VoxelDensityOp.h n'est encore inclus par aucun .cpp, donc le -// compilateur ne le verrait jamais. Le fold qu'il définit prétend reproduire ClassifyTile — ce -// fichier est l'endroit naturel pour que cette prétention soit à la fois COMPILÉE et TESTÉE. -// Deliberately included here: VoxelDensityOp.h is not yet included by any .cpp, so the compiler -// would never see it. Its fold claims to reproduce ClassifyTile, so this is the natural place for -// that claim to be both compiled and tested. +// Le fold défini par VoxelDensityOp.h prétend reproduire ClassifyTile : ce fichier le teste. +// The fold defined in VoxelDensityOp.h claims to reproduce ClassifyTile; this file tests it. #include "VoxelDensityOp.h" IMPLEMENT_SIMPLE_AUTOMATION_TEST( @@ -153,7 +149,7 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters) AddInfo(FString::Printf( TEXT("ClassifyTile verdicts over %d scanned tiles: Mixed %d, AllSolid %d, AllAir %d ") - TEXT("(brute-forcing %d of them). Cave archetypes now use the opt-in operator-stack ") + TEXT("(brute-forcing %d of them). Cave archetypes use the operator-stack ") TEXT("ClassifyBox fold; the global XY edge proof also skips proven outer-shell tiles. A low ") TEXT("non-Mixed count is expected: every uncertain case remains Mixed for safety."), NumTilesScanned, NumMixed, NumAllSolid, NumAllAir, ToVerify.Num())); @@ -237,25 +233,24 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters) } //============================================================================= -// LE CHEMIN PILE D'OPÉRATEURS DE ClassifyTile — MÊME FORCE BRUTE, MONDE OPT-IN +// LE CHEMIN PILE D'OPÉRATEURS DE ClassifyTile — MÊME FORCE BRUTE, STRATES DE CAVE //============================================================================= -// `ClassifyTile` rendait `Mixed` sans appel pour tout archétype de CAVE. Il consulte désormais -// `FVoxelOpStack::ClassifyBox` quand la strate a coché `bUseOperatorStack` — donc **un tout nouveau -// chemin peut faire sauter le maillage d'une tuile**, et son erreur est un TROU : pas de triangles, -// pas de collision, invisible jusqu'à ce qu'un joueur tombe au travers. +// Pour un slot de CAVE, `ClassifyTile` consulte `FVoxelOpStack::ClassifyBox` — donc **la pile peut +// faire sauter le maillage d'une tuile**, et son erreur est un TROU : pas de triangles, pas de +// collision, invisible jusqu'à ce qu'un joueur tombe au travers. // -// Ce test est le même oracle par force brute que `ClassifyTileSoundness`, sur un monde dont TOUTES -// les strates ont coché la case. Il ne vérifie pas le pliage (c'est `BoxVerdictFold`) ni les -// opérateurs (ce sont les huit tests d'équivalence) : il vérifie le **câblage** — que la pile -// interrogée par le classifieur est bien celle qui produit la densité, params, drapeau et -// disturbances compris. +// Ce test est le même oracle par force brute que `ClassifyTileSoundness`, avec un tirage uniforme +// sur tout le layout. Il ne vérifie pas le pliage (c'est `BoxVerdictFold`) ni les opérateurs : il +// vérifie le **câblage** — que la pile interrogée par le classifieur est bien celle qui produit la +// densité, params et disturbances compris. // // ⚠️ LE COMPTEUR À LIRE EN PREMIER est le nombre de tuiles réellement brute-forcées. Un run vert // avec zéro verdict non-Mixed ne prouverait RIEN — exactement le piège que ce fichier documente // depuis sa première version, et la raison pour laquelle l'absence de verdict est une ERREUR ici. // -// Same brute-force oracle as ClassifyTileSoundness, on a world where every strate has opted in. -// It checks the WIRING, not the fold and not the operators. +// Same brute-force oracle as ClassifyTileSoundness, drawn uniformly over the whole layout so cave +// slots (which classify through their stack) dominate. It checks the WIRING, not the fold and not +// the operators. IMPLEMENT_SIMPLE_AUTOMATION_TEST( FVoxelForgeOpStackClassifyTileTest, @@ -266,11 +261,10 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters) { using namespace VoxelForgeTest; - // ⚠️ `bUseOperatorStack = true` sur toutes les strates : c'est LE point du test. La fixture - // donne à ce monde une `LayoutVersion` unique dans le processus, sans quoi les caches par chunk - // de `GetDensityAt` — dont `CP_UseOpStack` — pourraient encore porter ceux d'un autre test. + // La fixture donne à ce monde une `LayoutVersion` unique dans le processus, sans quoi les caches + // par chunk de `GetDensityAt` — dont `CP_UseOpStack` — pourraient encore porter ceux d'un autre test. FTestWorld World; - World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); + World.Build(/*Seed*/1337, /*GapChunks*/2); if (!World.IsValid()) { AddError(World.WhyInvalid()); @@ -287,8 +281,8 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters) const int32 BottomVoxelZ = World.BottomChunkZ * CHUNK_SIZE; // Tirage uniforme sur tout le layout, PAS biaisé vers SurfaceWorld comme l'autre test : ici ce - // sont précisément les strates de cave qui intéressent, puisque ce sont elles qui passent par le - // nouveau chemin. SurfaceWorld continue d'être prouvé par le code écrit à la main. + // sont précisément les strates de cave qui intéressent, puisque ce sont elles que ClassifyTile + // juge par leur pile. SurfaceWorld est prouvé par le test colonne écrit à la main. for (int32 t = 0; t < NumTilesScanned; ++t) { FTileSpec Spec; @@ -316,20 +310,17 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters) AddInfo(FString::Printf( TEXT("ClassifyTile ON THE OPERATOR-STACK PATH, %d scanned tiles: Mixed %d, AllSolid %d, ") - TEXT("AllAir %d (brute-forcing %d). Compare with VoxelForge.Determinism.ClassifyTileSoundness, ") - TEXT("which runs the SAME scan on a world that has NOT opted in: every verdict beyond what ") - TEXT("that test reports is a tile the mesher now skips and did not before. That difference ") - TEXT("IS the T1.d prize OPSTACK-PLAN has been aiming at -- and every one of those tiles is a ") - TEXT("hole if the wiring is wrong, which is what the brute force below is for."), + TEXT("AllAir %d (brute-forcing %d). Every cave-slot verdict is a tile the mesher skips on ") + TEXT("the strength of the stack's ClassifyBox fold -- and a hole if the wiring is wrong, ") + TEXT("which is what the brute force below is for."), NumTilesScanned, NumMixed, NumAllSolid, NumAllAir, ToVerify.Num())); if (ToVerify.Num() == 0) { AddError(TEXT("VACUOUS: not one tile got a non-Mixed verdict on the operator-stack path, so ") - TEXT("this test verified NOTHING about the new wiring. Either no strate actually ") - TEXT("opted in (check FTestWorld::Build's bUseOperatorStack), or every guard in the ") - TEXT("cave branch of ClassifyTile bailed to Mixed -- the params-identical check and ") - TEXT("the 27-chunk-coord cap are the likeliest. Do NOT read a green run as proof.")); + TEXT("this test verified NOTHING about the wiring. Every guard in the cave branch ") + TEXT("of ClassifyTile bailed to Mixed -- the params-identical check and the ") + TEXT("27-chunk-coord cap are the likeliest. Do NOT read a green run as proof.")); return false; } diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeComposerCorpusFreeTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeComposerCorpusFreeTest.cpp index 00e9994..53dea07 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeComposerCorpusFreeTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeComposerCorpusFreeTest.cpp @@ -356,7 +356,7 @@ bool FVoxelForgeComposerCorpusFreeTest::RunTest(const FString& Parameters) Corpus.GetEntries().Num(), 12); FTestWorld World; - World.Build(1337, 2, true); + World.Build(1337, 2); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeComposerParameterRollTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeComposerParameterRollTest.cpp index e1c42b5..6696d3b 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeComposerParameterRollTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeComposerParameterRollTest.cpp @@ -955,7 +955,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters) } FTestWorld World; - World.Build(1337, 2, true); + World.Build(1337, 2); if (!World.IsValid()) { AddError(World.WhyInvalid()); @@ -1117,7 +1117,6 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters) CandidateDefinition->SurfaceParams = Roll.ArchetypeParams.SurfaceParams; CandidateDefinition->VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams; CandidateDefinition->FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams; - CandidateDefinition->bUseOperatorStack = true; CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard; World.Reinitialize(); @@ -1342,123 +1341,109 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters) constexpr int32 ExpectedLatticeSamples = 16 * 16 * 16; FString Table = TEXT( "TERRAIN DETAIL LIVENESS (same 4096-point lattice; only one group changed per row)\n" - "path | group | samples | baseline digest | variant digest | baseline mean | variant mean | " + "group | samples | baseline digest | variant digest | baseline mean | variant mean | " "changed | sign changes | sum abs delta | max abs delta | verdict\n"); int32 TotalRows = 0; int32 FailedRows = 0; - for (const bool bUseOperatorStack : { false, true }) + FTestWorld World; + World.Build(1337, 2); + if (!World.IsValid()) { - FTestWorld World; - World.Build(1337, 2, bUseOperatorStack); - if (!World.IsValid()) + AddError(World.WhyInvalid()); + return false; + } + + UVoxelStrateDefinition* Definition = + World.Definitions[FTestWorld::SlotTunnelNetwork].Get(); + if (Definition == nullptr) + { + AddError(TEXT("The fixture has no TunnelNetwork definition for the liveness experiment.")); + return false; + } + Definition->TerrainOperations.Reset(); + TestEqual(TEXT("fixture has no terrain-op asset pool"), Definition->TerrainOperations.Num(), 0); + + TArray Points; + VF_BuildTerrainDetailLattice(World, Points); + TestEqual(TEXT("experiment uses the fixed lattice"), Points.Num(), ExpectedLatticeSamples); + + FStrateGenerationParams BaselineParams = Definition->GenerationParams; + VF_ClearTerrainDetailFields(BaselineParams); + TArray FirstBaseline; + bool bHaveFirstBaseline = false; + + for (const FTerrainDetailProbeSpec& Spec : GTerrainDetailProbeSpecs) + { + Definition->GenerationParams = BaselineParams; + World.Reinitialize(); + + TArray BaselineSamples; + const FTerrainDetailDensitySummary BaselineSummary = + VF_SampleTerrainDetailLattice(World, Points, BaselineSamples); + if (!bHaveFirstBaseline) { - AddError(World.WhyInvalid()); - return false; + FirstBaseline = BaselineSamples; + bHaveFirstBaseline = true; } - - UVoxelStrateDefinition* Definition = - World.Definitions[FTestWorld::SlotTunnelNetwork].Get(); - if (Definition == nullptr) + else { - AddError(TEXT("The fixture has no TunnelNetwork definition for the liveness experiment.")); - return false; - } - Definition->TerrainOperations.Reset(); - TestEqual( - FString::Printf(TEXT("%s fixture has no terrain-op asset pool"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")), - Definition->TerrainOperations.Num(), 0); - - TArray Points; - VF_BuildTerrainDetailLattice(World, Points); - TestEqual( - FString::Printf(TEXT("%s experiment uses the fixed lattice"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")), - Points.Num(), ExpectedLatticeSamples); - - FStrateGenerationParams BaselineParams = Definition->GenerationParams; - VF_ClearTerrainDetailFields(BaselineParams); - TArray FirstBaseline; - bool bHaveFirstBaseline = false; - - for (const FTerrainDetailProbeSpec& Spec : GTerrainDetailProbeSpecs) - { - Definition->GenerationParams = BaselineParams; - World.Reinitialize(); - - TArray BaselineSamples; - const FTerrainDetailDensitySummary BaselineSummary = - VF_SampleTerrainDetailLattice(World, Points, BaselineSamples); - if (!bHaveFirstBaseline) + bool bSameBaseline = FirstBaseline.Num() == BaselineSamples.Num(); + for (int32 SampleIndex = 0; bSameBaseline && SampleIndex < FirstBaseline.Num(); ++SampleIndex) { - FirstBaseline = BaselineSamples; - bHaveFirstBaseline = true; - } - else - { - bool bSameBaseline = FirstBaseline.Num() == BaselineSamples.Num(); - for (int32 SampleIndex = 0; bSameBaseline && SampleIndex < FirstBaseline.Num(); ++SampleIndex) - { - bSameBaseline = BitEqual(FirstBaseline[SampleIndex], BaselineSamples[SampleIndex]); - } - TestTrue( - FString::Printf(TEXT("%s baseline is stable before %s probe"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"), - Spec.Name), - bSameBaseline); - } - - FStrateGenerationParams VariantParams = BaselineParams; - VF_SetTerrainDetailProbe(VariantParams, Spec.Group); - Definition->GenerationParams = VariantParams; - World.Reinitialize(); - - TArray VariantSamples; - const FTerrainDetailDensitySummary VariantSummary = - VF_SampleTerrainDetailLattice(World, Points, VariantSamples); - const FTerrainDetailDensityDelta Delta = - VF_CompareTerrainDetailSamples(BaselineSamples, VariantSamples); - - const bool bObservedLive = Delta.Changed > 0; - const bool bVerdictMatches = bObservedLive == Spec.bExpectedLive; - ++TotalRows; - if (!bVerdictMatches) - { - ++FailedRows; + bSameBaseline = BitEqual(FirstBaseline[SampleIndex], BaselineSamples[SampleIndex]); } TestTrue( - FString::Printf(TEXT("%s %s field group has the measured liveness verdict"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"), - Spec.Name), - bVerdictMatches); - - Table += FString::Printf( - TEXT("%s | %s | %d | 0x%08x | 0x%08x | %.9g | %.9g | %d | %d | %.9g | %.9g | %s\n"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"), - Spec.Name, - Points.Num(), - BaselineSummary.Digest, - VariantSummary.Digest, - BaselineSummary.Mean, - VariantSummary.Mean, - Delta.Changed, - Delta.SignChanged, - Delta.SumAbs, - Delta.MaxAbs, - bObservedLive ? TEXT("LIVE") : TEXT("DEAD")); + FString::Printf(TEXT("baseline is stable before %s probe"), Spec.Name), + bSameBaseline); } + + FStrateGenerationParams VariantParams = BaselineParams; + VF_SetTerrainDetailProbe(VariantParams, Spec.Group); + Definition->GenerationParams = VariantParams; + World.Reinitialize(); + + TArray VariantSamples; + const FTerrainDetailDensitySummary VariantSummary = + VF_SampleTerrainDetailLattice(World, Points, VariantSamples); + const FTerrainDetailDensityDelta Delta = + VF_CompareTerrainDetailSamples(BaselineSamples, VariantSamples); + + const bool bObservedLive = Delta.Changed > 0; + const bool bVerdictMatches = bObservedLive == Spec.bExpectedLive; + ++TotalRows; + if (!bVerdictMatches) + { + ++FailedRows; + } + TestTrue( + FString::Printf(TEXT("%s field group has the measured liveness verdict"), Spec.Name), + bVerdictMatches); + + Table += FString::Printf( + TEXT("%s | %d | 0x%08x | 0x%08x | %.9g | %.9g | %d | %d | %.9g | %.9g | %s\n"), + Spec.Name, + Points.Num(), + BaselineSummary.Digest, + VariantSummary.Digest, + BaselineSummary.Mean, + VariantSummary.Mean, + Delta.Changed, + Delta.SignChanged, + Delta.SumAbs, + Delta.MaxAbs, + bObservedLive ? TEXT("LIVE") : TEXT("DEAD")); } AddInfo(Table); AddInfo(FString::Printf( - TEXT("Terrain detail liveness: %d group/path rows measured, %d mismatches; LIVE groups " + TEXT("Terrain detail liveness: %d group rows measured, %d mismatches; LIVE groups " "must have at least one bit-changed density and DEAD groups must have none. No " "terrain-operation assets were present in the fixture pool."), TotalRows, FailedRows)); TestEqual(TEXT("all terrain detail liveness rows matched their measured verdict"), FailedRows, 0); - TestEqual(TEXT("both density paths measured every terrain detail group"), TotalRows, - static_cast(2 * UE_ARRAY_COUNT(GTerrainDetailProbeSpecs))); + TestEqual(TEXT("every terrain detail group was measured"), TotalRows, + static_cast(UE_ARRAY_COUNT(GTerrainDetailProbeSpecs))); return FailedRows == 0; } diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeComposerPromotionTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeComposerPromotionTest.cpp index 73b354c..f64757f 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeComposerPromotionTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeComposerPromotionTest.cpp @@ -394,7 +394,7 @@ bool FVoxelForgeComposerPromotionTest::RunTest(const FString& Parameters) } FTestWorld World; - World.Build(1337, 2, true); + World.Build(1337, 2); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeComposerShowcaseTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeComposerShowcaseTest.cpp index 6135e36..a3f8ff1 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeComposerShowcaseTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeComposerShowcaseTest.cpp @@ -1590,7 +1590,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters) FTestWorld World; // The shared fixture defaults to a bounded 4-chunk volume for fast density tests. The // owner-facing scale audit must exercise the production/default 8-chunk envelope. - World.Build(BaseComposerSeed, 2, true, 8); + World.Build(BaseComposerSeed, 2, 8); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeComposerStructureRollTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeComposerStructureRollTest.cpp index 5ec9544..994cf90 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeComposerStructureRollTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeComposerStructureRollTest.cpp @@ -262,7 +262,7 @@ bool FVoxelForgeComposerStructureRollTest::RunTest(const FString& Parameters) } FTestWorld World; - World.Build(1337, 2, true); + World.Build(1337, 2); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeCorrectnessRoundTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeCorrectnessRoundTest.cpp index b3a3986..cc5f5cb 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeCorrectnessRoundTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeCorrectnessRoundTest.cpp @@ -132,7 +132,7 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST( bool FVoxelForgeTileCacheContextDeterminismTest::RunTest(const FString& Parameters) { FTestWorld World; - World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); + World.Build(/*Seed*/1337, /*GapChunks*/2); if (!World.IsValid()) { AddError(World.WhyInvalid()); @@ -208,8 +208,8 @@ bool FVoxelForgeTileCacheContextDeterminismTest::RunTest(const FString& Paramete // level-0 tile at the same origin is compared with a fresh world's level-0 output. FTestWorld WarmWorld; FTestWorld FreshWorld; - WarmWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); - FreshWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); + WarmWorld.Build(/*Seed*/1337, /*GapChunks*/2); + FreshWorld.Build(/*Seed*/1337, /*GapChunks*/2); if (!WarmWorld.IsValid() || !FreshWorld.IsValid()) { AddError(WarmWorld.IsValid() && FreshWorld.IsValid() diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeDensityBlockEquivalenceTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeDensityBlockEquivalenceTest.cpp index 92c9e41..2b780a7 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeDensityBlockEquivalenceTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeDensityBlockEquivalenceTest.cpp @@ -348,7 +348,7 @@ bool FVoxelForgeDensityBlockEquivalenceTest::RunTest(const FString& Parameters) for (int32 SeedIndex = 0; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex) { VoxelForgeTest::FTestWorld World; - World.Build(Seeds[SeedIndex], /*gap chunks=*/2, /*operator stack=*/true); + World.Build(Seeds[SeedIndex], /*gap chunks=*/2); if (!World.IsValid()) { AddError(World.WhyInvalid()); @@ -470,8 +470,8 @@ bool FVoxelForgeDensityBlockEquivalenceTest::RunTest(const FString& Parameters) // No-edit grids. In the two canonical network archetypes this enters the actual worker // block session, which partitions each mesher grid by resolved chunk before running the - // active operator list. The six legacy density routes still run their production scalar - // evaluator, as they do outside the block-safe network branch. + // active operator list. The six other archetypes run the scalar stack evaluator, as they + // do in production outside the block-safe network branch. for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex) { BaseResults[GridIndex] = CompareGeneratorBlockWithScalar( diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeDescentCorridorTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeDescentCorridorTest.cpp index 380c252..16f3d4f 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeDescentCorridorTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeDescentCorridorTest.cpp @@ -761,14 +761,13 @@ namespace const float SimulatedCarvedFinalDensity = -SimulatedCarvedInternalDensity; UE_LOG(LogTemp, Display, TEXT("[VoxelForgeDescentWriterContext] seed=0 sample=(%.3f,%.3f,%.3f) " - "chunk=(%d,%d,%d) archetype=%s asset_stack_flag=%d effective_op_stack=%d " + "chunk=(%d,%d,%d) archetype=%s effective_op_stack=%d " "passage_sdf=%.6f simulated_passage_carve_density=%.6f " "terrain_ops='%s' terrain_op_paths='%s' " "base=%.3f tunnel_columns=(%.3f,%.3f,%.3f) slab_columns=(%.3f,%.3f,%.3f)"), OutSamples.FirstSolidPosition.X, OutSamples.FirstSolidPosition.Y, OutSamples.FirstSolidPosition.Z, ChunkCoord.X, ChunkCoord.Y, ChunkCoord.Z, - *GeneratorName, Definition != nullptr && Definition->bUseOperatorStack ? 1 : 0, - bUsesOperatorStack ? 1 : 0, PassageSDF, SimulatedCarvedFinalDensity, + *GeneratorName, bUsesOperatorStack ? 1 : 0, PassageSDF, SimulatedCarvedFinalDensity, *TerrainOps, *TerrainOpPaths, Params.BaseDensity, Params.ColumnDensity, Params.ColumnMinRadius, Params.ColumnMaxRadius, Definition != nullptr ? Definition->SlabParams.ColumnDensity : -1.0f, diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeLateralRegionsTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeLateralRegionsTest.cpp index 21695e3..01c5ecd 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeLateralRegionsTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeLateralRegionsTest.cpp @@ -549,7 +549,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters) } FTestWorld World; - World.Build(Seed, 2, false); + World.Build(Seed, 2); int32 TopWorldZ = 0; int32 BottomWorldZ = 0; FVoxelStrateRegionManifest Manifest; @@ -612,7 +612,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters) } FTestWorld World; - World.Build(Seed, 2, false); + World.Build(Seed, 2); int32 TopWorldZ = 0; int32 BottomWorldZ = 0; FVoxelStrateRegionManifest Manifest; @@ -661,7 +661,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters) TestTrue(TEXT("found a single-region recipe identity case"), bRecipeIdentityChecked); FTestWorld World; - World.Build(MultiSeed, 2, false); + World.Build(MultiSeed, 2); if (!World.IsValid()) { AddError(World.WhyInvalid()); @@ -955,7 +955,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters) } FTestWorld LawWorld; - LawWorld.Build(Seed, 2, false); + LawWorld.Build(Seed, 2); FVoxelStrateRegionManifest LawManifest; FVoxelOpStack LawStack; FVoxelOpContext LawContext; diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeMazeSeamTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeMazeSeamTest.cpp index 86e8819..d83ccd5 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeMazeSeamTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeMazeSeamTest.cpp @@ -100,65 +100,44 @@ bool FVoxelForgeMazeSeamTest::RunTest(const FString& Parameters) { using namespace VoxelForgeTest; - FTestWorld LegacyWorld; - LegacyWorld.Build(7331, 2, false, 4); - FTestWorld StackWorld; - StackWorld.Build(7331, 2, true, 4); - if (!LegacyWorld.IsValid() || !StackWorld.IsValid()) + FTestWorld World; + World.Build(7331, 2, 4); + if (!World.IsValid()) { - AddError(LegacyWorld.IsValid() ? StackWorld.WhyInvalid() : LegacyWorld.WhyInvalid()); + AddError(World.WhyInvalid()); return false; } - int32 LegacyTop = 0, LegacyBottom = 0; - int32 StackTop = 0, StackBottom = 0; - if (!LegacyWorld.GetSlotVoxelZRange(FTestWorld::SlotMaze, LegacyTop, LegacyBottom) - || !StackWorld.GetSlotVoxelZRange(FTestWorld::SlotMaze, StackTop, StackBottom)) + int32 Top = 0, Bottom = 0; + if (!World.GetSlotVoxelZRange(FTestWorld::SlotMaze, Top, Bottom)) { AddError(TEXT("The seam fixture has no Maze slot.")); return false; } - const int32 LegacyMidChunkZ = ((LegacyTop + LegacyBottom) / 2) / CHUNK_SIZE; - const int32 StackMidChunkZ = ((StackTop + StackBottom) / 2) / CHUNK_SIZE; - const FMazeGenerationParams LegacyParams = LegacyWorld.StrateManager->GetMazeParamsForChunk( - FIntVector(0, 0, LegacyMidChunkZ)); - const FMazeGenerationParams StackParams = StackWorld.StrateManager->GetMazeParamsForChunk( - FIntVector(0, 0, StackMidChunkZ)); + const int32 MidChunkZ = ((Top + Bottom) / 2) / CHUNK_SIZE; + const FMazeGenerationParams Params = World.StrateManager->GetMazeParamsForChunk( + FIntVector(0, 0, MidChunkZ)); - TArray LegacyProbes; - TArray StackProbes; - AddMazeBoundaryProbes(LegacyParams, LegacyBottom, LegacyTop, LegacyProbes); - AddMazeBoundaryProbes(StackParams, StackBottom, StackTop, StackProbes); - TestEqual(TEXT("legacy and operator seam tests have the same probe count"), - LegacyProbes.Num(), StackProbes.Num()); + TArray Probes; + AddMazeBoundaryProbes(Params, Bottom, Top, Probes); - int32 LegacyFirst = INDEX_NONE, StackFirst = INDEX_NONE; - float LegacyBefore = 0.0f, LegacyAfter = 0.0f; - float StackBefore = 0.0f, StackAfter = 0.0f; - const int32 LegacyMismatches = CountContextMismatches( - LegacyWorld, LegacyProbes, LegacyFirst, LegacyBefore, LegacyAfter); - const int32 StackMismatches = CountContextMismatches( - StackWorld, StackProbes, StackFirst, StackBefore, StackAfter); + int32 First = INDEX_NONE; + float Before = 0.0f, After = 0.0f; + const int32 Mismatches = CountContextMismatches(World, Probes, First, Before, After); - TestEqual(TEXT("legacy Maze has no chunk-context seam mismatches"), LegacyMismatches, 0); - TestEqual(TEXT("operator-stack Maze has no chunk-context seam mismatches"), StackMismatches, 0); - TestTrue(TEXT("Maze seam test sampled cell-boundary probes"), LegacyProbes.Num() > 0); + TestEqual(TEXT("Maze has no chunk-context seam mismatches"), Mismatches, 0); + TestTrue(TEXT("Maze seam test sampled cell-boundary probes"), Probes.Num() > 0); - if (LegacyFirst != INDEX_NONE) + if (First != INDEX_NONE) { - AddError(FString::Printf(TEXT("Legacy Maze seam mismatch at probe %d: %.9g -> %.9g"), - LegacyFirst, LegacyBefore, LegacyAfter)); - } - if (StackFirst != INDEX_NONE) - { - AddError(FString::Printf(TEXT("Operator Maze seam mismatch at probe %d: %.9g -> %.9g"), - StackFirst, StackBefore, StackAfter)); + AddError(FString::Printf(TEXT("Maze seam mismatch at probe %d: %.9g -> %.9g"), + First, Before, After)); } AddInfo(FString::Printf( - TEXT("Maze seam freedom: %d boundary probes, legacy mismatches=%d, operator mismatches=%d; " + TEXT("Maze seam freedom: %d boundary probes, mismatches=%d; " "local window=2x2x2 child nodes ({-1,0}^3), no wide collect."), - LegacyProbes.Num(), LegacyMismatches, StackMismatches)); + Probes.Num(), Mismatches)); return true; } @@ -175,7 +154,7 @@ bool FVoxelForgeCaveWarpTileLodSeamTest::RunTest(const FString& Parameters) using namespace VoxelForgeTest; FTestWorld World; - World.Build(1337, 2, true, 4); + World.Build(1337, 2, 4); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackChannelTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackChannelTest.cpp index 2d905a8..95ddc21 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackChannelTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackChannelTest.cpp @@ -90,9 +90,8 @@ bool FVoxelForgeOpStackChannelTest::RunTest(const FString& Parameters) TestTrue(*FString::Printf(TEXT("%s shipping stack has a legal channel DAG"), Name), bValid); }; - // These are the eight builders currently shipped behind bUseOperatorStack. Keep the - // archetype list explicit so adding a builder without adding its validator assertion is - // visible in this test. + // These are the eight shipped archetype builders. Keep the archetype list explicit so adding + // a builder without adding its validator assertion is visible in this test. int32 MidChunkZ = 0; if (MidChunkForSlot(FTestWorld::SlotTunnelNetwork, TEXT("TunnelNetwork"), MidChunkZ)) diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeScaleDiagnosisTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeScaleDiagnosisTest.cpp index 4a9a64e..143f3d4 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeScaleDiagnosisTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeScaleDiagnosisTest.cpp @@ -140,7 +140,6 @@ namespace Definition->SurfaceParams = Candidate.ArchetypeParams.SurfaceParams; Definition->VerticalShaftParams = Candidate.ArchetypeParams.VerticalShaftParams; Definition->FloatingIslandParams = Candidate.ArchetypeParams.FloatingIslandParams; - Definition->bUseOperatorStack = true; Definition->TransitionType = EVoxelStrateTransition::Hard; World.Reinitialize(); } @@ -869,7 +868,7 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters) // Resolve every probe from the current corpus. This keeps the diagnosis attached to real // single-region candidates without making a corpus asset edit look like a geometry failure. FTestWorld World; - World.Build(DiagnosisSeed, 2, true, 8); + World.Build(DiagnosisSeed, 2, 8); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeSealedSolidProofTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeSealedSolidProofTest.cpp index abed1f4..a8b8392 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeSealedSolidProofTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeSealedSolidProofTest.cpp @@ -64,8 +64,7 @@ bool FVoxelForgeSealedSolidProofTest::RunTest(const FString& Parameters) // Forty voxels makes a complete 32-cell level-0 tile fit inside a bottom boundary seal. The // production asset remains untouched; this is a transient proof-only fixture configuration. FTestWorld World; - World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false, - /*InStrateHeightInChunks*/8); + World.Build(/*Seed*/1337, /*GapChunks*/2, /*InStrateHeightInChunks*/8); if (!World.IsValid()) { AddError(World.WhyInvalid()); diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeStrateConnectivityTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeStrateConnectivityTest.cpp index 707aa5b..7929d5b 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeStrateConnectivityTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeStrateConnectivityTest.cpp @@ -1325,54 +1325,38 @@ bool FVoxelForgeVerticalShaftSeamTest::RunTest(const FString& Parameters) { using namespace VoxelForgeTest; - bool bAllChecksPassed = true; - FString Summary = TEXT("VerticalShafts seam-freedom check (cell-boundary probes with two " - "neighbouring chunk-context orders):\n"); - for (const bool bUseOperatorStack : { false, true }) + FTestWorld World; + World.Build(/*InSeed=*/1337, /*InGapChunks=*/2); + if (!World.IsValid()) { - FTestWorld World; - World.Build(/*InSeed=*/1337, /*InGapChunks=*/2, bUseOperatorStack); - if (!World.IsValid()) - { - AddError(FString::Printf( - TEXT("HARD FAILURE: could not build the %s seam-test fixture."), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"))); - bAllChecksPassed = false; - continue; - } - - int32 Checked = 0; - int32 Mismatches = 0; - FString FirstMismatch; - if (!CheckVerticalShaftSeams(World, Checked, Mismatches, FirstMismatch)) - { - AddError(FString::Printf( - TEXT("HARD FAILURE: the %s seam test sampled no VerticalShafts positions."), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"))); - bAllChecksPassed = false; - continue; - } - - Summary += FString::Printf( - TEXT(" %s path: %d same-position re-evaluations, %d bit mismatches%s%s.\n"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"), - Checked, - Mismatches, - Mismatches > 0 ? TEXT("; first ") : TEXT(""), - Mismatches > 0 ? *FirstMismatch : TEXT("")); - if (Mismatches != 0) - { - AddError(FString::Printf( - TEXT("SEAM: %s VerticalShafts density changed after different chunk-context " - "orders: %s"), - bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"), - *FirstMismatch)); - bAllChecksPassed = false; - } + AddError(TEXT("HARD FAILURE: could not build the seam-test fixture.")); + return false; } - AddInfo(Summary); - return bAllChecksPassed; + int32 Checked = 0; + int32 Mismatches = 0; + FString FirstMismatch; + if (!CheckVerticalShaftSeams(World, Checked, Mismatches, FirstMismatch)) + { + AddError(TEXT("HARD FAILURE: the seam test sampled no VerticalShafts positions.")); + return false; + } + + AddInfo(FString::Printf( + TEXT("VerticalShafts seam-freedom check (cell-boundary probes with two neighbouring " + "chunk-context orders): %d same-position re-evaluations, %d bit mismatches%s%s."), + Checked, + Mismatches, + Mismatches > 0 ? TEXT("; first ") : TEXT(""), + Mismatches > 0 ? *FirstMismatch : TEXT(""))); + if (Mismatches != 0) + { + AddError(FString::Printf( + TEXT("SEAM: VerticalShafts density changed after different chunk-context orders: %s"), + *FirstMismatch)); + return false; + } + return true; } bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters) diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeTestFixture.h b/Source/VoxelForge/Private/Tests/VoxelForgeTestFixture.h index 65b9295..f4da2b3 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeTestFixture.h +++ b/Source/VoxelForge/Private/Tests/VoxelForgeTestFixture.h @@ -6,9 +6,9 @@ // --------------- // The interesting invariants (density purity across worker threads, ClassifyTile soundness) // only fire on the REAL path — UVoxelGenerator::GetDensityAt — because that is where the -// thread_local per-chunk caches live (CP_*, GSurfColCache, the diff slots, the SDF cache). -// Calling GetSurfaceDensity / GetMazeDensity directly bypasses every one of them and would -// test almost nothing. GetDensityAt in turn needs a live UVoxelStrateManager, whose only +// thread_local per-chunk caches live (CP_*, the prepared stacks, the diff slots, the SDF cache). +// Evaluating a stack directly bypasses every one of them and would test almost nothing. +// GetDensityAt in turn needs a live UVoxelStrateManager, whose only // entry point is Initialize(UVoxelSettings*, int32) reading TSoftObjectPtr pools. // // So the fixture builds a whole synthetic world in memory: transient strate definitions → @@ -46,7 +46,7 @@ namespace VoxelForgeTest * generator + diff layer. No AActor, no UWorld, no PIE. * * The default layout stacks one strate of EVERY archetype (in ECaveGeneratorType order), - * so a single fixture exercises all eight density functions and their per-chunk caches, + * so a single fixture exercises all eight archetype stacks and their per-chunk caches, * plus the gap-bedrock path when InterStrateGapChunks > 0. */ struct FTestWorld @@ -73,8 +73,7 @@ namespace VoxelForgeTest * dangerous — and `VoxelForge.Determinism.LargeSeedSurvives` deliberately passes big ones * (up to 2e9) to prove it stays that way. */ - void Build(int32 InSeed = 1337, int32 InGapChunks = 2, bool bUseOperatorStack = false, - int32 InStrateHeightInChunks = 4) + void Build(int32 InSeed = 1337, int32 InGapChunks = 2, int32 InStrateHeightInChunks = 4) { Settings = TStrongObjectPtr( NewObject(GetTransientPackage(), NAME_None, RF_Transient)); @@ -111,10 +110,6 @@ namespace VoxelForgeTest // refinement and roll tests use the smaller synthetic volume as a bounded memory // control; the owner-facing showcase opts into 8 chunks below. Def->StrateHeightInChunks = FMath::Max(1, InStrateHeightInChunks); - // L'OPT-IN de la pile d'opérateurs. Faux par défaut : les treize tests existants - // doivent continuer à exercer le `switch`, qui reste le comportement de référence. - // Seul le test de solidité de ClassifyTie côté pile le passe à vrai. - Def->bUseOperatorStack = bUseOperatorStack; // Hard transitions: param blending across a boundary would make "which archetype // owns this chunk" ambiguous, and these tests want an unambiguous mapping. Def->TransitionType = EVoxelStrateTransition::Hard; diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeTilePostReachTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeTilePostReachTest.cpp index d909cf2..134b5ca 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeTilePostReachTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeTilePostReachTest.cpp @@ -154,72 +154,50 @@ bool FVoxelForgeTilePostReachProofTest::RunTest(const FString& Parameters) return false; } - FTestWorld NativeWorld; - NativeWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false); - FTestWorld FusedWorld; - FusedWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); - if (!NativeWorld.IsValid() || !FusedWorld.IsValid()) + FTestWorld World; + World.Build(/*Seed*/1337, /*GapChunks*/2); + if (!World.IsValid()) { - AddError(NativeWorld.IsValid() && FusedWorld.IsValid() - ? TEXT("Unexpected invalid reach-proof fixture") - : (!NativeWorld.IsValid() ? NativeWorld.WhyInvalid() : FusedWorld.WhyInvalid())); + AddError(World.WhyInvalid()); return false; } - TArray NativeOrigins; - TArray FusedOrigins; - AppendWorldReachTiles(NativeWorld, NativeOrigins); - AppendWorldReachTiles(FusedWorld, FusedOrigins); - TestTrue(TEXT("the representative native tile set is non-empty"), NativeOrigins.Num() > 0); - TestTrue(TEXT("the representative fused tile set is non-empty"), FusedOrigins.Num() > 0); + TArray Origins; + AppendWorldReachTiles(World, Origins); + TestTrue(TEXT("the representative tile set is non-empty"), Origins.Num() > 0); - TStrongObjectPtr NativeMesher( + TStrongObjectPtr Mesher( NewObject(GetTransientPackage(), NAME_None, RF_Transient)); - TStrongObjectPtr FusedMesher( - NewObject(GetTransientPackage(), NAME_None, RF_Transient)); - NativeMesher->SetGenerator(NativeWorld.Generator.Get()); - FusedMesher->SetGenerator(FusedWorld.Generator.Get()); + Mesher->SetGenerator(World.Generator.Get()); VoxelGenLOD::ResetTilePostReachDiagnostics(); CVarGuard.SetScale(1.0f); - const FReachSnapshot NativeFull = RunReachTiles(*NativeMesher, NativeOrigins); - const FReachSnapshot FusedFull = RunReachTiles(*FusedMesher, FusedOrigins); - const uint64 FullFinalComparisons = NativeFull.FinalComparisons + FusedFull.FinalComparisons; - const uint64 FullFinalDifferences = NativeFull.FinalDifferences + FusedFull.FinalDifferences; + const FReachSnapshot Full = RunReachTiles(*Mesher, Origins); TestTrue(TEXT("normal reach compares final fields on the representative tiles"), - FullFinalComparisons > 0); - TestEqual(TEXT("normal reach has no final-field differences"), FullFinalDifferences, 0ull); - TestEqual(TEXT("normal reach has no skipped-post differences"), - NativeFull.AllDifferences + FusedFull.AllDifferences, 0ull); + Full.FinalComparisons > 0); + TestEqual(TEXT("normal reach has no final-field differences"), Full.FinalDifferences, 0ull); + TestEqual(TEXT("normal reach has no skipped-post differences"), Full.AllDifferences, 0ull); VoxelGenLOD::ResetTilePostReachDiagnostics(); // Use a deliberately tiny but valid scale so at least one envelope is rejected while the same // final-field samples remain exercised; this is the negative control, not a supported setting. CVarGuard.SetScale(0.01f); - const FReachSnapshot NativeShrunk = RunReachTiles(*NativeMesher, NativeOrigins); - const FReachSnapshot FusedShrunk = RunReachTiles(*FusedMesher, FusedOrigins); - const uint64 ShrunkFinalComparisons = - NativeShrunk.FinalComparisons + FusedShrunk.FinalComparisons; - const uint64 ShrunkFinalDifferences = - NativeShrunk.FinalDifferences + FusedShrunk.FinalDifferences; + const FReachSnapshot Shrunk = RunReachTiles(*Mesher, Origins); TestTrue(TEXT("shrunk reach still executes final-field comparisons"), - ShrunkFinalComparisons > 0); + Shrunk.FinalComparisons > 0); TestTrue(TEXT("shrunk reach demonstrably fails the final-field proof"), - ShrunkFinalDifferences > 0); + Shrunk.FinalDifferences > 0); AddInfo(FString::Printf( - TEXT("tile_post_reach_proof: native_tiles=%llu fused_tiles=%llu full_final=%llu/%llu " - "shrunk_final=%llu/%llu native_samples=%lld/%lld fused_samples=%lld/%lld."), - static_cast(NativeFull.Tiles), - static_cast(FusedFull.Tiles), - static_cast(FullFinalComparisons), - static_cast(FullFinalDifferences), - static_cast(ShrunkFinalComparisons), - static_cast(ShrunkFinalDifferences), - static_cast(NativeFull.DensitySamples), - static_cast(NativeShrunk.DensitySamples), - static_cast(FusedFull.DensitySamples), - static_cast(FusedShrunk.DensitySamples))); + TEXT("tile_post_reach_proof: tiles=%llu full_final=%llu/%llu shrunk_final=%llu/%llu " + "samples=%lld/%lld."), + static_cast(Full.Tiles), + static_cast(Full.FinalComparisons), + static_cast(Full.FinalDifferences), + static_cast(Shrunk.FinalComparisons), + static_cast(Shrunk.FinalDifferences), + static_cast(Full.DensitySamples), + static_cast(Shrunk.DensitySamples))); return true; } diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeWorldEdgeSealTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeWorldEdgeSealTest.cpp index d5e8d22..8b80dcd 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeWorldEdgeSealTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeWorldEdgeSealTest.cpp @@ -67,26 +67,20 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) FailureKinds += Kind; }; - // The ordinary fixture deliberately uses the requested default, so existing op-stack and - // legacy tests exercise the setting without having to know about the new post. - FTestWorld Legacy; - Legacy.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false); - FTestWorld StackWorld; - StackWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); + // The ordinary fixture deliberately uses the requested default, so the other tests exercise + // the setting without having to know about the new post. + FTestWorld Bounded; + Bounded.Build(/*Seed*/1337, /*GapChunks*/2); FTestWorld Unbounded; - Unbounded.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false); + Unbounded.Build(/*Seed*/1337, /*GapChunks*/2); - if (!Legacy.IsValid() || !StackWorld.IsValid() || !Unbounded.IsValid()) + if (!Bounded.IsValid() || !Unbounded.IsValid()) { - AddError(Legacy.IsValid() && StackWorld.IsValid() && Unbounded.IsValid() - ? TEXT("Unexpected edge-seal fixture state") - : (!Legacy.IsValid() ? Legacy.WhyInvalid() - : (!StackWorld.IsValid() ? StackWorld.WhyInvalid() : Unbounded.WhyInvalid()))); + AddError(!Bounded.IsValid() ? Bounded.WhyInvalid() : Unbounded.WhyInvalid()); return false; } - SetEdgeSettings(Legacy, TestWorldRadius); - SetEdgeSettings(StackWorld, TestWorldRadius); + SetEdgeSettings(Bounded, TestWorldRadius); SetEdgeSettings(Unbounded, 0.0f); //========================================================================== @@ -103,12 +97,12 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) for (int32 Slot = 0; Slot < 8; ++Slot) { int32 TopZ = 0, BottomZ = 0; - if (Legacy.GetSlotVoxelZRange(Slot, TopZ, BottomZ)) + if (Bounded.GetSlotVoxelZRange(Slot, TopZ, BottomZ)) { StrateZ.Add((float)((TopZ + BottomZ) / 2)); } } - StrateZ.Add(Legacy.MidVoxelZ()); + StrateZ.Add(Bounded.MidVoxelZ()); int32 NumPositiveSamples = 0; for (const FVector& XY : FarXY) @@ -117,8 +111,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) { const FVector P(XY.X, XY.Y, Z); ++NumPositiveSamples; - if (!IsSolidMC(*Legacy.Generator, P)) Fail(TEXT("positive-legacy")); - if (!IsSolidMC(*StackWorld.Generator, P)) Fail(TEXT("positive-opstack")); + if (!IsSolidMC(*Bounded.Generator, P)) Fail(TEXT("positive")); } } @@ -138,7 +131,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) for (const float Z : StrateZ) { const FVector P(XY.X, XY.Y, Z); - const float BoundedD = Legacy.Generator->GetDensityAt(P.X, P.Y, P.Z); + const float BoundedD = Bounded.Generator->GetDensityAt(P.X, P.Y, P.Z); const float UnboundedD = Unbounded.Generator->GetDensityAt(P.X, P.Y, P.Z); ++NumInteriorComparisons; if (!BitEqual(BoundedD, UnboundedD)) Fail(TEXT("interior-not-no-op")); @@ -193,7 +186,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) // CLASSIFYBOX — force boxes in the ramp and beyond the radius, then brute-force every voxel. //========================================================================== int32 TopShaftZ = 0, BottomShaftZ = 0; - if (!StackWorld.GetSlotVoxelZRange(FTestWorld::SlotVerticalShafts, TopShaftZ, BottomShaftZ)) + if (!Bounded.GetSlotVoxelZRange(FTestWorld::SlotVerticalShafts, TopShaftZ, BottomShaftZ)) { Fail(TEXT("missing-shaft-slot")); } @@ -218,17 +211,17 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) }; const int32 MidChunkZ = FMath::FloorToInt((BoxZ0 + BoxZ1) * 0.5f / CHUNK_SIZE); - const FVerticalShaftParams ShaftParams = StackWorld.StrateManager->GetVerticalShaftParamsForChunk( + const FVerticalShaftParams ShaftParams = Bounded.StrateManager->GetVerticalShaftParamsForChunk( FIntVector(0, 0, MidChunkZ)); FVoxelOpStack Stack; VoxelDensityOps::BuildVerticalShaftStack( - Stack, ShaftParams, StackWorld.Settings->Seed, - StackWorld.Generator->OriginSpineRadius, StackWorld.StrateManager.Get()); + Stack, ShaftParams, Bounded.Settings->Seed, + Bounded.Generator->OriginSpineRadius, Bounded.StrateManager.Get()); FVoxelOpContext Ctx; Ctx.ChunkCoord = FIntVector(0, 0, MidChunkZ); - Ctx.Seed = (uint32)StackWorld.Settings->Seed; - Ctx.LayoutVersion = StackWorld.StrateManager->GetLayoutVersion(); + Ctx.Seed = (uint32)Bounded.Settings->Seed; + Ctx.LayoutVersion = Bounded.StrateManager->GetLayoutVersion(); Ctx.StrateTopWorldZ = ShaftParams.StrateTopWorldZ; Ctx.StrateBottomWorldZ = ShaftParams.StrateBottomWorldZ; Ctx.WorldRadiusVoxels = TestWorldRadius; @@ -255,14 +248,14 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) for (int32 X = X0; X <= X1; ++X) { ++NumVoxelsChecked; - if (!IsSolidMC(*StackWorld.Generator, FVector((float)X, (float)Y, (float)Z))) + if (!IsSolidMC(*Bounded.Generator, FVector((float)X, (float)Y, (float)Z))) Fail(TEXT("classifybox-false-allsolid")); } } } // The T1.d path must be able to skip an edge tile without knowing which Z category it hits. - const EVoxelTileClass EdgeTile = StackWorld.Generator->ClassifyTile( + const EVoxelTileClass EdgeTile = Bounded.Generator->ClassifyTile( FIntVector(320, 0, BottomShaftZ + 8), 1, 8); int32 NumTilesProved = (EdgeTile == EVoxelTileClass::AllSolid) ? 1 : 0; if (EdgeTile != EVoxelTileClass::AllSolid) Fail(TEXT("classifytile-missed-edge")); @@ -283,9 +276,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) // coverage only: the synthetic helper case above is the hard assertion even if landing-site // adjustment moves every generated mouth out of this small test rim. FTestWorld GeneratedPassageWorld; - GeneratedPassageWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false); - FTestWorld GeneratedPassageStackWorld; - GeneratedPassageStackWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true); + GeneratedPassageWorld.Build(/*Seed*/1337, /*GapChunks*/2); auto CheckGeneratedPassages = [&](FTestWorld& World, const TCHAR* FailureKind) { if (!World.IsValid()) @@ -310,7 +301,6 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) } }; CheckGeneratedPassages(GeneratedPassageWorld, TEXT("generated-passage-breaches-rim")); - CheckGeneratedPassages(GeneratedPassageStackWorld, TEXT("generated-opstack-passage-breaches-rim")); // One sorted summary block; individual samples intentionally never log. AddInfo(FString::Printf( @@ -319,7 +309,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters) TEXT(" classify_tile_proved=%d\n") TEXT(" interior_comparisons=%d\n") TEXT(" near_rim_passage_checks=%d\n") - TEXT(" positive_sample_points=%d (checked in both paths)\n") + TEXT(" positive_sample_points=%d\n") TEXT(" rim_directions=%d\n") TEXT(" rim_max_adjacent_solidness_jump=%.6f (threshold=%.6f)\n") TEXT(" voxels_checked=%lld\n") diff --git a/Source/VoxelForge/Private/VoxelGenerator.cpp b/Source/VoxelForge/Private/VoxelGenerator.cpp index 23d5fe8..157bd6b 100644 --- a/Source/VoxelForge/Private/VoxelGenerator.cpp +++ b/Source/VoxelForge/Private/VoxelGenerator.cpp @@ -16,7 +16,7 @@ #include "VoxelWormField.h" #include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements #include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack -#include "VoxelDensityOpStack.h" // the opt-in per-strate operator stack +#include "VoxelDensityOpStack.h" // the per-strate operator stack #include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it #include "VoxelStats.h" #include "VoxelDensityProfile.h" @@ -669,9 +669,9 @@ struct FVoxelDensityBlockSession return; } - // Allocate lazily on the first operator-stack sample. Legacy archetypes still use the - // ordinary scalar generator and should not pay for a block scratch lattice merely because - // the mesher opened the scoped hand-off. + // Allocate lazily on the first operator-stack sample. Chunks that never reach a stack + // (gaps, out-of-layout, degenerate strates) use the ordinary scalar path and should not + // pay for a block scratch lattice merely because the mesher opened the scoped hand-off. BlockScratch.Reset(); } @@ -2549,12 +2549,11 @@ namespace /** * Construit la pile de cet archétype dans `OutStack` et remplit les bornes Z de `OutCtx`. * - * @return false quand la pile NE DOIT PAS être utilisée — archétype non porté, params absents, - * ou **strate dégénérée**. Ce dernier cas n'est pas de la prudence : cinq fonctions - * d'archétype court-circuitent sur `return 1.0f` (= air) quand la hauteur est nulle, - * et la pile n'a pas cet early-out, par conception. L'appelant retombe sur le `switch`, - * qui EST le comportement de référence. (`GetDensityWithParams`, lui, n'a aucun - * early-out de ce genre : TunnelNetwork/Underwater n'ont donc pas cette garde.) + * @return false quand la pile NE DOIT PAS être utilisée — archétype inconnu, params absents, + * ou **strate dégénérée** (hauteur nulle). La pile n'a pas d'early-out pour ce dernier + * cas, par conception : l'appelant rend de l'air (`GetDensityAt`) ou refuse de prouver + * la tuile (`ClassifyTile`). (`GetDensityWithParams` n'a aucun early-out de ce genre : + * TunnelNetwork/Underwater n'ont donc pas cette garde.) */ bool VF_BuildOpStackForChunk(ECaveGeneratorType Type, FVoxelStackParamRefs& Refs, int32 Seed, float SpineRadius, const UVoxelStrateManager* SM, @@ -2572,8 +2571,8 @@ namespace case ECaveGeneratorType::FlatPlain: case ECaveGeneratorType::CrystalChamber: - // UN SEUL cas pour les deux, comme le `switch` de production : `GetSlabDensity` ne les - // distingue pas non plus. Voir BuildSlabStack. + // UN SEUL cas pour les deux : ils ne diffèrent que par leurs valeurs par défaut. + // Voir BuildSlabStack. if (!Refs.Slab) { return false; } if (Refs.Slab->StrateTopWorldZ - Refs.Slab->StrateBottomWorldZ <= 0.0f) { return false; } OutCtx.StrateTopWorldZ = Refs.Slab->StrateTopWorldZ; @@ -2621,9 +2620,8 @@ namespace return true; default: - // `UsesOperatorStackForChunk` ne rend true que pour les archétypes portés (les 8), donc - // on ne devrait jamais arriver ici. Si ça arrive : retomber sur le `switch` plutôt que - // générer du vide — un monde faux est pire qu'un monde non porté. + // `UsesOperatorStackForChunk` ne rend true que pour les 8 archétypes, donc on ne devrait + // jamais arriver ici (valeur d'enum invalide). return false; } } @@ -2842,11 +2840,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co thread_local FBiomeContext CP_BiomeCtx; thread_local FChunkBiomeCache CP_BiomeCache; thread_local TArray CP_SurfaceBiomeParams; - // T1.a per-column surface cache: GSurfColCache (file-scope, shared with ClassifyTile). - // Discriminates the surface cache by strate: same strate ⇒ identical heightfield params ⇒ columns - // are shareable across the whole vertical chunk stack. Taken from the params themselves - // (StrateBottomWorldZ is unique per stacked strate) so the key can never disagree with CP_Surface. - thread_local int32 CP_StrateKey = MIN_int32; // AUDIT C2 — la clé DOIT contenir la version de layout, pas seulement le chunk. Après un // RebuildStrates / une édition à chaud, StrateManager reconstruit le layout et bumpe la // version ; un worker dont CP_Chunk vaut encore ce chunk sauterait le refetch et @@ -2860,8 +2853,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co // when the other context misses, this bit must force the rebuild guard below to run. thread_local bool CP_UsesTileCacheWindow = false; // OPSTACK — la pile d'opérateurs, construite dans le MÊME bloc de refetch que les - // params (donc même clé owner+chunk+version, aucune logique d'invalidation en plus). Vide tant que - // la strate n'a pas coché `bUseOperatorStack` ET que son archétype n'est pas porté. + // params (donc même clé owner+chunk+version, aucune logique d'invalidation en plus). Vide + // seulement pour une strate dégénérée (hauteur nulle). thread_local FVoxelOpStack CP_OpStack; thread_local bool CP_UseOpStack = false; thread_local uint64 CP_ManagerLifetimeId = 0; @@ -2890,8 +2883,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co // A coarse tile samples thousands of exact XY chunk keys, but those keys describe the // same immutable stack whenever the selected archetype is operator-stack backed. Share // that prepared state by Z; the stack still queries the actual world position, and its - // room-graph cache remains keyed by the real XY search window. Legacy tunnel states keep - // the full XYZ key because their native cache is built for one XY window. + // room-graph cache remains keyed by the real XY search window. Chunks outside the strate + // layout (constant air) keep the full XYZ key. const ECaveGeneratorType QueryGeneratorType = bUseChunkRoutingCache ? CP_RouteGeneratorType : StrateManager->GetGeneratorTypeForChunk(ChunkCoord); @@ -3066,7 +3059,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co CP_Maze = StrateManager->GetMazeParamsForChunk(ChunkCoord); break; case ECaveGeneratorType::SurfaceWorld: ResolveSurfaceChunkParams(ChunkCoord, CP_Surface, CP_BiomeCtx, CP_SurfaceBiomeParams); - CP_StrateKey = FMath::RoundToInt(CP_Surface.StrateBottomWorldZ); break; case ECaveGeneratorType::VerticalShafts: CP_Vert = StrateManager->GetVerticalShaftParamsForChunk(ChunkCoord); break; @@ -3085,131 +3077,112 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co } CP_Dist = StrateManager->GetDisturbanceParamsForChunk(ChunkCoord); - // ── OPSTACK : (re)construire la pile si cette strate l'a demandée. ── - // Une seule branche ajoutée au chemin densité, et elle est FROIDE : la construction est - // par chunk (comme le refetch de params juste au-dessus), jamais par voxel. - CP_UseOpStack = bUseChunkRoutingCache - ? CP_RouteUsesOperatorStack - : StrateManager->UsesOperatorStackForChunk(ChunkCoord); - CP_UseOpStack = CP_UseOpStack || CP_UseCustomRecipe; + // ── OPSTACK : (re)construire la pile de ce chunk. ── + // Toute strate est générée par une pile d'opérateurs. La construction est par chunk + // (comme le refetch de params juste au-dessus), jamais par voxel. + // Every strate is generated by an operator stack. An editor composer candidate or a + // cooked-season recipe supplies its own stack; otherwise — or if that stack cannot be + // built — the archetype's native stack is used. + CP_OpStack = FVoxelOpStack(); // move-assign : libère l'ancienne pile + CP_UseOpStack = false; #if WITH_EDITOR - // A composer override is already an explicit, validated stack description. It must - // be evaluated even when the authored definition had the normal operator-stack opt-in - // disabled; otherwise a multi-region candidate would be installed in the manager but - // silently fall back to the old one-archetype switch. - CP_UseOpStack = CP_UseOpStack || CP_UseComposerRegions; -#endif - if (CP_UseOpStack) + if (CP_UseComposerRegions) { - CP_OpStack = FVoxelOpStack(); // move-assign : libère l'ancienne pile - bool bBuiltSpecialStack = false; -#if WITH_EDITOR - if (CP_UseComposerRegions) + FVoxelOpContext RegionContext; + if (VF_BuildStrateRegionStack( + CP_ComposerRegions, OriginSpineRadius, StrateManager, + CP_OpStack, RegionContext, nullptr)) { - bBuiltSpecialStack = true; - FVoxelOpContext RegionContext; - if (VF_BuildStrateRegionStack( - CP_ComposerRegions, OriginSpineRadius, StrateManager, - CP_OpStack, RegionContext, nullptr)) - { - RegionContext.ChunkCoord = ChunkCoord; - RegionContext.Step = 1; - RegionContext.LayoutVersion = LayoutVersion; - RegionContext.WorldRadiusVoxels = WorldRadiusVoxels; - RegionContext.EdgeSealThickness = EdgeSealThickness; - CP_OpStack.PrepareChunk(RegionContext); - } - else - { - CP_UseOpStack = false; - } + RegionContext.ChunkCoord = ChunkCoord; + RegionContext.Step = 1; + RegionContext.LayoutVersion = LayoutVersion; + RegionContext.WorldRadiusVoxels = WorldRadiusVoxels; + RegionContext.EdgeSealThickness = EdgeSealThickness; + CP_OpStack.PrepareChunk(RegionContext); + CP_UseOpStack = true; } -#endif - if (!bBuiltSpecialStack) + else { - if (CP_UseCustomRecipe) - { - FVoxelOpContext RecipeContext; - if (VF_BuildStackFromRecipe( - CP_CustomRecipe, CP_CustomParams, CP_CustomRecipeSeed, - OriginSpineRadius, StrateManager, CP_OpStack, RecipeContext, nullptr)) - { - RecipeContext.ChunkCoord = ChunkCoord; - RecipeContext.Step = 1; - RecipeContext.LayoutVersion = LayoutVersion; - // The recipe builder supplies the candidate's vertical seal to the - // structural post. The XY edge seal is global and uses live settings. - RecipeContext.WorldRadiusVoxels = WorldRadiusVoxels; - RecipeContext.EdgeSealThickness = EdgeSealThickness; - CP_OpStack.PrepareChunk(RecipeContext); - } - else - { - // Import/load validates recipes before a season becomes active. Keep - // the fallback hole-safe if a future schema changes underneath it. - CP_UseOpStack = false; - } - } - else - { - FVoxelOpContext OpCtx; - OpCtx.ChunkCoord = ChunkCoord; - OpCtx.Seed = (uint32)Seed; - OpCtx.LayoutVersion = LayoutVersion; - OpCtx.WorldRadiusVoxels = WorldRadiusVoxels; - OpCtx.EdgeSealThickness = EdgeSealThickness; + CP_OpStack = FVoxelOpStack(); + CP_UseComposerRegions = false; + } + } +#endif + if (!CP_UseOpStack && CP_UseCustomRecipe) + { + FVoxelOpContext RecipeContext; + if (VF_BuildStackFromRecipe( + CP_CustomRecipe, CP_CustomParams, CP_CustomRecipeSeed, + OriginSpineRadius, StrateManager, CP_OpStack, RecipeContext, nullptr)) + { + RecipeContext.ChunkCoord = ChunkCoord; + RecipeContext.Step = 1; + RecipeContext.LayoutVersion = LayoutVersion; + // The recipe builder supplies the candidate's vertical seal to the + // structural post. The XY edge seal is global and uses live settings. + RecipeContext.WorldRadiusVoxels = WorldRadiusVoxels; + RecipeContext.EdgeSealThickness = EdgeSealThickness; + CP_OpStack.PrepareChunk(RecipeContext); + CP_UseOpStack = true; + } + else + { + // Import/load validates recipes before a season becomes active. If a future + // schema still fails here, fall back to the archetype's native stack below. + CP_OpStack = FVoxelOpStack(); + CP_UseCustomRecipe = false; + } + } + if (!CP_UseOpStack) + { + FVoxelOpContext OpCtx; + OpCtx.ChunkCoord = ChunkCoord; + OpCtx.Seed = (uint32)Seed; + OpCtx.LayoutVersion = LayoutVersion; + OpCtx.WorldRadiusVoxels = WorldRadiusVoxels; + OpCtx.EdgeSealThickness = EdgeSealThickness; // ⚠️ LE MAPPING VIT DANS `VF_BuildOpStackForChunk` (haut de ce fichier) ET NULLE // PART AILLEURS — `ClassifyTile` appelle la MÊME fabrique. Un verdict de tuile issu // d'une pile construite autrement serait un trou. Ici on ne fait que fournir les // params déjà cherchés juste au-dessus. - FVoxelStackParamRefs Refs; - Refs.Slab = &CP_Slab; - Refs.Maze = &CP_Maze; - Refs.Vert = &CP_Vert; - Refs.Float = &CP_Float; - Refs.Tunnel = &CP_Tunnel; + FVoxelStackParamRefs Refs; + Refs.Slab = &CP_Slab; + Refs.Maze = &CP_Maze; + Refs.Vert = &CP_Vert; + Refs.Float = &CP_Float; + Refs.Tunnel = &CP_Tunnel; // SurfaceWorld : le champ de biomes est fabriqué ICI, du côté qui connaît le // générateur, et TRANSFÉRÉ à la pile. L'opérateur ne voit qu'une `IVoxelBiomeField`, // ce qui lui permet de devenir un asset en Phase 3 sans traîner le générateur. - TArray PerBiome; - if (CP_GenType == ECaveGeneratorType::SurfaceWorld) - { - Refs.Surface = &CP_Surface; - if (CP_BiomeCtx.IsValid() && CP_SurfaceBiomeParams.Num() > 0) - { - PerBiome = CP_SurfaceBiomeParams; - Refs.SurfaceBiomeParams = &PerBiome; - Refs.BiomeField = MakeUnique( - this, &CP_BiomeCtx, &CP_BiomeCache, ChunkCoord.Z); - } - } - - CP_UseOpStack = VF_BuildOpStackForChunk( - CP_GenType, Refs, Seed, OriginSpineRadius, - StrateManager, CP_OpStack, OpCtx); - - // Le test appelle PrepareChunk, pas la production : c'est exactement la divergence - // qui rend un opérateur vert en test et faux en jeu. Les sept `PrepareChunk` - // actuels sont vides, donc ceci ne change RIEN aujourd'hui — c'est le point : le - // premier opérateur qui hisse vraiment du travail par chunk doit trouver l'appel - // déjà là. `Step` reste 1 : GetDensityAt ne connaît pas le pas d'échantillonnage - // du mesher (voir le contrat T2.b dans VoxelDensityOp.h). - // The test calls PrepareChunk and production did not — the exact divergence that - // makes an op green in test and wrong in game. All seven bodies are empty today, - // which is the point: the first op that hoists real per-chunk work must find the - // call already here. - if (CP_UseOpStack) { CP_OpStack.PrepareChunk(OpCtx); } + TArray PerBiome; + if (CP_GenType == ECaveGeneratorType::SurfaceWorld) + { + Refs.Surface = &CP_Surface; + if (CP_BiomeCtx.IsValid() && CP_SurfaceBiomeParams.Num() > 0) + { + PerBiome = CP_SurfaceBiomeParams; + Refs.SurfaceBiomeParams = &PerBiome; + Refs.BiomeField = MakeUnique( + this, &CP_BiomeCtx, &CP_BiomeCache, ChunkCoord.Z); } } + + CP_UseOpStack = VF_BuildOpStackForChunk( + CP_GenType, Refs, Seed, OriginSpineRadius, + StrateManager, CP_OpStack, OpCtx); + + // PrepareChunk runs here exactly as in the tests, so an op that hoists real + // per-chunk work behaves the same in game. `Step` reste 1 : GetDensityAt ne connaît + // pas le pas d'échantillonnage du mesher (voir le contrat T2.b dans VoxelDensityOp.h). + if (CP_UseOpStack) { CP_OpStack.PrepareChunk(OpCtx); } } - // The legacy path needs its prepared native tunnel-core cache. An operator-stack - // tunnel source publishes the same world-space result after EvalMC, so preparing a - // second full graph/core cache here would only duplicate the work that made coarse - // streaming expensive in the first place. Custom/composer paths retain the legacy - // cache unless their own stack supplies the hand-off. + // An operator-stack tunnel source publishes the world-space core after EvalMC, so + // preparing a second full graph/core cache here would only duplicate the work that made + // coarse streaming expensive in the first place. Only an editor composer region stack, + // which does not publish that hand-off, still gets the prepared native core. const bool bNativeTunnelCore = !CP_UseCustomRecipe && (CP_GenType == ECaveGeneratorType::TunnelNetwork || CP_GenType == ECaveGeneratorType::Underwater); @@ -3294,12 +3267,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co FTunnelCoreWorldEvaluation FusedTunnelCore; bool bRoomOwnsBottom = false; bool bRoomOwnsBottomValid = false; - FVector NativeWarpedPosition = FVector::ZeroVector; - bool bNativeWarpedPositionValid = false; // Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention - // MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche - // de diff qui suivent ne voient aucune différence. + // MC (négatif = solide), que les disturbances et la couche de diff qui suivent attendent. if (CP_UseOpStack) { const bool bCanUseFusedEvaluator = @@ -3383,66 +3353,11 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co } } } - else switch (CP_GenType) + else { - case ECaveGeneratorType::FlatPlain: - case ECaveGeneratorType::CrystalChamber: - Result = GetSlabDensity(WorldX, WorldY, WorldZ, CP_Slab); break; - case ECaveGeneratorType::Maze: - Result = GetMazeDensity(WorldX, WorldY, WorldZ, CP_Maze); break; - case ECaveGeneratorType::SurfaceWorld: - { - // Integer XY (the density grid) → reuse the column down its whole Z extent (T1.a). - // Fractional XY (gradient-normal samples) → compute directly (no cache key). - if (WorldX == FMath::FloorToFloat(WorldX) && WorldY == FMath::FloorToFloat(WorldY)) - { - const int32 IX = (int32)WorldX, IY = (int32)WorldY; - // XY-keyed LRU box (shared down the whole vertical strate stack). Acquire centres a box on - // the first sample so the rest of the chunk's queries — incl. the ±Step margin ring — hit. - FSurfaceColumnBox& Box = GSurfColCache.Acquire( - IX, IY, CP_StrateKey, Seed, LayoutVersion, - DensityCacheOwnerId, ManagerLifetimeId); - const int32 CI = (IY - Box.BaseY) * FSurfaceColumnBox::Dim + (IX - Box.BaseX); - if (!Box.Computed[CI]) - { - ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx, - CP_SurfaceBiomeParams, CP_BiomeCache, - Box.Cols[CI].TerrainZ, Box.Cols[CI].CeilSurf, - Box.Cols[CI].OverhangAmp, Box.Cols[CI].DirX, Box.Cols[CI].DirY); - Box.Computed[CI] = true; - } - const FSurfaceColumn& Col = Box.Cols[CI]; - Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ, - Col.TerrainZ, Col.CeilSurf, Col.OverhangAmp, - Col.DirX, Col.DirY, CP_Surface); - } - else - { - float TerrainZ, CeilSurf, OverhangAmp, DirX, DirY; - ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx, - CP_SurfaceBiomeParams, CP_BiomeCache, TerrainZ, CeilSurf, - OverhangAmp, DirX, DirY); - Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ, - TerrainZ, CeilSurf, OverhangAmp, DirX, DirY, CP_Surface); - } - break; - } - case ECaveGeneratorType::VerticalShafts: - Result = GetVerticalShaftDensity(WorldX, WorldY, WorldZ, CP_Vert); break; - case ECaveGeneratorType::FloatingIslands: - Result = GetFloatingIslandDensity(WorldX, WorldY, WorldZ, CP_Float); break; - case ECaveGeneratorType::Underwater: - case ECaveGeneratorType::TunnelNetwork: - default: - // Underwater shares tunnel rock (water table is a render-side overlay). - Result = GetDensityWithParams(WorldX, WorldY, WorldZ, CP_Tunnel, - CP_TunnelFP, LayoutVersion, - /*bApplyLegacyStructuralPosts=*/false, - nullptr, false, &bRoomOwnsBottom, - &NativeWarpedPosition); - bRoomOwnsBottomValid = true; - bNativeWarpedPositionValid = true; - break; + // Only a degenerate strate (zero height) has no stack. It is open air, like the + // archetype functions the stack replaced. + Result = 1.0f; } DensityCoreTimer.End(); @@ -3482,21 +3397,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co { VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::TunnelCorePosts); - FVector RoomQueryPosition; - if (bNativeWarpedPositionValid) + FVector RoomQueryPosition(WorldX, WorldY, WorldZ); + if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f) { - RoomQueryPosition = NativeWarpedPosition; - } - else - { - RoomQueryPosition = FVector(WorldX, WorldY, WorldZ); - if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f) - { - RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale; - } - RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp( - RoomQueryPosition, CP_Tunnel, static_cast(Seed)); + RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale; } + RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp( + RoomQueryPosition, CP_Tunnel, static_cast(Seed)); PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld( WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, nullptr, VoxelGenLOD::ShouldUseSpatialIndex( @@ -3520,7 +3427,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co // A disturbance is allowed to add visual detail, but it must not refill the landing's // measured air volume or turn its support slab back into a hole. Reassert the landing // air first, then the structural floor, in MC space before the final XY seal. The two - // calls use the same landing geometry already evaluated by the legacy and op-stack post. + // calls use the same landing geometry already evaluated by the op-stack structural post. VoxelDensityProfile::FScopedTimer DensityStructuralPostsTimer( VoxelDensityProfile::EBucket::DensityStructuralPosts); VoxelDensityProfile::FScopedTimer StructuralTailTimer( @@ -3695,21 +3602,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co { VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::TunnelCorePosts); - FVector RoomQueryPosition; - if (bNativeWarpedPositionValid) + FVector RoomQueryPosition(WorldX, WorldY, WorldZ); + if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f) { - RoomQueryPosition = NativeWarpedPosition; - } - else - { - RoomQueryPosition = FVector(WorldX, WorldY, WorldZ); - if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f) - { - RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale; - } - RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp( - RoomQueryPosition, CP_Tunnel, static_cast(Seed)); + RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale; } + RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp( + RoomQueryPosition, CP_Tunnel, static_cast(Seed)); TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld( WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, nullptr, @@ -6823,7 +6722,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached( bool bCanSolid = true; // "tout le treillis est solide" encore prouvable bool bCanAir = true; // "tout le treillis est air" encore prouvable // ── Catégorisation par Z du treillis : gap bedrock = solide ; hors layout = air constant ; - // SurfaceWorld = test colonne ; un slot cave opt-in = verdict de pile sur sa sous-boîte. ── + // SurfaceWorld = test colonne ; un slot cave = verdict de pile sur sa sous-boîte. ── struct FSurfSlot { int32 BotChunkZ = INT32_MAX; // identité du slot (borne basse de la strate, en chunks) @@ -6904,7 +6803,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached( // EST FAUX) et `IsGapChunk` rend false au-dessus du sommet (« open air, NOT a gap »). // Résultat : chaque tuile touchant l'air libre au-dessus du monde entrait dans la // BRANCHE DE CAVE, n'y trouvait aucun slot, et abandonnait — mesuré en jeu à 83 % des - // tuiles classées (`Cave Bail Not Op Stack No Layout` = 1.58 / 1.90). + // tuiles classées. // // La vérité est dans `GetGenerationParams` : hors layout il rend `BaseDensity = -1`, // `RoomDensity = 0`, `WormStrength = 0` — un champ CONSTANT, donc de l'air, sans salle @@ -6987,38 +6886,11 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached( // le pliage générique de la pile — mais SEULEMENT sous des conditions vérifiables, // parce qu'un faux verdict ici est un trou (pas de géométrie, pas de collision). // - // Condition 1 : la strate doit RÉELLEMENT être générée par la pile. Sinon on - // classerait un champ que le mesher ne produira pas. C'est le même drapeau, lu au - // même endroit, que `GetDensityAt`. + // Condition 1 : le slot doit avoir une pile (une définition d'archétype connue). + // Sinon on classerait un champ que le mesher ne produira pas. if (!StrateManager->UsesOperatorStackForChunk(CC)) { - // Attribution DIAGNOSTIQUE uniquement : distingue une strate cave entièrement - // désactivée d'une tuile de frontière qui a rencontré un slot désactivé avant - // la garde « slot différent » ci-dessous. - // On résout les bornes APRÈS l'échec du même prédicat ; elles ne changent ni - // la condition, ni le point de retour, ni le verdict. - // Diagnostic attribution only: separates a wholly disabled cave slot from a - // boundary tile that met a disabled slot before the different-slot guard - // below. Resolve bounds only after the same predicate fails; classification - // control flow and return value stay unchanged. - int32 FailedTopCZ = 0, FailedBotCZ = 0; - if (!StrateManager->GetStrateChunkZBounds(ChunkZ, FailedTopCZ, FailedBotCZ)) - { - INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackNoLayout); - } - else - { - const int32 TileMinCZ = FloorDivC(MinZ, CHUNK_SIZE); - const int32 TileMaxCZ = FloorDivC(MaxZ, CHUNK_SIZE); - if (TileMinCZ >= FailedBotCZ && TileMaxCZ <= FailedTopCZ) - { - INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackSoleSlot); - } - else - { - INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackBoundaryTile); - } - } + INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack); return EVoxelTileClass::Mixed; } @@ -7223,16 +7095,11 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached( return EVoxelTileClass::Mixed; // la boîte déborde sur un autre archétype } - // Le drapeau doit tenir sur TOUS les chunks de la boîte, pas seulement sur celui qui a - // déclenché la tentative : un seul chunk hors pile invaliderait le verdict. + // Every chunk of the box must have a stack, not only the one that triggered the + // attempt (redundant with the Z pass today, kept hole-safe). if (!StrateManager->UsesOperatorStackForChunk(CC)) { - // Le passage Z précédent a déjà accepté l'unique slot cave. Avec le layout actuel - // (prédicat indépendant de X/Y), ce recheck est redondant ; un hit nomme donc - // précisément cette garde tardive au lieu d'être agrégé aux opt-ins désactivés. - // The prior Z pass already accepted the sole cave slot. With the current X/Y- - // independent predicate this recheck is redundant, so attribute it separately. - INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackRecheck); + INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack); return EVoxelTileClass::Mixed; } @@ -7331,8 +7198,8 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached( if (!VF_BuildOpStackForChunk(CaveType, Refs, Seed, OriginSpineRadius, StrateManager, TileStack, OpCtx)) { - // Strate dégénérée ou archétype non porté : `GetDensityAt` retomberait sur le `switch`, - // donc la pile ne décrit pas ce que le mesher verra. Aucun verdict. + // Strate dégénérée : `GetDensityAt` y rend de l'air sans pile, donc la pile ne décrit + // pas ce que le mesher verra. Aucun verdict. INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack); return EVoxelTileClass::Mixed; } diff --git a/Source/VoxelForge/Private/VoxelStats.cpp b/Source/VoxelForge/Private/VoxelStats.cpp index d70d2de..94105ce 100644 --- a/Source/VoxelForge/Private/VoxelStats.cpp +++ b/Source/VoxelForge/Private/VoxelStats.cpp @@ -10,10 +10,6 @@ DEFINE_STAT(STAT_VoxelForgeTilesSkippedAllAir); DEFINE_STAT(STAT_VoxelForgeTilesMeshed); DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid); DEFINE_STAT(STAT_VoxelForgeTilesOpStackAir); -DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackSoleSlot); -DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackBoundaryTile); -DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackNoLayout); -DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackRecheck); DEFINE_STAT(STAT_VoxelForgeCaveMixOutOfLayout); DEFINE_STAT(STAT_VoxelForgeCaveMixGap); DEFINE_STAT(STAT_VoxelForgeCaveMixSurfaceWorld); diff --git a/Source/VoxelForge/Private/VoxelStrateManager.cpp b/Source/VoxelForge/Private/VoxelStrateManager.cpp index a8dce8a..5fc1413 100644 --- a/Source/VoxelForge/Private/VoxelStrateManager.cpp +++ b/Source/VoxelForge/Private/VoxelStrateManager.cpp @@ -2,7 +2,6 @@ // Runtime strate layout generation and queries. #include "VoxelStrateManager.h" -#include "CoreGlobals.h" // GIsAutomationTesting — the opt-in diagnostic stays quiet under tests #include "VoxelSettings.h" #include "VoxelSeasonAsset.h" #include "VoxelTypes.h" // For CHUNK_SIZE, VOXEL_SIZE, WorldToChunkCoord @@ -1334,7 +1333,6 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed) } Slot.Definition->GeneratorType = SeasonStrate.Archetype; - Slot.Definition->bUseOperatorStack = SeasonStrate.bUsesRecipe; Slot.Definition->StrateHeightInChunks = SeasonStrate.HeightInChunks; Slot.Definition->GenerationParams = SeasonStrate.Params.TunnelNetworkParams; Slot.Definition->SlabParams = SeasonStrate.Params.SlabParams; @@ -1401,10 +1399,9 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed) : FString::Printf(TEXT("Value_%d"), static_cast(Slot.Definition->GeneratorType)); VoxelForgeStartupTrace::RecordEvent(TEXT("strate_slot"), FString::Printf( TEXT("\"index\":%d,\"definition\":\"%s\",\"archetype\":\"%s\",\"top_chunk_z\":%d," - "\"bottom_chunk_z\":%d,\"height_chunks\":%d,\"operator_stack\":%d"), + "\"bottom_chunk_z\":%d,\"height_chunks\":%d"), Slot.StrateIndex, *DefinitionPath, *ArchetypeName, - Slot.TopChunkZ, Slot.BottomChunkZ, Slot.HeightInChunks, - Slot.Definition->bUseOperatorStack ? 1 : 0)); + Slot.TopChunkZ, Slot.BottomChunkZ, Slot.HeightInChunks)); } UE_LOG(LogTemp, Log, TEXT("[StrateManager] Strate %d: '%s' | Z chunks [%d to %d] | %d chunks tall"), @@ -1415,79 +1412,6 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed) Slot.HeightInChunks); } - // Diagnostic de configuration, une seule fois par construction de layout. SurfaceWorld est - // volontairement exclu : son chemin T1.d exact-lattice ne dépend pas de ce drapeau. - // Configuration diagnostic once per layout build. SurfaceWorld is deliberately excluded: - // its exact-lattice T1.d path does not depend on this flag. - int32 NumCaveSlots = 0; - int32 NumOperatorStackDisabledCaves = 0; - for (const FStrateSlot& Slot : StrateLayout) - { - const ECaveGeneratorType SlotArchetype = bUseSeason - ? SeasonStrates[Slot.StrateIndex].Archetype : Slot.Definition->GeneratorType; - if (!Slot.Definition || SlotArchetype == ECaveGeneratorType::SurfaceWorld) - { - continue; - } - - ++NumCaveSlots; - const bool bUsesStack = bUseSeason - ? SeasonStrates[Slot.StrateIndex].bUsesRecipe : Slot.Definition->bUseOperatorStack; - if (!bUsesStack) - { - ++NumOperatorStackDisabledCaves; - } - } - - // ⚠️ WARNING EN ÉDITEUR/JEU, JAMAIS EN TEST. Les tests `Determinism.*` construisent - // DÉLIBÉRÉMENT un monde non opt-in — c'est leur oracle de comparaison — et le framework - // d'automatisation compte un Warning comme un échec. Un diagnostic ne doit pas casser la suite - // qu'il est censé éclairer. Le message reste écrit UNE fois : seule la verbosité change. - // Warning in editor/game where it is actionable, never in tests: the Determinism.* tests build - // a non-opted-in world ON PURPOSE as their comparison oracle, and the automation framework - // treats a Warning as a failure. One message, two verbosities. - const bool bQuietDiagnostic = GIsAutomationTesting; - - if (NumOperatorStackDisabledCaves > 0) - { - const FString Summary = FString::Printf( - TEXT("[StrateManager] Operator-stack opt-in: %d/%d cave layout slots have Use Operator Stack disabled. These slots cannot use operator-stack ClassifyBox/T1.d; enable the asset setting on the listed definitions if that is intended."), - NumOperatorStackDisabledCaves, NumCaveSlots); - - if (bQuietDiagnostic) { UE_LOG(LogTemp, Verbose, TEXT("%s"), *Summary); } - else { UE_LOG(LogTemp, Warning, TEXT("%s"), *Summary); } - } - else - { - UE_LOG(LogTemp, Log, - TEXT("[StrateManager] Operator-stack opt-in: all %d cave layout slots have Use Operator Stack enabled."), - NumCaveSlots); - } - - for (const FStrateSlot& Slot : StrateLayout) - { - const bool bSeasonStack = bUseSeason - && SeasonStrates[Slot.StrateIndex].bUsesRecipe; - const ECaveGeneratorType SlotArchetype = bUseSeason - ? SeasonStrates[Slot.StrateIndex].Archetype : Slot.Definition->GeneratorType; - if (!Slot.Definition - || SlotArchetype == ECaveGeneratorType::SurfaceWorld - || (bUseSeason ? bSeasonStack : Slot.Definition->bUseOperatorStack)) - { - continue; - } - - const FString Line = FString::Printf( - TEXT("[StrateManager] cave slot=%d name='%s' Z chunks=[%d to %d] bUseOperatorStack=false"), - Slot.StrateIndex, - *Slot.Definition->StrateName.ToString(), - Slot.TopChunkZ, - Slot.BottomChunkZ); - - if (bQuietDiagnostic) { UE_LOG(LogTemp, Verbose, TEXT("%s"), *Line); } - else { UE_LOG(LogTemp, Warning, TEXT("%s"), *Line); } - } - CachedSeed = WorldSeed; bOpenSurfaceEntry = Settings->bOpenSurfaceEntry; OriginSpineRadius = bUseSeason @@ -6281,30 +6205,13 @@ bool UVoxelStrateManager::UsesOperatorStackForChunk(const FIntVector& ChunkCoord const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkCoord.Z); if (SlotIdx < 0 || !StrateLayout[SlotIdx].Definition) { return false; } - if (SeasonStrates.IsValidIndex(SlotIdx)) - { - // A recipe is an explicit stack opt-in. Native fixed entries retain the authored/switch - // route while still reading their season parameter vector. - return SeasonStrates[SlotIdx].bUsesRecipe; - } - -#if WITH_EDITOR - if (FindComposerOverride(StrateLayout[SlotIdx].StrateIndex) != nullptr) - { - // Temporary composer candidates are measured through the stack path. Do not inherit an - // unrelated asset flag from the slot being replaced. - return true; - } -#endif - + // Every strate is generated by the operator stack: authored definitions, cooked-season entries + // (recipe or native) and editor composer candidates alike. The slot definition carries the + // archetype in every case. const UVoxelStrateDefinition* Def = StrateLayout[SlotIdx].Definition; - if (!Def->bUseOperatorStack) { return false; } - // LA LISTE DES ARCHÉTYPES PORTÉS — le seul endroit où elle est écrite. Un archétype non porté - // ignore le drapeau et retombe sur le `switch`, pour qu'on puisse cocher la case sur n'importe - // quelle strate sans rien casser en attendant son portage. - // THE PORTED-ARCHETYPE LIST, written down exactly once. An unported archetype ignores the flag - // and falls back to the switch, so the box can be ticked anywhere without breaking anything. + // LA LISTE DES ARCHÉTYPES — le seul endroit où elle est écrite. + // THE ARCHETYPE LIST, written down exactly once. switch (Def->GeneratorType) { case ECaveGeneratorType::Maze: return true; @@ -6327,16 +6234,13 @@ bool UVoxelStrateManager::UsesOperatorStackForChunk(const FIntVector& ChunkCoord case ECaveGeneratorType::Underwater: // ⚠️ AUCUNE PILE À ELLE : `Underwater` EST `TunnelNetwork` plus un drapeau d'eau consommé - // côté rendu. `GetDensityAt` les met dans le même `case`, et `WaterLevelRelative` n'est lu + // côté rendu. `VF_BuildOpStackForChunk` les met dans le même `case`, et `WaterLevelRelative` n'est lu // que par `GetWaterLevel*` de ce manager — jamais par la densité (vérifié, pas supposé). case ECaveGeneratorType::TunnelNetwork: // Le plus gros : squelette SDF → douze modificateurs de détail → override d'op par salle, // 19 opérateurs, dont `FRoomGraphSource` qui **APPELLE** `BuildChunkCache`/`EvaluateSDFCached` // au lieu de les transcrire — c'est là que vit la discipline d'invariance de fenêtre // d'ARCHITECTURE §8.4 ; en forker une copie la casserait. - // - // **8 SUR 8.** Le `switch` d'archétypes a un jumeau en pile d'opérateurs, opt-in par - // strate, chacun vérifié par un test d'équivalence bit à bit contre sa fonction d'origine. return true; default: return false; diff --git a/Source/VoxelForge/Public/VoxelDensityOp.h b/Source/VoxelForge/Public/VoxelDensityOp.h index 52982ab..2b82d91 100644 --- a/Source/VoxelForge/Public/VoxelDensityOp.h +++ b/Source/VoxelForge/Public/VoxelDensityOp.h @@ -1,34 +1,32 @@ // VoxelDensityOp.h // LE CONTRAT de la pile d'opérateurs de densité / THE density operator stack CONTRACT. -// Design : Docs/archive/OPSTACK-PLAN.md. The contract is implemented by the opt-in stack path in -// UVoxelGenerator::GetDensityAt; the legacy archetype switch remains for non-opt-in strates. +// Design : Docs/archive/OPSTACK-PLAN.md. The contract is implemented by the stack path in +// UVoxelGenerator::GetDensityAt, which generates every strate. // // ───────────────────────────────────────────────────────────────────────────────────────── // POURQUOI / WHY // ───────────────────────────────────────────────────────────────────────────────────────── -// UVoxelGenerator::GetDensityAt est aujourd'hui un `switch` sur 8 ECaveGeneratorType, chacun -// possédant sa fonction de densité et son struct de params. Conséquences : une nouvelle idée de -// monde coûte ~6 sites d'édition, et surtout LES IDÉES NE PEUVENT PAS SE COMBINER — un archétype -// possède le voxel entier. On ne peut pas écrire « une strate de surface dont les montagnes -// contiennent un réseau de salles, avec des îles flottantes dans le vide au-dessus », à aucun prix. +// Chaque archétype (ECaveGeneratorType) est une composition d'opérateurs, pas une fonction de +// densité monolithique qui posséderait le voxel entier. Une idée de monde s'exprime en empilant +// sources, combinateurs et modificateurs — et les idées peuvent donc SE COMBINER : « une strate de +// surface dont les montagnes contiennent un réseau de salles, avec des îles flottantes au-dessus ». // -// GetDensityAt is today a `switch` over 8 ECaveGeneratorType, each owning a bespoke density -// function and param struct. A new world idea costs ~6 edit sites, and — the real problem — -// IDEAS CANNOT COMBINE: one archetype owns the whole voxel. +// Each archetype (ECaveGeneratorType) is a composition of operators, not a monolithic density +// function that owns the whole voxel. A world idea is expressed by stacking sources, combiners and +// modifiers — so ideas can COMBINE. // // ───────────────────────────────────────────────────────────────────────────────────────── // ⚠️ CE N'EST PAS L'ANCIEN SYSTÈME DE « ROOM OPERATIONS » / THIS IS NOT THE OLD ROOM-OPS SYSTEM // ───────────────────────────────────────────────────────────────────────────────────────── // UVoxelTerrainOpDefinition (Terrace, Ribbing, Cliff, Scallop, Overhang, Arch, Column, Pit…) ne // sait que PERTURBER une densité près d'une surface qui existe déjà. Il ne décide jamais ce que le -// champ EST — cette décision vit dans le `switch`. Une pile d'opérateurs qui se contenterait de -// cela aurait reconstruit le switch avec des étapes en plus. +// champ EST — c'est le rôle des sources de champ ci-dessous. // // D'où QUATRE RÔLES, dont l'ancien système n'occupait que le troisième : // // 1. FIELD SOURCE — fabrique un champ À PARTIR DE RIEN. C'est ce qui fait qu'une grotte est // une grotte et qu'un monde ouvert est un monde ouvert. Chaque archétype -// d'aujourd'hui est fondamentalement l'une de ces sources. +// est fondamentalement l'une de ces sources. // 2. COMBINER — comment deux champs fusionnent (min/max/smooth/mask). C'EST le rôle qui // achète la composition ; sans lui il n'y a pas de refactor. // 3. DETAIL MODIFIER — l'ancien système, rétrogradé à un rôle sur quatre. Inchangé. @@ -56,9 +54,8 @@ // An SDF-only writer reports an SDF interval; a later converter folds that interval through its own // response. Each operator answers for itself, so an unverified composition remains sound. // -// La première version de cette interface n'avait qu'une direction numérique. Presque tout opérateur -// existant est UNIDIRECTIONNEL : il ne fait que creuser, ou que remplir. Cela suffit à reproduire -// GÉNÉRIQUEMENT chaque garde écrite à la main dans ClassifyTile : +// Presque tout opérateur est UNIDIRECTIONNEL : il ne fait que creuser, ou que remplir. La direction +// suffit donc à exprimer GÉNÉRIQUEMENT les gardes de ClassifyTile : // // « passages ⇒ bCanSolid = false » EST CarveOnly // « ponts/arêtes ⇒ bCanAir = false » EST FillOnly @@ -71,26 +68,16 @@ // it never lets an SDF writer impersonate a downstream neighbour. // // ───────────────────────────────────────────────────────────────────────────────────────── -// LE GROS LOT PERF : les strates de grotte ne sautent AUCUNE tuile aujourd'hui -// THE PERF PRIZE: cave strates skip ZERO tiles today +// CLASSIFYTILE // ───────────────────────────────────────────────────────────────────────────────────────── -// ClassifyTile conserve ses preuves exactes pour les gaps de bedrock et SurfaceWorld ; les -// archétypes de cave opt-in passent maintenant par le même pliage `ClassifyBox`. Le post XY fournit -// en plus la première preuve globale de coque : une tuile entièrement dans la bande forcée peut être +// ClassifyTile garde ses preuves exactes pour les gaps de bedrock et SurfaceWorld ; les slots de +// cave passent par le pliage `ClassifyBox` de la pile même qui produit leur densité. Le post XY +// fournit en plus une preuve globale de coque : une tuile entièrement dans la bande forcée peut être // sautée sans connaître l'archétype ou le Z. // -// **Traiter cela comme un livrable explicite de chaque portage, pas comme un effet de bord.** -// -// ───────────────────────────────────────────────────────────────────────────────────────── -// ÉTAT / STATUS -// ───────────────────────────────────────────────────────────────────────────────────────── -// Les huit archétypes ont maintenant une pile derrière l'opt-in `bUseOperatorStack`, et ClassifyTile -// utilise cette pile pour les slots de cave opt-in. Le chemin legacy reste disponible pour les -// strates non opt-in; les tests d'équivalence servent de garde de portage. -// -// All eight archetypes now have an operator-stack path behind `bUseOperatorStack`, and ClassifyTile -// uses that stack for opt-in cave slots. The legacy switch remains available for opt-out strates; -// equivalence tests are port-correctness guards. +// ClassifyTile keeps its exact proofs for bedrock gaps and SurfaceWorld; cave slots go through the +// `ClassifyBox` fold of the same stack that produces their density. The XY post adds a global +// shell proof: a tile entirely inside the forced band is skipped without knowing archetype or Z. // // NOTE sur les UENUM : ces types sont volontairement du C++ nu (pas d'UHT, pas de .generated.h). // Ils deviendront UENUM/USTRUCT en Phase 3, quand les opérateurs deviendront des data assets et diff --git a/Source/VoxelForge/Public/VoxelDensityOpStack.h b/Source/VoxelForge/Public/VoxelDensityOpStack.h index 240e956..de48e5d 100644 --- a/Source/VoxelForge/Public/VoxelDensityOpStack.h +++ b/Source/VoxelForge/Public/VoxelDensityOpStack.h @@ -2,33 +2,21 @@ // La PILE : un conteneur ordonné d'opérateurs, plus les fabriques d'opérateurs concrets. // The STACK: an ordered container of operators, plus the concrete-operator factories. // -// ⚠️ CECI ALIMENTE LE JEU, MAIS SEULEMENT SUR OPT-IN. -// `UVoxelGenerator::GetDensityAt` construit la pile par chunk et l'évalue à la place du `switch` -// UNIQUEMENT quand `UVoxelStrateManager::UsesOperatorStackForChunk` rend true — c.-à-d. quand la -// strate a coché `bUseOperatorStack` ET que son archétype figure dans la liste des portés : +// ⚠️ CECI EST LE CHEMIN DE GÉNÉRATION DU JEU. +// `UVoxelGenerator::GetDensityAt` construit la pile par chunk pour chaque strate : // **Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, -// Underwater (8 sur 8)**. -// Toute autre strate passe encore par le `switch`, inchangé. -// `ClassifyTile` est branché pour les archétypes de cave opt-in : il construit la même pile et -// plie `ClassifyBox`; les gaps et SurfaceWorld gardent leurs preuves exactes dédiées. +// Underwater (8 sur 8)**. `ClassifyTile` construit la même pile pour les archétypes de cave et plie +// `ClassifyBox`; les gaps et SurfaceWorld gardent leurs preuves exactes dédiées. // -// THIS FEEDS THE GAME, BUT ONLY BEHIND AN OPT-IN. GetDensityAt builds the stack per chunk and -// evaluates it instead of the switch only when UsesOperatorStackForChunk returns true (strate ticked -// bUseOperatorStack AND its archetype is ported — all 8). ClassifyTile uses the same stack for cave -// archetypes; gaps and SurfaceWorld retain their exact hand-written proofs. -// -// ⛔ NE JAMAIS faire tourner les deux chemins dans le même monde. -// ⚠️ EN REVANCHE, LES COMPARER EST LÉGITIME. `AUDIT §C10` (le résidu ~1 ULP) est clos par -// `FPSemantics = Precise` : les cinq tests d'équivalence comparent bit à bit. Ce ne sont pas des -// contrôles de FIDÉLITÉ (la barre `§2.6.1` n'exige aucune ressemblance avec l'ancien monde) mais -// des oracles de CORRECTION DE PORTAGE — une faute de transcription reste un vrai bug, et l'ancienne fonction est -// le moyen le moins cher de l'attraper. +// THIS IS THE GAME'S GENERATION PATH. GetDensityAt builds the stack per chunk for every strate. +// ClassifyTile uses the same stack for cave archetypes; gaps and SurfaceWorld retain their exact +// hand-written proofs. // // POURQUOI CETTE FORME / WHY THIS SHAPE // La question à laquelle cette forme répond n'est pas « est-ce que ça marche ? » mais // **« est-ce que la séparation source / modifier tombe naturellement du code existant ? »** -// (OPSTACK-PLAN §4). Chaque archétype porté est comparé bit à bit à l'original : la réponse est -// MESURÉE plutôt qu'une opinion. +// (OPSTACK-PLAN §4). Chaque archétype a été porté puis comparé bit à bit à sa fonction +// d'origine avant qu'elle ne soit retirée : la réponse a été MESURÉE plutôt qu'une opinion. #pragma once diff --git a/Source/VoxelForge/Public/VoxelGenerator.h b/Source/VoxelForge/Public/VoxelGenerator.h index 350b9ed..c2baf04 100644 --- a/Source/VoxelForge/Public/VoxelGenerator.h +++ b/Source/VoxelForge/Public/VoxelGenerator.h @@ -414,7 +414,7 @@ public: * * GAP (bedrock), hors-layout (air constant) et SurfaceWorld plient leurs hypothèses par Z ; * SurfaceWorld évalue ses colonnes terrain/plafond avec le MÊME ComputeSurfaceColumn que le - * chemin densité. Un unique slot cave opt-in peut ajouter le verdict conservatif de sa pile, + * chemin densité. Un unique slot cave peut ajouter le verdict conservatif de sa pile, * sous gardes d'archétype et de params bit-identiques. Spine/passages/disturbances/diff restent * des gardes conservatrices. Worker-safe (lecture seule + caches thread_local partagés avec * GetDensityAt — un verdict Mixed laisse les colonnes chaudes pour la génération qui suit). diff --git a/Source/VoxelForge/Public/VoxelStats.h b/Source/VoxelForge/Public/VoxelStats.h index 170aaa6..9cc9d81 100644 --- a/Source/VoxelForge/Public/VoxelStats.h +++ b/Source/VoxelForge/Public/VoxelStats.h @@ -14,10 +14,6 @@ DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Skipped All Air"), STAT_VoxelForge DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Meshed"), STAT_VoxelForgeTilesMeshed, STATGROUP_VoxelForge, VOXELFORGE_API); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Solid"), STAT_VoxelForgeTilesOpStackSolid, STATGROUP_VoxelForge, VOXELFORGE_API); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Air"), STAT_VoxelForgeTilesOpStackAir, STATGROUP_VoxelForge, VOXELFORGE_API); -DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Sole Slot"), STAT_VoxelForgeCaveBailNotOpStackSoleSlot, STATGROUP_VoxelForge, VOXELFORGE_API); -DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Boundary Tile"), STAT_VoxelForgeCaveBailNotOpStackBoundaryTile, STATGROUP_VoxelForge, VOXELFORGE_API); -DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack No Layout"), STAT_VoxelForgeCaveBailNotOpStackNoLayout, STATGROUP_VoxelForge, VOXELFORGE_API); -DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Recheck"), STAT_VoxelForgeCaveBailNotOpStackRecheck, STATGROUP_VoxelForge, VOXELFORGE_API); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Out Of Layout"), STAT_VoxelForgeCaveMixOutOfLayout, STATGROUP_VoxelForge, VOXELFORGE_API); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Gap"), STAT_VoxelForgeCaveMixGap, STATGROUP_VoxelForge, VOXELFORGE_API); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Surface World"), STAT_VoxelForgeCaveMixSurfaceWorld, STATGROUP_VoxelForge, VOXELFORGE_API); diff --git a/Source/VoxelForge/Public/VoxelStrateDefinition.h b/Source/VoxelForge/Public/VoxelStrateDefinition.h index 26c7d1c..e0714e6 100644 --- a/Source/VoxelForge/Public/VoxelStrateDefinition.h +++ b/Source/VoxelForge/Public/VoxelStrateDefinition.h @@ -115,29 +115,6 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation") ECaveGeneratorType GeneratorType = ECaveGeneratorType::TunnelNetwork; - /** - * OPERATOR STACK (experimental) — generate this strate through the composable density operator - * stack instead of the hardcoded archetype switch. Same world, different machinery. - * - * This is the A/B switch for `OPSTACK-PLAN §2.6`'s acceptance bar: flip it, regenerate, and - * judge on a screenshot that it is recognisably the same place. Both systems coexist - * indefinitely — the switch is not going away until every archetype is ported. - * - * ⚠️ ONLY `Maze` IS PORTED SO FAR. On any other GeneratorType this flag is ignored and the - * switch runs as before, so setting it is harmless but does nothing yet. - * - * ⚠️ Do NOT flip this on a strate mid-session and expect the old and new geometry to agree to - * the bit — they differ by ~1-2 ULP with ZERO isosurface crossings, so the shape is identical - * but the floats are not (`Docs/archive/AUDIT-2026-07.md §C10`). Regenerate the world after changing it - * rather than letting old and new tiles sit side by side. - * - * Pile d'opérateurs (expérimental) : génère cette strate via la pile composable au lieu du - * `switch` d'archétype. Seul `Maze` est porté ; ailleurs le drapeau est ignoré. - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation", - meta = (DisplayName = "Use Operator Stack (experimental)")) - bool bUseOperatorStack = false; - /** * Authoritative per-strate worm switch. Existing assets default to enabled. The manager * resolves a disabled strate to WormStrength == 0 before any fused, stack, composer, or diff --git a/Source/VoxelForge/Public/VoxelStrateManager.h b/Source/VoxelForge/Public/VoxelStrateManager.h index 6c28f86..ed56bbb 100644 --- a/Source/VoxelForge/Public/VoxelStrateManager.h +++ b/Source/VoxelForge/Public/VoxelStrateManager.h @@ -294,7 +294,7 @@ public: * * Returns TunnelNetwork if the chunk is outside all strates or if the * strate definition is null. The generator uses this to pick which - * density function to call (GetDensityWithParams vs GetSlabDensity). + * operator stack to build for the chunk. * * @param ChunkCoord - The chunk position * @return The ECaveGeneratorType for the strate containing this chunk @@ -302,12 +302,9 @@ public: ECaveGeneratorType GetGeneratorTypeForChunk(const FIntVector& ChunkCoord) const; /** - * True when this chunk's strate opts into the density OPERATOR STACK instead of the hardcoded - * archetype switch (`UVoxelStrateDefinition::bUseOperatorStack`). - * - * Returns false for archetypes that have no port yet, so the flag can be set on any strate - * without changing its output until that archetype lands. Only `Maze` is ported today — this - * predicate is where that list grows, and it is deliberately the ONLY place it is written down. + * True when this chunk lies in a strate slot whose archetype has an operator stack (all eight + * do). False outside the layout or for a slot without a definition. This predicate is the ONLY + * place the archetype list is written down. */ bool UsesOperatorStackForChunk(const FIntVector& ChunkCoord) const; diff --git a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp index 9774045..d96c435 100644 --- a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp +++ b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp @@ -314,7 +314,6 @@ struct FExploreArguments bool bRender = true; bool bWalk = true; bool bExport = true; - bool bUseOperatorStack = true; bool bWormsEnabledOverride = false; bool bWormsEnabled = true; bool bWormStrengthOverride = false; @@ -431,9 +430,6 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr } ModesText.TrimQuotesInline(); } - int32 UseOperatorStack = 1; - FParse::Value(*Params, TEXT("opstack="), UseOperatorStack); - OutArguments.bUseOperatorStack = UseOperatorStack != 0; int32 WormsEnabled = 1; if (FParse::Value(*Params, TEXT("wormsenabled="), WormsEnabled)) { @@ -1218,9 +1214,6 @@ struct FExploreWorld Definition->GeneratorType = Index == Arguments.Slot ? Arguments.Archetype : ECaveGeneratorType::TunnelNetwork; - // Explicitly select the production switch so the same synthetic layout, seed and - // mesher invocation can be measured through both density branches. - Definition->bUseOperatorStack = Arguments.bUseOperatorStack; if (Arguments.bWormsEnabledOverride) { Definition->bEnableWorms = Arguments.bWormsEnabled; @@ -1376,7 +1369,7 @@ struct FExploreWorld const FStrateGenerationParams& ReportParams = Target.Definition->GenerationParams; UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore][OwnerAssets] strate=%s settings=%s slot=%d " - "seed=%d height_chunks=%d archetype=%s opstack=%d " + "seed=%d height_chunks=%d archetype=%s " "floor_switch_ignored=1 floor_terracing=%d floor_step=%.9g max_ledge=%.9g " "gentle_slope_degrees=%.9g gentle_gradient=%.9g route_cap=%.9g " "wind_wave_cap=%d " @@ -1385,7 +1378,7 @@ struct FExploreWorld bUseAuthoredStrate ? *Arguments.StrateReference : TEXT("synthetic"), bUseAuthoredSettings ? *Arguments.SettingsReference : TEXT("synthetic"), Arguments.Slot, Settings->Seed, Target.Definition->StrateHeightInChunks, - ArchetypeName(TargetArchetype), Target.Definition->bUseOperatorStack ? 1 : 0, + ArchetypeName(TargetArchetype), ReportParams.bTunnelFloorTerracingEnabled ? 1 : 0, ReportParams.TunnelFloorTerraceStepHeight, ReportParams.TunnelFloorMaxLedgeHeight, @@ -4453,7 +4446,6 @@ FString BuildManifestJson( Writer->WriteValue(TEXT("seed"), Arguments.Seed); Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype)); Writer->WriteValue(TEXT("slot"), Arguments.Slot); - Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack); Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride); if (Arguments.bWormsEnabledOverride) { @@ -6135,7 +6127,6 @@ FString BuildExploreJson( Writer->WriteValue(TEXT("seed"), Arguments.Seed); Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype)); Writer->WriteValue(TEXT("slot"), Arguments.Slot); - Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack); Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride); if (Arguments.bWormsEnabledOverride) { @@ -6216,12 +6207,11 @@ FString BuildExploreJson( Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory); Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf( TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s " - "-slot=%d -opstack=%d -modes=%s -blockearlyout=%d " + "-slot=%d -modes=%s -blockearlyout=%d " "-out="), Arguments.Seed, ArchetypeName(Arguments.Archetype), Arguments.Slot, - Arguments.bUseOperatorStack ? 1 : 0, *Arguments.CanonicalModes(), Arguments.bBlockEarlyOut ? 1 : 0)); Writer->WriteObjectEnd(); @@ -7203,7 +7193,6 @@ bool WriteExploreOpBoundsReport( Writer->WriteValue(TEXT("seed"), Arguments.Seed); Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype)); Writer->WriteValue(TEXT("slot"), Arguments.Slot); - Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack); Writer->WriteValue(TEXT("sample_step"), 1); Writer->WriteValue(TEXT("block_cells"), 8); Writer->WriteValue(TEXT("classifier_blocks_tested"), BlocksTested); @@ -7311,10 +7300,9 @@ bool RunOpBounds( FExploreBudget& Budget, FString& OutError) { - if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork - || !Arguments.bUseOperatorStack) + if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork) { - OutError = TEXT("opbounds requires archetype=TunnelNetwork and opstack=1."); + OutError = TEXT("opbounds requires archetype=TunnelNetwork."); return false; } @@ -8262,8 +8250,6 @@ bool BuildBatchCaseParams( FString Modes = TEXT("export"); JsonString(Case, TEXT("modes"), Modes); - bool bOperatorStack = true; - JsonBool(Case, TEXT("operator_stack"), bOperatorStack); bool bProfileDensity = false; JsonBool(Case, TEXT("profile_density"), bProfileDensity); bool bProfileDensityFull = false; @@ -8293,11 +8279,11 @@ bool BuildBatchCaseParams( Case, TEXT("tunnel_floor_terracing"), bTunnelFloorTerracing); OutParams = FString::Printf( - TEXT("-seed=%d -archetype=%s -slot=%d -modes=%s -opstack=%d " + TEXT("-seed=%d -archetype=%s -slot=%d -modes=%s " "-exportsize=%d -exportstep=%d -densitygridreuse=%d " "-blockearlyout=%d -meshminbatch=%d " "-failurefocus=%d -out=\"%s\""), - Seed, *Archetype, Slot, *Modes, bOperatorStack ? 1 : 0, + Seed, *Archetype, Slot, *Modes, ExportSize, ExportStep, bReuseDensityGrid ? 1 : 0, bBlockEarlyOut ? 1 : 0, MeshMinBatchSize, bFailureFocus ? 1 : 0, *CaseOutDirectory); diff --git a/Tools/VoxelForgeTest.ps1 b/Tools/VoxelForgeTest.ps1 index 9936dd2..e53d2d6 100644 --- a/Tools/VoxelForgeTest.ps1 +++ b/Tools/VoxelForgeTest.ps1 @@ -633,7 +633,6 @@ function New-CommandletArguments([string]$CommandletOut, [string]$Modes, [bool]$ [void]$args.Add('-seed=0') [void]$args.Add('-archetype=TunnelNetwork') [void]$args.Add("-slot=$Slot") - [void]$args.Add('-opstack=1') [void]$args.Add(('-modes="{0}"' -f $Modes)) [void]$args.Add('-blockearlyout=0') [void]$args.Add('-densitygridreuse=1')