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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URDGLsu7c4nunqNFPJEFPU
This commit is contained in:
29 files changed
+249
-654
No files matched your search
@@ -39,12 +39,8 @@
|
|||||||
#include "Misc/AutomationTest.h"
|
#include "Misc/AutomationTest.h"
|
||||||
|
|
||||||
#include "VoxelForgeTestFixture.h"
|
#include "VoxelForgeTestFixture.h"
|
||||||
// Inclus ici DÉLIBÉRÉMENT : VoxelDensityOp.h n'est encore inclus par aucun .cpp, donc le
|
// Le fold défini par VoxelDensityOp.h prétend reproduire ClassifyTile : ce fichier le teste.
|
||||||
// compilateur ne le verrait jamais. Le fold qu'il définit prétend reproduire ClassifyTile — ce
|
// The fold defined in VoxelDensityOp.h claims to reproduce ClassifyTile; this file tests it.
|
||||||
// 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.
|
|
||||||
#include "VoxelDensityOp.h"
|
#include "VoxelDensityOp.h"
|
||||||
|
|
||||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||||
@@ -153,7 +149,7 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
|
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT("ClassifyTile verdicts over %d scanned tiles: Mixed %d, AllSolid %d, AllAir %d ")
|
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("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."),
|
TEXT("non-Mixed count is expected: every uncertain case remains Mixed for safety."),
|
||||||
NumTilesScanned, NumMixed, NumAllSolid, NumAllAir, ToVerify.Num()));
|
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
|
// Pour un slot de CAVE, `ClassifyTile` consulte `FVoxelOpStack::ClassifyBox` — donc **la pile peut
|
||||||
// `FVoxelOpStack::ClassifyBox` quand la strate a coché `bUseOperatorStack` — donc **un tout nouveau
|
// faire sauter le maillage d'une tuile**, et son erreur est un TROU : pas de triangles, pas de
|
||||||
// chemin peut faire sauter le maillage d'une tuile**, et son erreur est un TROU : pas de triangles,
|
// collision, invisible jusqu'à ce qu'un joueur tombe au travers.
|
||||||
// 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
|
// Ce test est le même oracle par force brute que `ClassifyTileSoundness`, avec un tirage uniforme
|
||||||
// les strates ont coché la case. Il ne vérifie pas le pliage (c'est `BoxVerdictFold`) ni les
|
// sur tout le layout. Il ne vérifie pas le pliage (c'est `BoxVerdictFold`) ni les opérateurs : il
|
||||||
// opérateurs (ce sont les huit tests d'équivalence) : il vérifie le **câblage** — que la pile
|
// vérifie le **câblage** — que la pile interrogée par le classifieur est bien celle qui produit la
|
||||||
// interrogée par le classifieur est bien celle qui produit la densité, params, drapeau et
|
// densité, params et disturbances compris.
|
||||||
// disturbances compris.
|
|
||||||
//
|
//
|
||||||
// ⚠️ LE COMPTEUR À LIRE EN PREMIER est le nombre de tuiles réellement brute-forcées. Un run vert
|
// ⚠️ 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
|
// 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.
|
// 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.
|
// Same brute-force oracle as ClassifyTileSoundness, drawn uniformly over the whole layout so cave
|
||||||
// It checks the WIRING, not the fold and not the operators.
|
// slots (which classify through their stack) dominate. It checks the WIRING, not the fold and not
|
||||||
|
// the operators.
|
||||||
|
|
||||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||||
FVoxelForgeOpStackClassifyTileTest,
|
FVoxelForgeOpStackClassifyTileTest,
|
||||||
@@ -266,11 +261,10 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
{
|
{
|
||||||
using namespace VoxelForgeTest;
|
using namespace VoxelForgeTest;
|
||||||
|
|
||||||
// ⚠️ `bUseOperatorStack = true` sur toutes les strates : c'est LE point du test. La fixture
|
// La fixture donne à ce monde une `LayoutVersion` unique dans le processus, sans quoi les caches
|
||||||
// donne à ce monde une `LayoutVersion` unique dans le processus, sans quoi les caches par chunk
|
// par chunk de `GetDensityAt` — dont `CP_UseOpStack` — pourraient encore porter ceux d'un autre test.
|
||||||
// de `GetDensityAt` — dont `CP_UseOpStack` — pourraient encore porter ceux d'un autre test.
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
World.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
@@ -287,8 +281,8 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
const int32 BottomVoxelZ = World.BottomChunkZ * CHUNK_SIZE;
|
const int32 BottomVoxelZ = World.BottomChunkZ * CHUNK_SIZE;
|
||||||
|
|
||||||
// Tirage uniforme sur tout le layout, PAS biaisé vers SurfaceWorld comme l'autre test : ici ce
|
// 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
|
// sont précisément les strates de cave qui intéressent, puisque ce sont elles que ClassifyTile
|
||||||
// nouveau chemin. SurfaceWorld continue d'être prouvé par le code écrit à la main.
|
// juge par leur pile. SurfaceWorld est prouvé par le test colonne écrit à la main.
|
||||||
for (int32 t = 0; t < NumTilesScanned; ++t)
|
for (int32 t = 0; t < NumTilesScanned; ++t)
|
||||||
{
|
{
|
||||||
FTileSpec Spec;
|
FTileSpec Spec;
|
||||||
@@ -316,20 +310,17 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
|
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT("ClassifyTile ON THE OPERATOR-STACK PATH, %d scanned tiles: Mixed %d, AllSolid %d, ")
|
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("AllAir %d (brute-forcing %d). Every cave-slot verdict is a tile the mesher skips on ")
|
||||||
TEXT("which runs the SAME scan on a world that has NOT opted in: every verdict beyond what ")
|
TEXT("the strength of the stack's ClassifyBox fold -- and a hole if the wiring is wrong, ")
|
||||||
TEXT("that test reports is a tile the mesher now skips and did not before. That difference ")
|
TEXT("which is what the brute force below is for."),
|
||||||
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."),
|
|
||||||
NumTilesScanned, NumMixed, NumAllSolid, NumAllAir, ToVerify.Num()));
|
NumTilesScanned, NumMixed, NumAllSolid, NumAllAir, ToVerify.Num()));
|
||||||
|
|
||||||
if (ToVerify.Num() == 0)
|
if (ToVerify.Num() == 0)
|
||||||
{
|
{
|
||||||
AddError(TEXT("VACUOUS: not one tile got a non-Mixed verdict on the operator-stack path, so ")
|
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("this test verified NOTHING about the wiring. Every guard in the cave branch ")
|
||||||
TEXT("opted in (check FTestWorld::Build's bUseOperatorStack), or every guard in the ")
|
TEXT("of ClassifyTile bailed to Mixed -- the params-identical check and the ")
|
||||||
TEXT("cave branch of ClassifyTile bailed to Mixed -- the params-identical check and ")
|
TEXT("27-chunk-coord cap are the likeliest. Do NOT read a green run as proof."));
|
||||||
TEXT("the 27-chunk-coord cap are the likeliest. Do NOT read a green run as proof."));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ bool FVoxelForgeComposerCorpusFreeTest::RunTest(const FString& Parameters)
|
|||||||
Corpus.GetEntries().Num(), 12);
|
Corpus.GetEntries().Num(), 12);
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, true);
|
World.Build(1337, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -955,7 +955,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, true);
|
World.Build(1337, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
@@ -1117,7 +1117,6 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
CandidateDefinition->SurfaceParams = Roll.ArchetypeParams.SurfaceParams;
|
CandidateDefinition->SurfaceParams = Roll.ArchetypeParams.SurfaceParams;
|
||||||
CandidateDefinition->VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams;
|
CandidateDefinition->VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams;
|
||||||
CandidateDefinition->FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams;
|
CandidateDefinition->FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams;
|
||||||
CandidateDefinition->bUseOperatorStack = true;
|
|
||||||
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
|
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
|
||||||
World.Reinitialize();
|
World.Reinitialize();
|
||||||
|
|
||||||
@@ -1342,15 +1341,13 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
|||||||
constexpr int32 ExpectedLatticeSamples = 16 * 16 * 16;
|
constexpr int32 ExpectedLatticeSamples = 16 * 16 * 16;
|
||||||
FString Table = TEXT(
|
FString Table = TEXT(
|
||||||
"TERRAIN DETAIL LIVENESS (same 4096-point lattice; only one group changed per row)\n"
|
"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");
|
"changed | sign changes | sum abs delta | max abs delta | verdict\n");
|
||||||
int32 TotalRows = 0;
|
int32 TotalRows = 0;
|
||||||
int32 FailedRows = 0;
|
int32 FailedRows = 0;
|
||||||
|
|
||||||
for (const bool bUseOperatorStack : { false, true })
|
|
||||||
{
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, bUseOperatorStack);
|
World.Build(1337, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
@@ -1365,17 +1362,11 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Definition->TerrainOperations.Reset();
|
Definition->TerrainOperations.Reset();
|
||||||
TestEqual(
|
TestEqual(TEXT("fixture has no terrain-op asset pool"), Definition->TerrainOperations.Num(), 0);
|
||||||
FString::Printf(TEXT("%s fixture has no terrain-op asset pool"),
|
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")),
|
|
||||||
Definition->TerrainOperations.Num(), 0);
|
|
||||||
|
|
||||||
TArray<FVector> Points;
|
TArray<FVector> Points;
|
||||||
VF_BuildTerrainDetailLattice(World, Points);
|
VF_BuildTerrainDetailLattice(World, Points);
|
||||||
TestEqual(
|
TestEqual(TEXT("experiment uses the fixed lattice"), Points.Num(), ExpectedLatticeSamples);
|
||||||
FString::Printf(TEXT("%s experiment uses the fixed lattice"),
|
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")),
|
|
||||||
Points.Num(), ExpectedLatticeSamples);
|
|
||||||
|
|
||||||
FStrateGenerationParams BaselineParams = Definition->GenerationParams;
|
FStrateGenerationParams BaselineParams = Definition->GenerationParams;
|
||||||
VF_ClearTerrainDetailFields(BaselineParams);
|
VF_ClearTerrainDetailFields(BaselineParams);
|
||||||
@@ -1403,9 +1394,7 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
|||||||
bSameBaseline = BitEqual(FirstBaseline[SampleIndex], BaselineSamples[SampleIndex]);
|
bSameBaseline = BitEqual(FirstBaseline[SampleIndex], BaselineSamples[SampleIndex]);
|
||||||
}
|
}
|
||||||
TestTrue(
|
TestTrue(
|
||||||
FString::Printf(TEXT("%s baseline is stable before %s probe"),
|
FString::Printf(TEXT("baseline is stable before %s probe"), Spec.Name),
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
|
||||||
Spec.Name),
|
|
||||||
bSameBaseline);
|
bSameBaseline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1428,14 +1417,11 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
|||||||
++FailedRows;
|
++FailedRows;
|
||||||
}
|
}
|
||||||
TestTrue(
|
TestTrue(
|
||||||
FString::Printf(TEXT("%s %s field group has the measured liveness verdict"),
|
FString::Printf(TEXT("%s field group has the measured liveness verdict"), Spec.Name),
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
|
||||||
Spec.Name),
|
|
||||||
bVerdictMatches);
|
bVerdictMatches);
|
||||||
|
|
||||||
Table += FString::Printf(
|
Table += FString::Printf(
|
||||||
TEXT("%s | %s | %d | 0x%08x | 0x%08x | %.9g | %.9g | %d | %d | %.9g | %.9g | %s\n"),
|
TEXT("%s | %d | 0x%08x | 0x%08x | %.9g | %.9g | %d | %d | %.9g | %.9g | %s\n"),
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
|
||||||
Spec.Name,
|
Spec.Name,
|
||||||
Points.Num(),
|
Points.Num(),
|
||||||
BaselineSummary.Digest,
|
BaselineSummary.Digest,
|
||||||
@@ -1448,17 +1434,16 @@ bool FVoxelForgeTerrainDetailLivenessTest::RunTest(const FString& Parameters)
|
|||||||
Delta.MaxAbs,
|
Delta.MaxAbs,
|
||||||
bObservedLive ? TEXT("LIVE") : TEXT("DEAD"));
|
bObservedLive ? TEXT("LIVE") : TEXT("DEAD"));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
AddInfo(Table);
|
AddInfo(Table);
|
||||||
AddInfo(FString::Printf(
|
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 "
|
"must have at least one bit-changed density and DEAD groups must have none. No "
|
||||||
"terrain-operation assets were present in the fixture pool."),
|
"terrain-operation assets were present in the fixture pool."),
|
||||||
TotalRows, FailedRows));
|
TotalRows, FailedRows));
|
||||||
TestEqual(TEXT("all terrain detail liveness rows matched their measured verdict"), FailedRows, 0);
|
TestEqual(TEXT("all terrain detail liveness rows matched their measured verdict"), FailedRows, 0);
|
||||||
TestEqual(TEXT("both density paths measured every terrain detail group"), TotalRows,
|
TestEqual(TEXT("every terrain detail group was measured"), TotalRows,
|
||||||
static_cast<int32>(2 * UE_ARRAY_COUNT(GTerrainDetailProbeSpecs)));
|
static_cast<int32>(UE_ARRAY_COUNT(GTerrainDetailProbeSpecs)));
|
||||||
return FailedRows == 0;
|
return FailedRows == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ bool FVoxelForgeComposerPromotionTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, true);
|
World.Build(1337, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -1590,7 +1590,7 @@ bool FVoxelForgeComposerShowcaseTest::RunTest(const FString& Parameters)
|
|||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
// The shared fixture defaults to a bounded 4-chunk volume for fast density tests. The
|
// 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.
|
// 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())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ bool FVoxelForgeComposerStructureRollTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, true);
|
World.Build(1337, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|||||||
bool FVoxelForgeTileCacheContextDeterminismTest::RunTest(const FString& Parameters)
|
bool FVoxelForgeTileCacheContextDeterminismTest::RunTest(const FString& Parameters)
|
||||||
{
|
{
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
World.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
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.
|
// level-0 tile at the same origin is compared with a fresh world's level-0 output.
|
||||||
FTestWorld WarmWorld;
|
FTestWorld WarmWorld;
|
||||||
FTestWorld FreshWorld;
|
FTestWorld FreshWorld;
|
||||||
WarmWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
WarmWorld.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
FreshWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
FreshWorld.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
if (!WarmWorld.IsValid() || !FreshWorld.IsValid())
|
if (!WarmWorld.IsValid() || !FreshWorld.IsValid())
|
||||||
{
|
{
|
||||||
AddError(WarmWorld.IsValid() && FreshWorld.IsValid()
|
AddError(WarmWorld.IsValid() && FreshWorld.IsValid()
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ bool FVoxelForgeDensityBlockEquivalenceTest::RunTest(const FString& Parameters)
|
|||||||
for (int32 SeedIndex = 0; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex)
|
for (int32 SeedIndex = 0; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex)
|
||||||
{
|
{
|
||||||
VoxelForgeTest::FTestWorld World;
|
VoxelForgeTest::FTestWorld World;
|
||||||
World.Build(Seeds[SeedIndex], /*gap chunks=*/2, /*operator stack=*/true);
|
World.Build(Seeds[SeedIndex], /*gap chunks=*/2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
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
|
// 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
|
// 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
|
// active operator list. The six other archetypes run the scalar stack evaluator, as they
|
||||||
// evaluator, as they do outside the block-safe network branch.
|
// do in production outside the block-safe network branch.
|
||||||
for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex)
|
for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex)
|
||||||
{
|
{
|
||||||
BaseResults[GridIndex] = CompareGeneratorBlockWithScalar(
|
BaseResults[GridIndex] = CompareGeneratorBlockWithScalar(
|
||||||
|
|||||||
@@ -761,14 +761,13 @@ namespace
|
|||||||
const float SimulatedCarvedFinalDensity = -SimulatedCarvedInternalDensity;
|
const float SimulatedCarvedFinalDensity = -SimulatedCarvedInternalDensity;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeDescentWriterContext] seed=0 sample=(%.3f,%.3f,%.3f) "
|
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 "
|
"passage_sdf=%.6f simulated_passage_carve_density=%.6f "
|
||||||
"terrain_ops='%s' terrain_op_paths='%s' "
|
"terrain_ops='%s' terrain_op_paths='%s' "
|
||||||
"base=%.3f tunnel_columns=(%.3f,%.3f,%.3f) slab_columns=(%.3f,%.3f,%.3f)"),
|
"base=%.3f tunnel_columns=(%.3f,%.3f,%.3f) slab_columns=(%.3f,%.3f,%.3f)"),
|
||||||
OutSamples.FirstSolidPosition.X, OutSamples.FirstSolidPosition.Y,
|
OutSamples.FirstSolidPosition.X, OutSamples.FirstSolidPosition.Y,
|
||||||
OutSamples.FirstSolidPosition.Z, ChunkCoord.X, ChunkCoord.Y, ChunkCoord.Z,
|
OutSamples.FirstSolidPosition.Z, ChunkCoord.X, ChunkCoord.Y, ChunkCoord.Z,
|
||||||
*GeneratorName, Definition != nullptr && Definition->bUseOperatorStack ? 1 : 0,
|
*GeneratorName, bUsesOperatorStack ? 1 : 0, PassageSDF, SimulatedCarvedFinalDensity,
|
||||||
bUsesOperatorStack ? 1 : 0, PassageSDF, SimulatedCarvedFinalDensity,
|
|
||||||
*TerrainOps, *TerrainOpPaths, Params.BaseDensity, Params.ColumnDensity,
|
*TerrainOps, *TerrainOpPaths, Params.BaseDensity, Params.ColumnDensity,
|
||||||
Params.ColumnMinRadius, Params.ColumnMaxRadius,
|
Params.ColumnMinRadius, Params.ColumnMaxRadius,
|
||||||
Definition != nullptr ? Definition->SlabParams.ColumnDensity : -1.0f,
|
Definition != nullptr ? Definition->SlabParams.ColumnDensity : -1.0f,
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(Seed, 2, false);
|
World.Build(Seed, 2);
|
||||||
int32 TopWorldZ = 0;
|
int32 TopWorldZ = 0;
|
||||||
int32 BottomWorldZ = 0;
|
int32 BottomWorldZ = 0;
|
||||||
FVoxelStrateRegionManifest Manifest;
|
FVoxelStrateRegionManifest Manifest;
|
||||||
@@ -612,7 +612,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(Seed, 2, false);
|
World.Build(Seed, 2);
|
||||||
int32 TopWorldZ = 0;
|
int32 TopWorldZ = 0;
|
||||||
int32 BottomWorldZ = 0;
|
int32 BottomWorldZ = 0;
|
||||||
FVoxelStrateRegionManifest Manifest;
|
FVoxelStrateRegionManifest Manifest;
|
||||||
@@ -661,7 +661,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters)
|
|||||||
TestTrue(TEXT("found a single-region recipe identity case"), bRecipeIdentityChecked);
|
TestTrue(TEXT("found a single-region recipe identity case"), bRecipeIdentityChecked);
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(MultiSeed, 2, false);
|
World.Build(MultiSeed, 2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
@@ -955,7 +955,7 @@ bool FVoxelForgeLateralRegionsTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld LawWorld;
|
FTestWorld LawWorld;
|
||||||
LawWorld.Build(Seed, 2, false);
|
LawWorld.Build(Seed, 2);
|
||||||
FVoxelStrateRegionManifest LawManifest;
|
FVoxelStrateRegionManifest LawManifest;
|
||||||
FVoxelOpStack LawStack;
|
FVoxelOpStack LawStack;
|
||||||
FVoxelOpContext LawContext;
|
FVoxelOpContext LawContext;
|
||||||
|
|||||||
@@ -100,65 +100,44 @@ bool FVoxelForgeMazeSeamTest::RunTest(const FString& Parameters)
|
|||||||
{
|
{
|
||||||
using namespace VoxelForgeTest;
|
using namespace VoxelForgeTest;
|
||||||
|
|
||||||
FTestWorld LegacyWorld;
|
FTestWorld World;
|
||||||
LegacyWorld.Build(7331, 2, false, 4);
|
World.Build(7331, 2, 4);
|
||||||
FTestWorld StackWorld;
|
if (!World.IsValid())
|
||||||
StackWorld.Build(7331, 2, true, 4);
|
|
||||||
if (!LegacyWorld.IsValid() || !StackWorld.IsValid())
|
|
||||||
{
|
{
|
||||||
AddError(LegacyWorld.IsValid() ? StackWorld.WhyInvalid() : LegacyWorld.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 LegacyTop = 0, LegacyBottom = 0;
|
int32 Top = 0, Bottom = 0;
|
||||||
int32 StackTop = 0, StackBottom = 0;
|
if (!World.GetSlotVoxelZRange(FTestWorld::SlotMaze, Top, Bottom))
|
||||||
if (!LegacyWorld.GetSlotVoxelZRange(FTestWorld::SlotMaze, LegacyTop, LegacyBottom)
|
|
||||||
|| !StackWorld.GetSlotVoxelZRange(FTestWorld::SlotMaze, StackTop, StackBottom))
|
|
||||||
{
|
{
|
||||||
AddError(TEXT("The seam fixture has no Maze slot."));
|
AddError(TEXT("The seam fixture has no Maze slot."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const int32 LegacyMidChunkZ = ((LegacyTop + LegacyBottom) / 2) / CHUNK_SIZE;
|
const int32 MidChunkZ = ((Top + Bottom) / 2) / CHUNK_SIZE;
|
||||||
const int32 StackMidChunkZ = ((StackTop + StackBottom) / 2) / CHUNK_SIZE;
|
const FMazeGenerationParams Params = World.StrateManager->GetMazeParamsForChunk(
|
||||||
const FMazeGenerationParams LegacyParams = LegacyWorld.StrateManager->GetMazeParamsForChunk(
|
FIntVector(0, 0, MidChunkZ));
|
||||||
FIntVector(0, 0, LegacyMidChunkZ));
|
|
||||||
const FMazeGenerationParams StackParams = StackWorld.StrateManager->GetMazeParamsForChunk(
|
|
||||||
FIntVector(0, 0, StackMidChunkZ));
|
|
||||||
|
|
||||||
TArray<FVector> LegacyProbes;
|
TArray<FVector> Probes;
|
||||||
TArray<FVector> StackProbes;
|
AddMazeBoundaryProbes(Params, Bottom, Top, Probes);
|
||||||
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());
|
|
||||||
|
|
||||||
int32 LegacyFirst = INDEX_NONE, StackFirst = INDEX_NONE;
|
int32 First = INDEX_NONE;
|
||||||
float LegacyBefore = 0.0f, LegacyAfter = 0.0f;
|
float Before = 0.0f, After = 0.0f;
|
||||||
float StackBefore = 0.0f, StackAfter = 0.0f;
|
const int32 Mismatches = CountContextMismatches(World, Probes, First, Before, After);
|
||||||
const int32 LegacyMismatches = CountContextMismatches(
|
|
||||||
LegacyWorld, LegacyProbes, LegacyFirst, LegacyBefore, LegacyAfter);
|
|
||||||
const int32 StackMismatches = CountContextMismatches(
|
|
||||||
StackWorld, StackProbes, StackFirst, StackBefore, StackAfter);
|
|
||||||
|
|
||||||
TestEqual(TEXT("legacy Maze has no chunk-context seam mismatches"), LegacyMismatches, 0);
|
TestEqual(TEXT("Maze has no chunk-context seam mismatches"), Mismatches, 0);
|
||||||
TestEqual(TEXT("operator-stack Maze has no chunk-context seam mismatches"), StackMismatches, 0);
|
TestTrue(TEXT("Maze seam test sampled cell-boundary probes"), Probes.Num() > 0);
|
||||||
TestTrue(TEXT("Maze seam test sampled cell-boundary probes"), LegacyProbes.Num() > 0);
|
|
||||||
|
|
||||||
if (LegacyFirst != INDEX_NONE)
|
if (First != INDEX_NONE)
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(TEXT("Legacy Maze seam mismatch at probe %d: %.9g -> %.9g"),
|
AddError(FString::Printf(TEXT("Maze seam mismatch at probe %d: %.9g -> %.9g"),
|
||||||
LegacyFirst, LegacyBefore, LegacyAfter));
|
First, Before, After));
|
||||||
}
|
|
||||||
if (StackFirst != INDEX_NONE)
|
|
||||||
{
|
|
||||||
AddError(FString::Printf(TEXT("Operator Maze seam mismatch at probe %d: %.9g -> %.9g"),
|
|
||||||
StackFirst, StackBefore, StackAfter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInfo(FString::Printf(
|
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."),
|
"local window=2x2x2 child nodes ({-1,0}^3), no wide collect."),
|
||||||
LegacyProbes.Num(), LegacyMismatches, StackMismatches));
|
Probes.Num(), Mismatches));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +154,7 @@ bool FVoxelForgeCaveWarpTileLodSeamTest::RunTest(const FString& Parameters)
|
|||||||
using namespace VoxelForgeTest;
|
using namespace VoxelForgeTest;
|
||||||
|
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(1337, 2, true, 4);
|
World.Build(1337, 2, 4);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -90,9 +90,8 @@ bool FVoxelForgeOpStackChannelTest::RunTest(const FString& Parameters)
|
|||||||
TestTrue(*FString::Printf(TEXT("%s shipping stack has a legal channel DAG"), Name), bValid);
|
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
|
// These are the eight shipped archetype builders. Keep the archetype list explicit so adding
|
||||||
// archetype list explicit so adding a builder without adding its validator assertion is
|
// a builder without adding its validator assertion is visible in this test.
|
||||||
// visible in this test.
|
|
||||||
int32 MidChunkZ = 0;
|
int32 MidChunkZ = 0;
|
||||||
|
|
||||||
if (MidChunkForSlot(FTestWorld::SlotTunnelNetwork, TEXT("TunnelNetwork"), MidChunkZ))
|
if (MidChunkForSlot(FTestWorld::SlotTunnelNetwork, TEXT("TunnelNetwork"), MidChunkZ))
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ namespace
|
|||||||
Definition->SurfaceParams = Candidate.ArchetypeParams.SurfaceParams;
|
Definition->SurfaceParams = Candidate.ArchetypeParams.SurfaceParams;
|
||||||
Definition->VerticalShaftParams = Candidate.ArchetypeParams.VerticalShaftParams;
|
Definition->VerticalShaftParams = Candidate.ArchetypeParams.VerticalShaftParams;
|
||||||
Definition->FloatingIslandParams = Candidate.ArchetypeParams.FloatingIslandParams;
|
Definition->FloatingIslandParams = Candidate.ArchetypeParams.FloatingIslandParams;
|
||||||
Definition->bUseOperatorStack = true;
|
|
||||||
Definition->TransitionType = EVoxelStrateTransition::Hard;
|
Definition->TransitionType = EVoxelStrateTransition::Hard;
|
||||||
World.Reinitialize();
|
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
|
// 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.
|
// single-region candidates without making a corpus asset edit look like a geometry failure.
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(DiagnosisSeed, 2, true, 8);
|
World.Build(DiagnosisSeed, 2, 8);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// production asset remains untouched; this is a transient proof-only fixture configuration.
|
||||||
FTestWorld World;
|
FTestWorld World;
|
||||||
World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false,
|
World.Build(/*Seed*/1337, /*GapChunks*/2, /*InStrateHeightInChunks*/8);
|
||||||
/*InStrateHeightInChunks*/8);
|
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(World.WhyInvalid());
|
AddError(World.WhyInvalid());
|
||||||
|
|||||||
@@ -1325,20 +1325,12 @@ bool FVoxelForgeVerticalShaftSeamTest::RunTest(const FString& Parameters)
|
|||||||
{
|
{
|
||||||
using namespace VoxelForgeTest;
|
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;
|
FTestWorld World;
|
||||||
World.Build(/*InSeed=*/1337, /*InGapChunks=*/2, bUseOperatorStack);
|
World.Build(/*InSeed=*/1337, /*InGapChunks=*/2);
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(TEXT("HARD FAILURE: could not build the seam-test fixture."));
|
||||||
TEXT("HARD FAILURE: could not build the %s seam-test fixture."),
|
return false;
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")));
|
|
||||||
bAllChecksPassed = false;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 Checked = 0;
|
int32 Checked = 0;
|
||||||
@@ -1346,33 +1338,25 @@ bool FVoxelForgeVerticalShaftSeamTest::RunTest(const FString& Parameters)
|
|||||||
FString FirstMismatch;
|
FString FirstMismatch;
|
||||||
if (!CheckVerticalShaftSeams(World, Checked, Mismatches, FirstMismatch))
|
if (!CheckVerticalShaftSeams(World, Checked, Mismatches, FirstMismatch))
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(TEXT("HARD FAILURE: the seam test sampled no VerticalShafts positions."));
|
||||||
TEXT("HARD FAILURE: the %s seam test sampled no VerticalShafts positions."),
|
return false;
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy")));
|
|
||||||
bAllChecksPassed = false;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Summary += FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT(" %s path: %d same-position re-evaluations, %d bit mismatches%s%s.\n"),
|
TEXT("VerticalShafts seam-freedom check (cell-boundary probes with two neighbouring "
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
"chunk-context orders): %d same-position re-evaluations, %d bit mismatches%s%s."),
|
||||||
Checked,
|
Checked,
|
||||||
Mismatches,
|
Mismatches,
|
||||||
Mismatches > 0 ? TEXT("; first ") : TEXT(""),
|
Mismatches > 0 ? TEXT("; first ") : TEXT(""),
|
||||||
Mismatches > 0 ? *FirstMismatch : TEXT(""));
|
Mismatches > 0 ? *FirstMismatch : TEXT("")));
|
||||||
if (Mismatches != 0)
|
if (Mismatches != 0)
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("SEAM: %s VerticalShafts density changed after different chunk-context "
|
TEXT("SEAM: VerticalShafts density changed after different chunk-context orders: %s"),
|
||||||
"orders: %s"),
|
|
||||||
bUseOperatorStack ? TEXT("operator-stack") : TEXT("legacy"),
|
|
||||||
*FirstMismatch));
|
*FirstMismatch));
|
||||||
bAllChecksPassed = false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
|
|
||||||
AddInfo(Summary);
|
|
||||||
return bAllChecksPassed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
// ---------------
|
// ---------------
|
||||||
// The interesting invariants (density purity across worker threads, ClassifyTile soundness)
|
// The interesting invariants (density purity across worker threads, ClassifyTile soundness)
|
||||||
// only fire on the REAL path — UVoxelGenerator::GetDensityAt — because that is where the
|
// 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).
|
// thread_local per-chunk caches live (CP_*, the prepared stacks, the diff slots, the SDF cache).
|
||||||
// Calling GetSurfaceDensity / GetMazeDensity directly bypasses every one of them and would
|
// Evaluating a stack directly bypasses every one of them and would test almost nothing.
|
||||||
// test almost nothing. GetDensityAt in turn needs a live UVoxelStrateManager, whose only
|
// GetDensityAt in turn needs a live UVoxelStrateManager, whose only
|
||||||
// entry point is Initialize(UVoxelSettings*, int32) reading TSoftObjectPtr pools.
|
// entry point is Initialize(UVoxelSettings*, int32) reading TSoftObjectPtr pools.
|
||||||
//
|
//
|
||||||
// So the fixture builds a whole synthetic world in memory: transient strate definitions →
|
// 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.
|
* generator + diff layer. No AActor, no UWorld, no PIE.
|
||||||
*
|
*
|
||||||
* The default layout stacks one strate of EVERY archetype (in ECaveGeneratorType order),
|
* 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.
|
* plus the gap-bedrock path when InterStrateGapChunks > 0.
|
||||||
*/
|
*/
|
||||||
struct FTestWorld
|
struct FTestWorld
|
||||||
@@ -73,8 +73,7 @@ namespace VoxelForgeTest
|
|||||||
* dangerous — and `VoxelForge.Determinism.LargeSeedSurvives` deliberately passes big ones
|
* dangerous — and `VoxelForge.Determinism.LargeSeedSurvives` deliberately passes big ones
|
||||||
* (up to 2e9) to prove it stays that way.
|
* (up to 2e9) to prove it stays that way.
|
||||||
*/
|
*/
|
||||||
void Build(int32 InSeed = 1337, int32 InGapChunks = 2, bool bUseOperatorStack = false,
|
void Build(int32 InSeed = 1337, int32 InGapChunks = 2, int32 InStrateHeightInChunks = 4)
|
||||||
int32 InStrateHeightInChunks = 4)
|
|
||||||
{
|
{
|
||||||
Settings = TStrongObjectPtr<UVoxelSettings>(
|
Settings = TStrongObjectPtr<UVoxelSettings>(
|
||||||
NewObject<UVoxelSettings>(GetTransientPackage(), NAME_None, RF_Transient));
|
NewObject<UVoxelSettings>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||||
@@ -111,10 +110,6 @@ namespace VoxelForgeTest
|
|||||||
// refinement and roll tests use the smaller synthetic volume as a bounded memory
|
// refinement and roll tests use the smaller synthetic volume as a bounded memory
|
||||||
// control; the owner-facing showcase opts into 8 chunks below.
|
// control; the owner-facing showcase opts into 8 chunks below.
|
||||||
Def->StrateHeightInChunks = FMath::Max(1, InStrateHeightInChunks);
|
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
|
// Hard transitions: param blending across a boundary would make "which archetype
|
||||||
// owns this chunk" ambiguous, and these tests want an unambiguous mapping.
|
// owns this chunk" ambiguous, and these tests want an unambiguous mapping.
|
||||||
Def->TransitionType = EVoxelStrateTransition::Hard;
|
Def->TransitionType = EVoxelStrateTransition::Hard;
|
||||||
|
|||||||
@@ -154,72 +154,50 @@ bool FVoxelForgeTilePostReachProofTest::RunTest(const FString& Parameters)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FTestWorld NativeWorld;
|
FTestWorld World;
|
||||||
NativeWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false);
|
World.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
FTestWorld FusedWorld;
|
if (!World.IsValid())
|
||||||
FusedWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
|
||||||
if (!NativeWorld.IsValid() || !FusedWorld.IsValid())
|
|
||||||
{
|
{
|
||||||
AddError(NativeWorld.IsValid() && FusedWorld.IsValid()
|
AddError(World.WhyInvalid());
|
||||||
? TEXT("Unexpected invalid reach-proof fixture")
|
|
||||||
: (!NativeWorld.IsValid() ? NativeWorld.WhyInvalid() : FusedWorld.WhyInvalid()));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<FIntVector> NativeOrigins;
|
TArray<FIntVector> Origins;
|
||||||
TArray<FIntVector> FusedOrigins;
|
AppendWorldReachTiles(World, Origins);
|
||||||
AppendWorldReachTiles(NativeWorld, NativeOrigins);
|
TestTrue(TEXT("the representative tile set is non-empty"), Origins.Num() > 0);
|
||||||
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);
|
|
||||||
|
|
||||||
TStrongObjectPtr<UVoxelMarchingCubesMesher> NativeMesher(
|
TStrongObjectPtr<UVoxelMarchingCubesMesher> Mesher(
|
||||||
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
|
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||||
TStrongObjectPtr<UVoxelMarchingCubesMesher> FusedMesher(
|
Mesher->SetGenerator(World.Generator.Get());
|
||||||
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
|
|
||||||
NativeMesher->SetGenerator(NativeWorld.Generator.Get());
|
|
||||||
FusedMesher->SetGenerator(FusedWorld.Generator.Get());
|
|
||||||
|
|
||||||
VoxelGenLOD::ResetTilePostReachDiagnostics();
|
VoxelGenLOD::ResetTilePostReachDiagnostics();
|
||||||
CVarGuard.SetScale(1.0f);
|
CVarGuard.SetScale(1.0f);
|
||||||
const FReachSnapshot NativeFull = RunReachTiles(*NativeMesher, NativeOrigins);
|
const FReachSnapshot Full = RunReachTiles(*Mesher, Origins);
|
||||||
const FReachSnapshot FusedFull = RunReachTiles(*FusedMesher, FusedOrigins);
|
|
||||||
const uint64 FullFinalComparisons = NativeFull.FinalComparisons + FusedFull.FinalComparisons;
|
|
||||||
const uint64 FullFinalDifferences = NativeFull.FinalDifferences + FusedFull.FinalDifferences;
|
|
||||||
TestTrue(TEXT("normal reach compares final fields on the representative tiles"),
|
TestTrue(TEXT("normal reach compares final fields on the representative tiles"),
|
||||||
FullFinalComparisons > 0);
|
Full.FinalComparisons > 0);
|
||||||
TestEqual(TEXT("normal reach has no final-field differences"), FullFinalDifferences, 0ull);
|
TestEqual(TEXT("normal reach has no final-field differences"), Full.FinalDifferences, 0ull);
|
||||||
TestEqual(TEXT("normal reach has no skipped-post differences"),
|
TestEqual(TEXT("normal reach has no skipped-post differences"), Full.AllDifferences, 0ull);
|
||||||
NativeFull.AllDifferences + FusedFull.AllDifferences, 0ull);
|
|
||||||
|
|
||||||
VoxelGenLOD::ResetTilePostReachDiagnostics();
|
VoxelGenLOD::ResetTilePostReachDiagnostics();
|
||||||
// Use a deliberately tiny but valid scale so at least one envelope is rejected while the same
|
// 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.
|
// final-field samples remain exercised; this is the negative control, not a supported setting.
|
||||||
CVarGuard.SetScale(0.01f);
|
CVarGuard.SetScale(0.01f);
|
||||||
const FReachSnapshot NativeShrunk = RunReachTiles(*NativeMesher, NativeOrigins);
|
const FReachSnapshot Shrunk = RunReachTiles(*Mesher, Origins);
|
||||||
const FReachSnapshot FusedShrunk = RunReachTiles(*FusedMesher, FusedOrigins);
|
|
||||||
const uint64 ShrunkFinalComparisons =
|
|
||||||
NativeShrunk.FinalComparisons + FusedShrunk.FinalComparisons;
|
|
||||||
const uint64 ShrunkFinalDifferences =
|
|
||||||
NativeShrunk.FinalDifferences + FusedShrunk.FinalDifferences;
|
|
||||||
TestTrue(TEXT("shrunk reach still executes final-field comparisons"),
|
TestTrue(TEXT("shrunk reach still executes final-field comparisons"),
|
||||||
ShrunkFinalComparisons > 0);
|
Shrunk.FinalComparisons > 0);
|
||||||
TestTrue(TEXT("shrunk reach demonstrably fails the final-field proof"),
|
TestTrue(TEXT("shrunk reach demonstrably fails the final-field proof"),
|
||||||
ShrunkFinalDifferences > 0);
|
Shrunk.FinalDifferences > 0);
|
||||||
|
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT("tile_post_reach_proof: native_tiles=%llu fused_tiles=%llu full_final=%llu/%llu "
|
TEXT("tile_post_reach_proof: tiles=%llu full_final=%llu/%llu shrunk_final=%llu/%llu "
|
||||||
"shrunk_final=%llu/%llu native_samples=%lld/%lld fused_samples=%lld/%lld."),
|
"samples=%lld/%lld."),
|
||||||
static_cast<unsigned long long>(NativeFull.Tiles),
|
static_cast<unsigned long long>(Full.Tiles),
|
||||||
static_cast<unsigned long long>(FusedFull.Tiles),
|
static_cast<unsigned long long>(Full.FinalComparisons),
|
||||||
static_cast<unsigned long long>(FullFinalComparisons),
|
static_cast<unsigned long long>(Full.FinalDifferences),
|
||||||
static_cast<unsigned long long>(FullFinalDifferences),
|
static_cast<unsigned long long>(Shrunk.FinalComparisons),
|
||||||
static_cast<unsigned long long>(ShrunkFinalComparisons),
|
static_cast<unsigned long long>(Shrunk.FinalDifferences),
|
||||||
static_cast<unsigned long long>(ShrunkFinalDifferences),
|
static_cast<long long>(Full.DensitySamples),
|
||||||
static_cast<long long>(NativeFull.DensitySamples),
|
static_cast<long long>(Shrunk.DensitySamples)));
|
||||||
static_cast<long long>(NativeShrunk.DensitySamples),
|
|
||||||
static_cast<long long>(FusedFull.DensitySamples),
|
|
||||||
static_cast<long long>(FusedShrunk.DensitySamples)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,26 +67,20 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
FailureKinds += Kind;
|
FailureKinds += Kind;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The ordinary fixture deliberately uses the requested default, so existing op-stack and
|
// The ordinary fixture deliberately uses the requested default, so the other tests exercise
|
||||||
// legacy tests exercise the setting without having to know about the new post.
|
// the setting without having to know about the new post.
|
||||||
FTestWorld Legacy;
|
FTestWorld Bounded;
|
||||||
Legacy.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false);
|
Bounded.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
FTestWorld StackWorld;
|
|
||||||
StackWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
|
||||||
FTestWorld Unbounded;
|
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()
|
AddError(!Bounded.IsValid() ? Bounded.WhyInvalid() : Unbounded.WhyInvalid());
|
||||||
? TEXT("Unexpected edge-seal fixture state")
|
|
||||||
: (!Legacy.IsValid() ? Legacy.WhyInvalid()
|
|
||||||
: (!StackWorld.IsValid() ? StackWorld.WhyInvalid() : Unbounded.WhyInvalid())));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetEdgeSettings(Legacy, TestWorldRadius);
|
SetEdgeSettings(Bounded, TestWorldRadius);
|
||||||
SetEdgeSettings(StackWorld, TestWorldRadius);
|
|
||||||
SetEdgeSettings(Unbounded, 0.0f);
|
SetEdgeSettings(Unbounded, 0.0f);
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
@@ -103,12 +97,12 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
for (int32 Slot = 0; Slot < 8; ++Slot)
|
for (int32 Slot = 0; Slot < 8; ++Slot)
|
||||||
{
|
{
|
||||||
int32 TopZ = 0, BottomZ = 0;
|
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((float)((TopZ + BottomZ) / 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StrateZ.Add(Legacy.MidVoxelZ());
|
StrateZ.Add(Bounded.MidVoxelZ());
|
||||||
|
|
||||||
int32 NumPositiveSamples = 0;
|
int32 NumPositiveSamples = 0;
|
||||||
for (const FVector& XY : FarXY)
|
for (const FVector& XY : FarXY)
|
||||||
@@ -117,8 +111,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
{
|
{
|
||||||
const FVector P(XY.X, XY.Y, Z);
|
const FVector P(XY.X, XY.Y, Z);
|
||||||
++NumPositiveSamples;
|
++NumPositiveSamples;
|
||||||
if (!IsSolidMC(*Legacy.Generator, P)) Fail(TEXT("positive-legacy"));
|
if (!IsSolidMC(*Bounded.Generator, P)) Fail(TEXT("positive"));
|
||||||
if (!IsSolidMC(*StackWorld.Generator, P)) Fail(TEXT("positive-opstack"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +131,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
for (const float Z : StrateZ)
|
for (const float Z : StrateZ)
|
||||||
{
|
{
|
||||||
const FVector P(XY.X, XY.Y, Z);
|
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);
|
const float UnboundedD = Unbounded.Generator->GetDensityAt(P.X, P.Y, P.Z);
|
||||||
++NumInteriorComparisons;
|
++NumInteriorComparisons;
|
||||||
if (!BitEqual(BoundedD, UnboundedD)) Fail(TEXT("interior-not-no-op"));
|
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.
|
// CLASSIFYBOX — force boxes in the ramp and beyond the radius, then brute-force every voxel.
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
int32 TopShaftZ = 0, BottomShaftZ = 0;
|
int32 TopShaftZ = 0, BottomShaftZ = 0;
|
||||||
if (!StackWorld.GetSlotVoxelZRange(FTestWorld::SlotVerticalShafts, TopShaftZ, BottomShaftZ))
|
if (!Bounded.GetSlotVoxelZRange(FTestWorld::SlotVerticalShafts, TopShaftZ, BottomShaftZ))
|
||||||
{
|
{
|
||||||
Fail(TEXT("missing-shaft-slot"));
|
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 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));
|
FIntVector(0, 0, MidChunkZ));
|
||||||
FVoxelOpStack Stack;
|
FVoxelOpStack Stack;
|
||||||
VoxelDensityOps::BuildVerticalShaftStack(
|
VoxelDensityOps::BuildVerticalShaftStack(
|
||||||
Stack, ShaftParams, StackWorld.Settings->Seed,
|
Stack, ShaftParams, Bounded.Settings->Seed,
|
||||||
StackWorld.Generator->OriginSpineRadius, StackWorld.StrateManager.Get());
|
Bounded.Generator->OriginSpineRadius, Bounded.StrateManager.Get());
|
||||||
|
|
||||||
FVoxelOpContext Ctx;
|
FVoxelOpContext Ctx;
|
||||||
Ctx.ChunkCoord = FIntVector(0, 0, MidChunkZ);
|
Ctx.ChunkCoord = FIntVector(0, 0, MidChunkZ);
|
||||||
Ctx.Seed = (uint32)StackWorld.Settings->Seed;
|
Ctx.Seed = (uint32)Bounded.Settings->Seed;
|
||||||
Ctx.LayoutVersion = StackWorld.StrateManager->GetLayoutVersion();
|
Ctx.LayoutVersion = Bounded.StrateManager->GetLayoutVersion();
|
||||||
Ctx.StrateTopWorldZ = ShaftParams.StrateTopWorldZ;
|
Ctx.StrateTopWorldZ = ShaftParams.StrateTopWorldZ;
|
||||||
Ctx.StrateBottomWorldZ = ShaftParams.StrateBottomWorldZ;
|
Ctx.StrateBottomWorldZ = ShaftParams.StrateBottomWorldZ;
|
||||||
Ctx.WorldRadiusVoxels = TestWorldRadius;
|
Ctx.WorldRadiusVoxels = TestWorldRadius;
|
||||||
@@ -255,14 +248,14 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
for (int32 X = X0; X <= X1; ++X)
|
for (int32 X = X0; X <= X1; ++X)
|
||||||
{
|
{
|
||||||
++NumVoxelsChecked;
|
++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"));
|
Fail(TEXT("classifybox-false-allsolid"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The T1.d path must be able to skip an edge tile without knowing which Z category it hits.
|
// 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);
|
FIntVector(320, 0, BottomShaftZ + 8), 1, 8);
|
||||||
int32 NumTilesProved = (EdgeTile == EVoxelTileClass::AllSolid) ? 1 : 0;
|
int32 NumTilesProved = (EdgeTile == EVoxelTileClass::AllSolid) ? 1 : 0;
|
||||||
if (EdgeTile != EVoxelTileClass::AllSolid) Fail(TEXT("classifytile-missed-edge"));
|
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
|
// 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.
|
// adjustment moves every generated mouth out of this small test rim.
|
||||||
FTestWorld GeneratedPassageWorld;
|
FTestWorld GeneratedPassageWorld;
|
||||||
GeneratedPassageWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false);
|
GeneratedPassageWorld.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
FTestWorld GeneratedPassageStackWorld;
|
|
||||||
GeneratedPassageStackWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
|
|
||||||
auto CheckGeneratedPassages = [&](FTestWorld& World, const TCHAR* FailureKind)
|
auto CheckGeneratedPassages = [&](FTestWorld& World, const TCHAR* FailureKind)
|
||||||
{
|
{
|
||||||
if (!World.IsValid())
|
if (!World.IsValid())
|
||||||
@@ -310,7 +301,6 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
CheckGeneratedPassages(GeneratedPassageWorld, TEXT("generated-passage-breaches-rim"));
|
CheckGeneratedPassages(GeneratedPassageWorld, TEXT("generated-passage-breaches-rim"));
|
||||||
CheckGeneratedPassages(GeneratedPassageStackWorld, TEXT("generated-opstack-passage-breaches-rim"));
|
|
||||||
|
|
||||||
// One sorted summary block; individual samples intentionally never log.
|
// One sorted summary block; individual samples intentionally never log.
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
@@ -319,7 +309,7 @@ bool FVoxelForgeWorldEdgeSealTest::RunTest(const FString& Parameters)
|
|||||||
TEXT(" classify_tile_proved=%d\n")
|
TEXT(" classify_tile_proved=%d\n")
|
||||||
TEXT(" interior_comparisons=%d\n")
|
TEXT(" interior_comparisons=%d\n")
|
||||||
TEXT(" near_rim_passage_checks=%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_directions=%d\n")
|
||||||
TEXT(" rim_max_adjacent_solidness_jump=%.6f (threshold=%.6f)\n")
|
TEXT(" rim_max_adjacent_solidness_jump=%.6f (threshold=%.6f)\n")
|
||||||
TEXT(" voxels_checked=%lld\n")
|
TEXT(" voxels_checked=%lld\n")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
#include "VoxelWormField.h"
|
#include "VoxelWormField.h"
|
||||||
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
|
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
|
||||||
#include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack
|
#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 "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
|
||||||
#include "VoxelStats.h"
|
#include "VoxelStats.h"
|
||||||
#include "VoxelDensityProfile.h"
|
#include "VoxelDensityProfile.h"
|
||||||
@@ -669,9 +669,9 @@ struct FVoxelDensityBlockSession
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate lazily on the first operator-stack sample. Legacy archetypes still use the
|
// Allocate lazily on the first operator-stack sample. Chunks that never reach a stack
|
||||||
// ordinary scalar generator and should not pay for a block scratch lattice merely because
|
// (gaps, out-of-layout, degenerate strates) use the ordinary scalar path and should not
|
||||||
// the mesher opened the scoped hand-off.
|
// pay for a block scratch lattice merely because the mesher opened the scoped hand-off.
|
||||||
BlockScratch.Reset();
|
BlockScratch.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2549,12 +2549,11 @@ namespace
|
|||||||
/**
|
/**
|
||||||
* Construit la pile de cet archétype dans `OutStack` et remplit les bornes Z de `OutCtx`.
|
* 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,
|
* @return false quand la pile NE DOIT PAS être utilisée — archétype inconnu, params absents,
|
||||||
* ou **strate dégénérée**. Ce dernier cas n'est pas de la prudence : cinq fonctions
|
* ou **strate dégénérée** (hauteur nulle). La pile n'a pas d'early-out pour ce dernier
|
||||||
* d'archétype court-circuitent sur `return 1.0f` (= air) quand la hauteur est nulle,
|
* cas, par conception : l'appelant rend de l'air (`GetDensityAt`) ou refuse de prouver
|
||||||
* et la pile n'a pas cet early-out, par conception. L'appelant retombe sur le `switch`,
|
* la tuile (`ClassifyTile`). (`GetDensityWithParams` n'a aucun early-out de ce genre :
|
||||||
* qui EST le comportement de référence. (`GetDensityWithParams`, lui, n'a aucun
|
* TunnelNetwork/Underwater n'ont donc pas cette garde.)
|
||||||
* early-out de ce genre : TunnelNetwork/Underwater n'ont donc pas cette garde.)
|
|
||||||
*/
|
*/
|
||||||
bool VF_BuildOpStackForChunk(ECaveGeneratorType Type, FVoxelStackParamRefs& Refs,
|
bool VF_BuildOpStackForChunk(ECaveGeneratorType Type, FVoxelStackParamRefs& Refs,
|
||||||
int32 Seed, float SpineRadius, const UVoxelStrateManager* SM,
|
int32 Seed, float SpineRadius, const UVoxelStrateManager* SM,
|
||||||
@@ -2572,8 +2571,8 @@ namespace
|
|||||||
|
|
||||||
case ECaveGeneratorType::FlatPlain:
|
case ECaveGeneratorType::FlatPlain:
|
||||||
case ECaveGeneratorType::CrystalChamber:
|
case ECaveGeneratorType::CrystalChamber:
|
||||||
// UN SEUL cas pour les deux, comme le `switch` de production : `GetSlabDensity` ne les
|
// UN SEUL cas pour les deux : ils ne diffèrent que par leurs valeurs par défaut.
|
||||||
// distingue pas non plus. Voir BuildSlabStack.
|
// Voir BuildSlabStack.
|
||||||
if (!Refs.Slab) { return false; }
|
if (!Refs.Slab) { return false; }
|
||||||
if (Refs.Slab->StrateTopWorldZ - Refs.Slab->StrateBottomWorldZ <= 0.0f) { return false; }
|
if (Refs.Slab->StrateTopWorldZ - Refs.Slab->StrateBottomWorldZ <= 0.0f) { return false; }
|
||||||
OutCtx.StrateTopWorldZ = Refs.Slab->StrateTopWorldZ;
|
OutCtx.StrateTopWorldZ = Refs.Slab->StrateTopWorldZ;
|
||||||
@@ -2621,9 +2620,8 @@ namespace
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// `UsesOperatorStackForChunk` ne rend true que pour les archétypes portés (les 8), donc
|
// `UsesOperatorStackForChunk` ne rend true que pour les 8 archétypes, donc on ne devrait
|
||||||
// on ne devrait jamais arriver ici. Si ça arrive : retomber sur le `switch` plutôt que
|
// jamais arriver ici (valeur d'enum invalide).
|
||||||
// générer du vide — un monde faux est pire qu'un monde non porté.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2842,11 +2840,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
thread_local FBiomeContext CP_BiomeCtx;
|
thread_local FBiomeContext CP_BiomeCtx;
|
||||||
thread_local FChunkBiomeCache CP_BiomeCache;
|
thread_local FChunkBiomeCache CP_BiomeCache;
|
||||||
thread_local TArray<FSurfaceGenerationParams> CP_SurfaceBiomeParams;
|
thread_local TArray<FSurfaceGenerationParams> 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
|
// 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
|
// 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
|
// 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.
|
// when the other context misses, this bit must force the rebuild guard below to run.
|
||||||
thread_local bool CP_UsesTileCacheWindow = false;
|
thread_local bool CP_UsesTileCacheWindow = false;
|
||||||
// OPSTACK — la pile d'opérateurs, construite dans le MÊME bloc de refetch que les
|
// 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
|
// params (donc même clé owner+chunk+version, aucune logique d'invalidation en plus). Vide
|
||||||
// la strate n'a pas coché `bUseOperatorStack` ET que son archétype n'est pas porté.
|
// seulement pour une strate dégénérée (hauteur nulle).
|
||||||
thread_local FVoxelOpStack CP_OpStack;
|
thread_local FVoxelOpStack CP_OpStack;
|
||||||
thread_local bool CP_UseOpStack = false;
|
thread_local bool CP_UseOpStack = false;
|
||||||
thread_local uint64 CP_ManagerLifetimeId = 0;
|
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
|
// 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
|
// 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
|
// 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
|
// room-graph cache remains keyed by the real XY search window. Chunks outside the strate
|
||||||
// the full XYZ key because their native cache is built for one XY window.
|
// layout (constant air) keep the full XYZ key.
|
||||||
const ECaveGeneratorType QueryGeneratorType = bUseChunkRoutingCache
|
const ECaveGeneratorType QueryGeneratorType = bUseChunkRoutingCache
|
||||||
? CP_RouteGeneratorType
|
? CP_RouteGeneratorType
|
||||||
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
: StrateManager->GetGeneratorTypeForChunk(ChunkCoord);
|
||||||
@@ -3066,7 +3059,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
CP_Maze = StrateManager->GetMazeParamsForChunk(ChunkCoord); break;
|
CP_Maze = StrateManager->GetMazeParamsForChunk(ChunkCoord); break;
|
||||||
case ECaveGeneratorType::SurfaceWorld:
|
case ECaveGeneratorType::SurfaceWorld:
|
||||||
ResolveSurfaceChunkParams(ChunkCoord, CP_Surface, CP_BiomeCtx, CP_SurfaceBiomeParams);
|
ResolveSurfaceChunkParams(ChunkCoord, CP_Surface, CP_BiomeCtx, CP_SurfaceBiomeParams);
|
||||||
CP_StrateKey = FMath::RoundToInt(CP_Surface.StrateBottomWorldZ);
|
|
||||||
break;
|
break;
|
||||||
case ECaveGeneratorType::VerticalShafts:
|
case ECaveGeneratorType::VerticalShafts:
|
||||||
CP_Vert = StrateManager->GetVerticalShaftParamsForChunk(ChunkCoord); break;
|
CP_Vert = StrateManager->GetVerticalShaftParamsForChunk(ChunkCoord); break;
|
||||||
@@ -3085,28 +3077,17 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
}
|
}
|
||||||
CP_Dist = StrateManager->GetDisturbanceParamsForChunk(ChunkCoord);
|
CP_Dist = StrateManager->GetDisturbanceParamsForChunk(ChunkCoord);
|
||||||
|
|
||||||
// ── OPSTACK : (re)construire la pile si cette strate l'a demandée. ──
|
// ── OPSTACK : (re)construire la pile de ce chunk. ──
|
||||||
// Une seule branche ajoutée au chemin densité, et elle est FROIDE : la construction est
|
// Toute strate est générée par une pile d'opérateurs. La construction est par chunk
|
||||||
// par chunk (comme le refetch de params juste au-dessus), jamais par voxel.
|
// (comme le refetch de params juste au-dessus), jamais par voxel.
|
||||||
CP_UseOpStack = bUseChunkRoutingCache
|
// Every strate is generated by an operator stack. An editor composer candidate or a
|
||||||
? CP_RouteUsesOperatorStack
|
// cooked-season recipe supplies its own stack; otherwise — or if that stack cannot be
|
||||||
: StrateManager->UsesOperatorStackForChunk(ChunkCoord);
|
// built — the archetype's native stack is used.
|
||||||
CP_UseOpStack = CP_UseOpStack || CP_UseCustomRecipe;
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
CP_OpStack = FVoxelOpStack(); // move-assign : libère l'ancienne pile
|
CP_OpStack = FVoxelOpStack(); // move-assign : libère l'ancienne pile
|
||||||
bool bBuiltSpecialStack = false;
|
CP_UseOpStack = false;
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
if (CP_UseComposerRegions)
|
if (CP_UseComposerRegions)
|
||||||
{
|
{
|
||||||
bBuiltSpecialStack = true;
|
|
||||||
FVoxelOpContext RegionContext;
|
FVoxelOpContext RegionContext;
|
||||||
if (VF_BuildStrateRegionStack(
|
if (VF_BuildStrateRegionStack(
|
||||||
CP_ComposerRegions, OriginSpineRadius, StrateManager,
|
CP_ComposerRegions, OriginSpineRadius, StrateManager,
|
||||||
@@ -3118,16 +3099,16 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
RegionContext.WorldRadiusVoxels = WorldRadiusVoxels;
|
RegionContext.WorldRadiusVoxels = WorldRadiusVoxels;
|
||||||
RegionContext.EdgeSealThickness = EdgeSealThickness;
|
RegionContext.EdgeSealThickness = EdgeSealThickness;
|
||||||
CP_OpStack.PrepareChunk(RegionContext);
|
CP_OpStack.PrepareChunk(RegionContext);
|
||||||
|
CP_UseOpStack = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CP_UseOpStack = false;
|
CP_OpStack = FVoxelOpStack();
|
||||||
|
CP_UseComposerRegions = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!bBuiltSpecialStack)
|
if (!CP_UseOpStack && CP_UseCustomRecipe)
|
||||||
{
|
|
||||||
if (CP_UseCustomRecipe)
|
|
||||||
{
|
{
|
||||||
FVoxelOpContext RecipeContext;
|
FVoxelOpContext RecipeContext;
|
||||||
if (VF_BuildStackFromRecipe(
|
if (VF_BuildStackFromRecipe(
|
||||||
@@ -3142,15 +3123,17 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
RecipeContext.WorldRadiusVoxels = WorldRadiusVoxels;
|
RecipeContext.WorldRadiusVoxels = WorldRadiusVoxels;
|
||||||
RecipeContext.EdgeSealThickness = EdgeSealThickness;
|
RecipeContext.EdgeSealThickness = EdgeSealThickness;
|
||||||
CP_OpStack.PrepareChunk(RecipeContext);
|
CP_OpStack.PrepareChunk(RecipeContext);
|
||||||
|
CP_UseOpStack = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Import/load validates recipes before a season becomes active. Keep
|
// Import/load validates recipes before a season becomes active. If a future
|
||||||
// the fallback hole-safe if a future schema changes underneath it.
|
// schema still fails here, fall back to the archetype's native stack below.
|
||||||
CP_UseOpStack = false;
|
CP_OpStack = FVoxelOpStack();
|
||||||
|
CP_UseCustomRecipe = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
if (!CP_UseOpStack)
|
||||||
{
|
{
|
||||||
FVoxelOpContext OpCtx;
|
FVoxelOpContext OpCtx;
|
||||||
OpCtx.ChunkCoord = ChunkCoord;
|
OpCtx.ChunkCoord = ChunkCoord;
|
||||||
@@ -3190,26 +3173,16 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
CP_GenType, Refs, Seed, OriginSpineRadius,
|
CP_GenType, Refs, Seed, OriginSpineRadius,
|
||||||
StrateManager, CP_OpStack, OpCtx);
|
StrateManager, CP_OpStack, OpCtx);
|
||||||
|
|
||||||
// Le test appelle PrepareChunk, pas la production : c'est exactement la divergence
|
// PrepareChunk runs here exactly as in the tests, so an op that hoists real
|
||||||
// qui rend un opérateur vert en test et faux en jeu. Les sept `PrepareChunk`
|
// per-chunk work behaves the same in game. `Step` reste 1 : GetDensityAt ne connaît
|
||||||
// actuels sont vides, donc ceci ne change RIEN aujourd'hui — c'est le point : le
|
// pas le pas d'échantillonnage du mesher (voir le contrat T2.b dans VoxelDensityOp.h).
|
||||||
// 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); }
|
if (CP_UseOpStack) { CP_OpStack.PrepareChunk(OpCtx); }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The legacy path needs its prepared native tunnel-core cache. An operator-stack
|
// An operator-stack tunnel source publishes the world-space core after EvalMC, so
|
||||||
// tunnel source publishes the same world-space result after EvalMC, so preparing a
|
// preparing a second full graph/core cache here would only duplicate the work that made
|
||||||
// second full graph/core cache here would only duplicate the work that made coarse
|
// coarse streaming expensive in the first place. Only an editor composer region stack,
|
||||||
// streaming expensive in the first place. Custom/composer paths retain the legacy
|
// which does not publish that hand-off, still gets the prepared native core.
|
||||||
// cache unless their own stack supplies the hand-off.
|
|
||||||
const bool bNativeTunnelCore = !CP_UseCustomRecipe
|
const bool bNativeTunnelCore = !CP_UseCustomRecipe
|
||||||
&& (CP_GenType == ECaveGeneratorType::TunnelNetwork
|
&& (CP_GenType == ECaveGeneratorType::TunnelNetwork
|
||||||
|| CP_GenType == ECaveGeneratorType::Underwater);
|
|| CP_GenType == ECaveGeneratorType::Underwater);
|
||||||
@@ -3294,12 +3267,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
FTunnelCoreWorldEvaluation FusedTunnelCore;
|
FTunnelCoreWorldEvaluation FusedTunnelCore;
|
||||||
bool bRoomOwnsBottom = false;
|
bool bRoomOwnsBottom = false;
|
||||||
bool bRoomOwnsBottomValid = 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
|
// 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
|
// MC (négatif = solide), que les disturbances et la couche de diff qui suivent attendent.
|
||||||
// de diff qui suivent ne voient aucune différence.
|
|
||||||
if (CP_UseOpStack)
|
if (CP_UseOpStack)
|
||||||
{
|
{
|
||||||
const bool bCanUseFusedEvaluator =
|
const bool bCanUseFusedEvaluator =
|
||||||
@@ -3383,66 +3353,11 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else switch (CP_GenType)
|
|
||||||
{
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
float TerrainZ, CeilSurf, OverhangAmp, DirX, DirY;
|
// Only a degenerate strate (zero height) has no stack. It is open air, like the
|
||||||
ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx,
|
// archetype functions the stack replaced.
|
||||||
CP_SurfaceBiomeParams, CP_BiomeCache, TerrainZ, CeilSurf,
|
Result = 1.0f;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
DensityCoreTimer.End();
|
DensityCoreTimer.End();
|
||||||
|
|
||||||
@@ -3482,21 +3397,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||||
FVector RoomQueryPosition;
|
FVector RoomQueryPosition(WorldX, WorldY, WorldZ);
|
||||||
if (bNativeWarpedPositionValid)
|
|
||||||
{
|
|
||||||
RoomQueryPosition = NativeWarpedPosition;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RoomQueryPosition = FVector(WorldX, WorldY, WorldZ);
|
|
||||||
if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f)
|
if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f)
|
||||||
{
|
{
|
||||||
RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale;
|
RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale;
|
||||||
}
|
}
|
||||||
RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp(
|
RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp(
|
||||||
RoomQueryPosition, CP_Tunnel, static_cast<uint32>(Seed));
|
RoomQueryPosition, CP_Tunnel, static_cast<uint32>(Seed));
|
||||||
}
|
|
||||||
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, nullptr,
|
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, nullptr,
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(
|
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
|
// 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
|
// 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
|
// 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::FScopedTimer DensityStructuralPostsTimer(
|
||||||
VoxelDensityProfile::EBucket::DensityStructuralPosts);
|
VoxelDensityProfile::EBucket::DensityStructuralPosts);
|
||||||
VoxelDensityProfile::FScopedTimer StructuralTailTimer(
|
VoxelDensityProfile::FScopedTimer StructuralTailTimer(
|
||||||
@@ -3695,21 +3602,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||||
FVector RoomQueryPosition;
|
FVector RoomQueryPosition(WorldX, WorldY, WorldZ);
|
||||||
if (bNativeWarpedPositionValid)
|
|
||||||
{
|
|
||||||
RoomQueryPosition = NativeWarpedPosition;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RoomQueryPosition = FVector(WorldX, WorldY, WorldZ);
|
|
||||||
if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f)
|
if (CP_Tunnel.VerticalScale != 1.0f && CP_Tunnel.VerticalScale > 0.0f)
|
||||||
{
|
{
|
||||||
RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale;
|
RoomQueryPosition.Z = WorldZ / CP_Tunnel.VerticalScale;
|
||||||
}
|
}
|
||||||
RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp(
|
RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp(
|
||||||
RoomQueryPosition, CP_Tunnel, static_cast<uint32>(Seed));
|
RoomQueryPosition, CP_Tunnel, static_cast<uint32>(Seed));
|
||||||
}
|
|
||||||
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
||||||
nullptr,
|
nullptr,
|
||||||
@@ -6823,7 +6722,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached(
|
|||||||
bool bCanSolid = true; // "tout le treillis est solide" encore prouvable
|
bool bCanSolid = true; // "tout le treillis est solide" encore prouvable
|
||||||
bool bCanAir = true; // "tout le treillis est air" 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 ;
|
// ── 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
|
struct FSurfSlot
|
||||||
{
|
{
|
||||||
int32 BotChunkZ = INT32_MAX; // identité du slot (borne basse de la strate, en chunks)
|
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 »).
|
// 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
|
// 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
|
// 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`,
|
// 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
|
// `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,
|
// 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).
|
// 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
|
// Condition 1 : le slot doit avoir une pile (une définition d'archétype connue).
|
||||||
// classerait un champ que le mesher ne produira pas. C'est le même drapeau, lu au
|
// Sinon on classerait un champ que le mesher ne produira pas.
|
||||||
// même endroit, que `GetDensityAt`.
|
|
||||||
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
||||||
{
|
{
|
||||||
// Attribution DIAGNOSTIQUE uniquement : distingue une strate cave entièrement
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EVoxelTileClass::Mixed;
|
return EVoxelTileClass::Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7223,16 +7095,11 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached(
|
|||||||
return EVoxelTileClass::Mixed; // la boîte déborde sur un autre archétype
|
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
|
// Every chunk of the box must have a stack, not only the one that triggered the
|
||||||
// déclenché la tentative : un seul chunk hors pile invaliderait le verdict.
|
// attempt (redundant with the Z pass today, kept hole-safe).
|
||||||
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
||||||
{
|
{
|
||||||
// Le passage Z précédent a déjà accepté l'unique slot cave. Avec le layout actuel
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack);
|
||||||
// (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);
|
|
||||||
return EVoxelTileClass::Mixed;
|
return EVoxelTileClass::Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7331,8 +7198,8 @@ EVoxelTileClass UVoxelGenerator::ClassifyTileUncached(
|
|||||||
if (!VF_BuildOpStackForChunk(CaveType, Refs, Seed, OriginSpineRadius,
|
if (!VF_BuildOpStackForChunk(CaveType, Refs, Seed, OriginSpineRadius,
|
||||||
StrateManager, TileStack, OpCtx))
|
StrateManager, TileStack, OpCtx))
|
||||||
{
|
{
|
||||||
// Strate dégénérée ou archétype non porté : `GetDensityAt` retomberait sur le `switch`,
|
// Strate dégénérée : `GetDensityAt` y rend de l'air sans pile, donc la pile ne décrit
|
||||||
// donc la pile ne décrit pas ce que le mesher verra. Aucun verdict.
|
// pas ce que le mesher verra. Aucun verdict.
|
||||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack);
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNoStack);
|
||||||
return EVoxelTileClass::Mixed;
|
return EVoxelTileClass::Mixed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ DEFINE_STAT(STAT_VoxelForgeTilesSkippedAllAir);
|
|||||||
DEFINE_STAT(STAT_VoxelForgeTilesMeshed);
|
DEFINE_STAT(STAT_VoxelForgeTilesMeshed);
|
||||||
DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
||||||
DEFINE_STAT(STAT_VoxelForgeTilesOpStackAir);
|
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_VoxelForgeCaveMixOutOfLayout);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveMixGap);
|
DEFINE_STAT(STAT_VoxelForgeCaveMixGap);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveMixSurfaceWorld);
|
DEFINE_STAT(STAT_VoxelForgeCaveMixSurfaceWorld);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Runtime strate layout generation and queries.
|
// Runtime strate layout generation and queries.
|
||||||
|
|
||||||
#include "VoxelStrateManager.h"
|
#include "VoxelStrateManager.h"
|
||||||
#include "CoreGlobals.h" // GIsAutomationTesting — the opt-in diagnostic stays quiet under tests
|
|
||||||
#include "VoxelSettings.h"
|
#include "VoxelSettings.h"
|
||||||
#include "VoxelSeasonAsset.h"
|
#include "VoxelSeasonAsset.h"
|
||||||
#include "VoxelTypes.h" // For CHUNK_SIZE, VOXEL_SIZE, WorldToChunkCoord
|
#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->GeneratorType = SeasonStrate.Archetype;
|
||||||
Slot.Definition->bUseOperatorStack = SeasonStrate.bUsesRecipe;
|
|
||||||
Slot.Definition->StrateHeightInChunks = SeasonStrate.HeightInChunks;
|
Slot.Definition->StrateHeightInChunks = SeasonStrate.HeightInChunks;
|
||||||
Slot.Definition->GenerationParams = SeasonStrate.Params.TunnelNetworkParams;
|
Slot.Definition->GenerationParams = SeasonStrate.Params.TunnelNetworkParams;
|
||||||
Slot.Definition->SlabParams = SeasonStrate.Params.SlabParams;
|
Slot.Definition->SlabParams = SeasonStrate.Params.SlabParams;
|
||||||
@@ -1401,10 +1399,9 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
|
|||||||
: FString::Printf(TEXT("Value_%d"), static_cast<int32>(Slot.Definition->GeneratorType));
|
: FString::Printf(TEXT("Value_%d"), static_cast<int32>(Slot.Definition->GeneratorType));
|
||||||
VoxelForgeStartupTrace::RecordEvent(TEXT("strate_slot"), FString::Printf(
|
VoxelForgeStartupTrace::RecordEvent(TEXT("strate_slot"), FString::Printf(
|
||||||
TEXT("\"index\":%d,\"definition\":\"%s\",\"archetype\":\"%s\",\"top_chunk_z\":%d,"
|
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.StrateIndex, *DefinitionPath, *ArchetypeName,
|
||||||
Slot.TopChunkZ, Slot.BottomChunkZ, Slot.HeightInChunks,
|
Slot.TopChunkZ, Slot.BottomChunkZ, Slot.HeightInChunks));
|
||||||
Slot.Definition->bUseOperatorStack ? 1 : 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogTemp, Log, TEXT("[StrateManager] Strate %d: '%s' | Z chunks [%d to %d] | %d chunks tall"),
|
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);
|
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;
|
CachedSeed = WorldSeed;
|
||||||
bOpenSurfaceEntry = Settings->bOpenSurfaceEntry;
|
bOpenSurfaceEntry = Settings->bOpenSurfaceEntry;
|
||||||
OriginSpineRadius = bUseSeason
|
OriginSpineRadius = bUseSeason
|
||||||
@@ -6281,30 +6205,13 @@ bool UVoxelStrateManager::UsesOperatorStackForChunk(const FIntVector& ChunkCoord
|
|||||||
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkCoord.Z);
|
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkCoord.Z);
|
||||||
if (SlotIdx < 0 || !StrateLayout[SlotIdx].Definition) { return false; }
|
if (SlotIdx < 0 || !StrateLayout[SlotIdx].Definition) { return false; }
|
||||||
|
|
||||||
if (SeasonStrates.IsValidIndex(SlotIdx))
|
// 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
|
||||||
// A recipe is an explicit stack opt-in. Native fixed entries retain the authored/switch
|
// archetype in every case.
|
||||||
// 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
|
|
||||||
|
|
||||||
const UVoxelStrateDefinition* Def = StrateLayout[SlotIdx].Definition;
|
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é
|
// LA LISTE DES ARCHÉTYPES — le seul endroit où elle est écrite.
|
||||||
// ignore le drapeau et retombe sur le `switch`, pour qu'on puisse cocher la case sur n'importe
|
// THE ARCHETYPE LIST, written down exactly once.
|
||||||
// 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.
|
|
||||||
switch (Def->GeneratorType)
|
switch (Def->GeneratorType)
|
||||||
{
|
{
|
||||||
case ECaveGeneratorType::Maze: return true;
|
case ECaveGeneratorType::Maze: return true;
|
||||||
@@ -6327,16 +6234,13 @@ bool UVoxelStrateManager::UsesOperatorStackForChunk(const FIntVector& ChunkCoord
|
|||||||
|
|
||||||
case ECaveGeneratorType::Underwater:
|
case ECaveGeneratorType::Underwater:
|
||||||
// ⚠️ AUCUNE PILE À ELLE : `Underwater` EST `TunnelNetwork` plus un drapeau d'eau consommé
|
// ⚠️ 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é).
|
// que par `GetWaterLevel*` de ce manager — jamais par la densité (vérifié, pas supposé).
|
||||||
case ECaveGeneratorType::TunnelNetwork:
|
case ECaveGeneratorType::TunnelNetwork:
|
||||||
// Le plus gros : squelette SDF → douze modificateurs de détail → override d'op par salle,
|
// Le plus gros : squelette SDF → douze modificateurs de détail → override d'op par salle,
|
||||||
// 19 opérateurs, dont `FRoomGraphSource` qui **APPELLE** `BuildChunkCache`/`EvaluateSDFCached`
|
// 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
|
// 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.
|
// 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;
|
return true;
|
||||||
|
|
||||||
default: return false;
|
default: return false;
|
||||||
|
|||||||
@@ -1,34 +1,32 @@
|
|||||||
// VoxelDensityOp.h
|
// VoxelDensityOp.h
|
||||||
// LE CONTRAT de la pile d'opérateurs de densité / THE density operator stack CONTRACT.
|
// 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
|
// Design : Docs/archive/OPSTACK-PLAN.md. The contract is implemented by the stack path in
|
||||||
// UVoxelGenerator::GetDensityAt; the legacy archetype switch remains for non-opt-in strates.
|
// UVoxelGenerator::GetDensityAt, which generates every strate.
|
||||||
//
|
//
|
||||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||||
// POURQUOI / WHY
|
// POURQUOI / WHY
|
||||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||||
// UVoxelGenerator::GetDensityAt est aujourd'hui un `switch` sur 8 ECaveGeneratorType, chacun
|
// Chaque archétype (ECaveGeneratorType) est une composition d'opérateurs, pas une fonction de
|
||||||
// possédant sa fonction de densité et son struct de params. Conséquences : une nouvelle idée de
|
// densité monolithique qui posséderait le voxel entier. Une idée de monde s'exprime en empilant
|
||||||
// monde coûte ~6 sites d'édition, et surtout LES IDÉES NE PEUVENT PAS SE COMBINER — un archétype
|
// sources, combinateurs et modificateurs — et les idées peuvent donc SE COMBINER : « une strate de
|
||||||
// possède le voxel entier. On ne peut pas écrire « une strate de surface dont les montagnes
|
// surface dont les montagnes contiennent un réseau de salles, avec des îles flottantes au-dessus ».
|
||||||
// contiennent un réseau de salles, avec des îles flottantes dans le vide au-dessus », à aucun prix.
|
|
||||||
//
|
//
|
||||||
// GetDensityAt is today a `switch` over 8 ECaveGeneratorType, each owning a bespoke density
|
// Each archetype (ECaveGeneratorType) is a composition of operators, not a monolithic density
|
||||||
// function and param struct. A new world idea costs ~6 edit sites, and — the real problem —
|
// function that owns the whole voxel. A world idea is expressed by stacking sources, combiners and
|
||||||
// IDEAS CANNOT COMBINE: one archetype owns the whole voxel.
|
// modifiers — so ideas can COMBINE.
|
||||||
//
|
//
|
||||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||||
// ⚠️ CE N'EST PAS L'ANCIEN SYSTÈME DE « ROOM OPERATIONS » / THIS IS NOT THE OLD ROOM-OPS SYSTEM
|
// ⚠️ 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
|
// 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
|
// 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
|
// champ EST — c'est le rôle des sources de champ ci-dessous.
|
||||||
// cela aurait reconstruit le switch avec des étapes en plus.
|
|
||||||
//
|
//
|
||||||
// D'où QUATRE RÔLES, dont l'ancien système n'occupait que le troisième :
|
// 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
|
// 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
|
// 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
|
// 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.
|
// 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é.
|
// 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
|
// 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.
|
// 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
|
// Presque tout opérateur est UNIDIRECTIONNEL : il ne fait que creuser, ou que remplir. La direction
|
||||||
// existant est UNIDIRECTIONNEL : il ne fait que creuser, ou que remplir. Cela suffit à reproduire
|
// suffit donc à exprimer GÉNÉRIQUEMENT les gardes de ClassifyTile :
|
||||||
// GÉNÉRIQUEMENT chaque garde écrite à la main dans ClassifyTile :
|
|
||||||
//
|
//
|
||||||
// « passages ⇒ bCanSolid = false » EST CarveOnly
|
// « passages ⇒ bCanSolid = false » EST CarveOnly
|
||||||
// « ponts/arêtes ⇒ bCanAir = false » EST FillOnly
|
// « ponts/arêtes ⇒ bCanAir = false » EST FillOnly
|
||||||
@@ -71,26 +68,16 @@
|
|||||||
// it never lets an SDF writer impersonate a downstream neighbour.
|
// it never lets an SDF writer impersonate a downstream neighbour.
|
||||||
//
|
//
|
||||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||||
// LE GROS LOT PERF : les strates de grotte ne sautent AUCUNE tuile aujourd'hui
|
// CLASSIFYTILE
|
||||||
// THE PERF PRIZE: cave strates skip ZERO tiles today
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||||
// ClassifyTile conserve ses preuves exactes pour les gaps de bedrock et SurfaceWorld ; les
|
// ClassifyTile garde ses preuves exactes pour les gaps de bedrock et SurfaceWorld ; les slots de
|
||||||
// archétypes de cave opt-in passent maintenant par le même pliage `ClassifyBox`. Le post XY fournit
|
// cave passent par le pliage `ClassifyBox` de la pile même qui produit leur densité. Le post XY
|
||||||
// en plus la première preuve globale de coque : une tuile entièrement dans la bande forcée peut être
|
// 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.
|
// 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.**
|
// 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.
|
||||||
// É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.
|
|
||||||
//
|
//
|
||||||
// NOTE sur les UENUM : ces types sont volontairement du C++ nu (pas d'UHT, pas de .generated.h).
|
// 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
|
// Ils deviendront UENUM/USTRUCT en Phase 3, quand les opérateurs deviendront des data assets et
|
||||||
|
|||||||
@@ -2,33 +2,21 @@
|
|||||||
// La PILE : un conteneur ordonné d'opérateurs, plus les fabriques d'opérateurs concrets.
|
// 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.
|
// The STACK: an ordered container of operators, plus the concrete-operator factories.
|
||||||
//
|
//
|
||||||
// ⚠️ CECI ALIMENTE LE JEU, MAIS SEULEMENT SUR OPT-IN.
|
// ⚠️ CECI EST LE CHEMIN DE GÉNÉRATION DU JEU.
|
||||||
// `UVoxelGenerator::GetDensityAt` construit la pile par chunk et l'évalue à la place du `switch`
|
// `UVoxelGenerator::GetDensityAt` construit la pile par chunk pour chaque strate :
|
||||||
// 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 :
|
|
||||||
// **Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork,
|
// **Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork,
|
||||||
// Underwater (8 sur 8)**.
|
// Underwater (8 sur 8)**. `ClassifyTile` construit la même pile pour les archétypes de cave et plie
|
||||||
// Toute autre strate passe encore par le `switch`, inchangé.
|
// `ClassifyBox`; les gaps et SurfaceWorld gardent leurs preuves exactes dédiées.
|
||||||
// `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.
|
|
||||||
//
|
//
|
||||||
// THIS FEEDS THE GAME, BUT ONLY BEHIND AN OPT-IN. GetDensityAt builds the stack per chunk and
|
// THIS IS THE GAME'S GENERATION PATH. GetDensityAt builds the stack per chunk for every strate.
|
||||||
// evaluates it instead of the switch only when UsesOperatorStackForChunk returns true (strate ticked
|
// ClassifyTile uses the same stack for cave archetypes; gaps and SurfaceWorld retain their exact
|
||||||
// bUseOperatorStack AND its archetype is ported — all 8). ClassifyTile uses the same stack for cave
|
// hand-written proofs.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// POURQUOI CETTE FORME / WHY THIS SHAPE
|
// POURQUOI CETTE FORME / WHY THIS SHAPE
|
||||||
// La question à laquelle cette forme répond n'est pas « est-ce que ça marche ? » mais
|
// 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 ? »**
|
// **« 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
|
// (OPSTACK-PLAN §4). Chaque archétype a été porté puis comparé bit à bit à sa fonction
|
||||||
// MESURÉE plutôt qu'une opinion.
|
// d'origine avant qu'elle ne soit retirée : la réponse a été MESURÉE plutôt qu'une opinion.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ public:
|
|||||||
*
|
*
|
||||||
* GAP (bedrock), hors-layout (air constant) et SurfaceWorld plient leurs hypothèses par Z ;
|
* 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
|
* 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
|
* 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
|
* 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).
|
* GetDensityAt — un verdict Mixed laisse les colonnes chaudes pour la génération qui suit).
|
||||||
|
|||||||
@@ -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 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 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("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 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 Gap"), STAT_VoxelForgeCaveMixGap, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Surface World"), STAT_VoxelForgeCaveMixSurfaceWorld, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Surface World"), STAT_VoxelForgeCaveMixSurfaceWorld, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
|||||||
@@ -115,29 +115,6 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation")
|
||||||
ECaveGeneratorType GeneratorType = ECaveGeneratorType::TunnelNetwork;
|
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
|
* 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
|
* resolves a disabled strate to WormStrength == 0 before any fused, stack, composer, or
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ public:
|
|||||||
*
|
*
|
||||||
* Returns TunnelNetwork if the chunk is outside all strates or if the
|
* Returns TunnelNetwork if the chunk is outside all strates or if the
|
||||||
* strate definition is null. The generator uses this to pick which
|
* 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
|
* @param ChunkCoord - The chunk position
|
||||||
* @return The ECaveGeneratorType for the strate containing this chunk
|
* @return The ECaveGeneratorType for the strate containing this chunk
|
||||||
@@ -302,12 +302,9 @@ public:
|
|||||||
ECaveGeneratorType GetGeneratorTypeForChunk(const FIntVector& ChunkCoord) const;
|
ECaveGeneratorType GetGeneratorTypeForChunk(const FIntVector& ChunkCoord) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when this chunk's strate opts into the density OPERATOR STACK instead of the hardcoded
|
* True when this chunk lies in a strate slot whose archetype has an operator stack (all eight
|
||||||
* archetype switch (`UVoxelStrateDefinition::bUseOperatorStack`).
|
* do). False outside the layout or for a slot without a definition. This predicate is the ONLY
|
||||||
*
|
* place the archetype list is written down.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
bool UsesOperatorStackForChunk(const FIntVector& ChunkCoord) const;
|
bool UsesOperatorStackForChunk(const FIntVector& ChunkCoord) const;
|
||||||
|
|
||||||
|
|||||||
@@ -314,7 +314,6 @@ struct FExploreArguments
|
|||||||
bool bRender = true;
|
bool bRender = true;
|
||||||
bool bWalk = true;
|
bool bWalk = true;
|
||||||
bool bExport = true;
|
bool bExport = true;
|
||||||
bool bUseOperatorStack = true;
|
|
||||||
bool bWormsEnabledOverride = false;
|
bool bWormsEnabledOverride = false;
|
||||||
bool bWormsEnabled = true;
|
bool bWormsEnabled = true;
|
||||||
bool bWormStrengthOverride = false;
|
bool bWormStrengthOverride = false;
|
||||||
@@ -431,9 +430,6 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
|||||||
}
|
}
|
||||||
ModesText.TrimQuotesInline();
|
ModesText.TrimQuotesInline();
|
||||||
}
|
}
|
||||||
int32 UseOperatorStack = 1;
|
|
||||||
FParse::Value(*Params, TEXT("opstack="), UseOperatorStack);
|
|
||||||
OutArguments.bUseOperatorStack = UseOperatorStack != 0;
|
|
||||||
int32 WormsEnabled = 1;
|
int32 WormsEnabled = 1;
|
||||||
if (FParse::Value(*Params, TEXT("wormsenabled="), WormsEnabled))
|
if (FParse::Value(*Params, TEXT("wormsenabled="), WormsEnabled))
|
||||||
{
|
{
|
||||||
@@ -1218,9 +1214,6 @@ struct FExploreWorld
|
|||||||
Definition->GeneratorType = Index == Arguments.Slot
|
Definition->GeneratorType = Index == Arguments.Slot
|
||||||
? Arguments.Archetype
|
? Arguments.Archetype
|
||||||
: ECaveGeneratorType::TunnelNetwork;
|
: 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)
|
if (Arguments.bWormsEnabledOverride)
|
||||||
{
|
{
|
||||||
Definition->bEnableWorms = Arguments.bWormsEnabled;
|
Definition->bEnableWorms = Arguments.bWormsEnabled;
|
||||||
@@ -1376,7 +1369,7 @@ struct FExploreWorld
|
|||||||
const FStrateGenerationParams& ReportParams = Target.Definition->GenerationParams;
|
const FStrateGenerationParams& ReportParams = Target.Definition->GenerationParams;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeExplore][OwnerAssets] strate=%s settings=%s slot=%d "
|
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 "
|
"floor_switch_ignored=1 floor_terracing=%d floor_step=%.9g max_ledge=%.9g "
|
||||||
"gentle_slope_degrees=%.9g gentle_gradient=%.9g route_cap=%.9g "
|
"gentle_slope_degrees=%.9g gentle_gradient=%.9g route_cap=%.9g "
|
||||||
"wind_wave_cap=%d "
|
"wind_wave_cap=%d "
|
||||||
@@ -1385,7 +1378,7 @@ struct FExploreWorld
|
|||||||
bUseAuthoredStrate ? *Arguments.StrateReference : TEXT("synthetic"),
|
bUseAuthoredStrate ? *Arguments.StrateReference : TEXT("synthetic"),
|
||||||
bUseAuthoredSettings ? *Arguments.SettingsReference : TEXT("synthetic"),
|
bUseAuthoredSettings ? *Arguments.SettingsReference : TEXT("synthetic"),
|
||||||
Arguments.Slot, Settings->Seed, Target.Definition->StrateHeightInChunks,
|
Arguments.Slot, Settings->Seed, Target.Definition->StrateHeightInChunks,
|
||||||
ArchetypeName(TargetArchetype), Target.Definition->bUseOperatorStack ? 1 : 0,
|
ArchetypeName(TargetArchetype),
|
||||||
ReportParams.bTunnelFloorTerracingEnabled ? 1 : 0,
|
ReportParams.bTunnelFloorTerracingEnabled ? 1 : 0,
|
||||||
ReportParams.TunnelFloorTerraceStepHeight,
|
ReportParams.TunnelFloorTerraceStepHeight,
|
||||||
ReportParams.TunnelFloorMaxLedgeHeight,
|
ReportParams.TunnelFloorMaxLedgeHeight,
|
||||||
@@ -4453,7 +4446,6 @@ FString BuildManifestJson(
|
|||||||
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
||||||
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
||||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||||
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
|
||||||
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
|
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
|
||||||
if (Arguments.bWormsEnabledOverride)
|
if (Arguments.bWormsEnabledOverride)
|
||||||
{
|
{
|
||||||
@@ -6135,7 +6127,6 @@ FString BuildExploreJson(
|
|||||||
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
||||||
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
||||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||||
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
|
||||||
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
|
Writer->WriteValue(TEXT("worms_enabled_override"), Arguments.bWormsEnabledOverride);
|
||||||
if (Arguments.bWormsEnabledOverride)
|
if (Arguments.bWormsEnabledOverride)
|
||||||
{
|
{
|
||||||
@@ -6216,12 +6207,11 @@ FString BuildExploreJson(
|
|||||||
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
||||||
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
||||||
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
|
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=<ABSOLUTE_PLUGIN_SAVED_PATH>"),
|
"-out=<ABSOLUTE_PLUGIN_SAVED_PATH>"),
|
||||||
Arguments.Seed,
|
Arguments.Seed,
|
||||||
ArchetypeName(Arguments.Archetype),
|
ArchetypeName(Arguments.Archetype),
|
||||||
Arguments.Slot,
|
Arguments.Slot,
|
||||||
Arguments.bUseOperatorStack ? 1 : 0,
|
|
||||||
*Arguments.CanonicalModes(),
|
*Arguments.CanonicalModes(),
|
||||||
Arguments.bBlockEarlyOut ? 1 : 0));
|
Arguments.bBlockEarlyOut ? 1 : 0));
|
||||||
Writer->WriteObjectEnd();
|
Writer->WriteObjectEnd();
|
||||||
@@ -7203,7 +7193,6 @@ bool WriteExploreOpBoundsReport(
|
|||||||
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
||||||
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
||||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||||
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
|
||||||
Writer->WriteValue(TEXT("sample_step"), 1);
|
Writer->WriteValue(TEXT("sample_step"), 1);
|
||||||
Writer->WriteValue(TEXT("block_cells"), 8);
|
Writer->WriteValue(TEXT("block_cells"), 8);
|
||||||
Writer->WriteValue(TEXT("classifier_blocks_tested"), BlocksTested);
|
Writer->WriteValue(TEXT("classifier_blocks_tested"), BlocksTested);
|
||||||
@@ -7311,10 +7300,9 @@ bool RunOpBounds(
|
|||||||
FExploreBudget& Budget,
|
FExploreBudget& Budget,
|
||||||
FString& OutError)
|
FString& OutError)
|
||||||
{
|
{
|
||||||
if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork
|
if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork)
|
||||||
|| !Arguments.bUseOperatorStack)
|
|
||||||
{
|
{
|
||||||
OutError = TEXT("opbounds requires archetype=TunnelNetwork and opstack=1.");
|
OutError = TEXT("opbounds requires archetype=TunnelNetwork.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8262,8 +8250,6 @@ bool BuildBatchCaseParams(
|
|||||||
|
|
||||||
FString Modes = TEXT("export");
|
FString Modes = TEXT("export");
|
||||||
JsonString(Case, TEXT("modes"), Modes);
|
JsonString(Case, TEXT("modes"), Modes);
|
||||||
bool bOperatorStack = true;
|
|
||||||
JsonBool(Case, TEXT("operator_stack"), bOperatorStack);
|
|
||||||
bool bProfileDensity = false;
|
bool bProfileDensity = false;
|
||||||
JsonBool(Case, TEXT("profile_density"), bProfileDensity);
|
JsonBool(Case, TEXT("profile_density"), bProfileDensity);
|
||||||
bool bProfileDensityFull = false;
|
bool bProfileDensityFull = false;
|
||||||
@@ -8293,11 +8279,11 @@ bool BuildBatchCaseParams(
|
|||||||
Case, TEXT("tunnel_floor_terracing"), bTunnelFloorTerracing);
|
Case, TEXT("tunnel_floor_terracing"), bTunnelFloorTerracing);
|
||||||
|
|
||||||
OutParams = FString::Printf(
|
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 "
|
"-exportsize=%d -exportstep=%d -densitygridreuse=%d "
|
||||||
"-blockearlyout=%d -meshminbatch=%d "
|
"-blockearlyout=%d -meshminbatch=%d "
|
||||||
"-failurefocus=%d -out=\"%s\""),
|
"-failurefocus=%d -out=\"%s\""),
|
||||||
Seed, *Archetype, Slot, *Modes, bOperatorStack ? 1 : 0,
|
Seed, *Archetype, Slot, *Modes,
|
||||||
ExportSize, ExportStep, bReuseDensityGrid ? 1 : 0,
|
ExportSize, ExportStep, bReuseDensityGrid ? 1 : 0,
|
||||||
bBlockEarlyOut ? 1 : 0, MeshMinBatchSize,
|
bBlockEarlyOut ? 1 : 0, MeshMinBatchSize,
|
||||||
bFailureFocus ? 1 : 0, *CaseOutDirectory);
|
bFailureFocus ? 1 : 0, *CaseOutDirectory);
|
||||||
|
|||||||
@@ -633,7 +633,6 @@ function New-CommandletArguments([string]$CommandletOut, [string]$Modes, [bool]$
|
|||||||
[void]$args.Add('-seed=0')
|
[void]$args.Add('-seed=0')
|
||||||
[void]$args.Add('-archetype=TunnelNetwork')
|
[void]$args.Add('-archetype=TunnelNetwork')
|
||||||
[void]$args.Add("-slot=$Slot")
|
[void]$args.Add("-slot=$Slot")
|
||||||
[void]$args.Add('-opstack=1')
|
|
||||||
[void]$args.Add(('-modes="{0}"' -f $Modes))
|
[void]$args.Add(('-modes="{0}"' -f $Modes))
|
||||||
[void]$args.Add('-blockearlyout=0')
|
[void]$args.Add('-blockearlyout=0')
|
||||||
[void]$args.Add('-densitygridreuse=1')
|
[void]$args.Add('-densitygridreuse=1')
|
||||||
|
|||||||
Reference in new issue
Block a user