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
593 lines
26 KiB
C++
593 lines
26 KiB
C++
#if WITH_DEV_AUTOMATION_TESTS
|
|
|
|
#include "Misc/AutomationTest.h"
|
|
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "VoxelClipmapDesiredTiles.h"
|
|
#include "VoxelDensityOpStack.h"
|
|
#include "VoxelForgeTestFixture.h"
|
|
|
|
namespace
|
|
{
|
|
// The marching-cubes grid contains 32 cells, with one sample of halo on each side.
|
|
constexpr int32 MesherGridSize = 35;
|
|
constexpr int32 ArchetypeCount = 8;
|
|
constexpr int32 PassageGridCount = 2;
|
|
constexpr int32 GridSetCount = ArchetypeCount + PassageGridCount;
|
|
|
|
static bool SameFloatBits(float A, float B)
|
|
{
|
|
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
|
|
}
|
|
|
|
static FIntVector MakeMesherGridOrigin(const FIntVector& TargetChunk, int32 Level)
|
|
{
|
|
const int32 TileSpanChunks = 1 << Level;
|
|
const FIntVector TileCoord = VoxelClipmapDesiredTiles::FloorDiv(
|
|
TargetChunk, TileSpanChunks);
|
|
const int32 Step = 1 << Level;
|
|
return TileCoord * (CHUNK_SIZE << Level) - FIntVector(Step, Step, Step);
|
|
}
|
|
|
|
struct FGridComparison
|
|
{
|
|
int64 Samples = 0;
|
|
int32 Mismatches = 0;
|
|
FString FirstMismatch;
|
|
TArray<float> BlockDensities;
|
|
};
|
|
|
|
static bool BuildProductionStackForArchetype(
|
|
VoxelForgeTest::FTestWorld& World, ECaveGeneratorType Type,
|
|
const FIntVector& ChunkCoord, FVoxelOpStack& OutStack, FVoxelOpContext& OutContext)
|
|
{
|
|
const int32 Seed = World.Settings->GetEffectiveWorldSeed();
|
|
const float SpineRadius = World.Settings->GetEffectiveOriginSpineRadius();
|
|
const UVoxelStrateManager* Manager = World.StrateManager.Get();
|
|
OutContext = FVoxelOpContext();
|
|
OutContext.ChunkCoord = ChunkCoord;
|
|
OutContext.Seed = static_cast<uint32>(Seed);
|
|
OutContext.LayoutVersion = Manager->GetLayoutVersion();
|
|
OutContext.WorldRadiusVoxels = World.Settings->GetEffectiveWorldRadiusVoxels();
|
|
OutContext.EdgeSealThickness = World.Settings->EdgeSealThickness;
|
|
|
|
switch (Type)
|
|
{
|
|
case ECaveGeneratorType::FlatPlain:
|
|
case ECaveGeneratorType::CrystalChamber:
|
|
{
|
|
const FSlabGenerationParams P = Manager->GetSlabParamsForChunk(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
VoxelDensityOps::BuildSlabStack(OutStack, P, Seed, SpineRadius, Manager);
|
|
return true;
|
|
}
|
|
case ECaveGeneratorType::Maze:
|
|
{
|
|
const FMazeGenerationParams P = Manager->GetMazeParamsForChunk(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
VoxelDensityOps::BuildMazeStack(OutStack, P, Seed, SpineRadius, Manager);
|
|
return true;
|
|
}
|
|
case ECaveGeneratorType::SurfaceWorld:
|
|
{
|
|
const FSurfaceGenerationParams P = Manager->GetSurfaceParamsForChunk(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
// The generator-grid comparison below covers the resolved biome route. This builder
|
|
// exercises the production stack's base surface branch directly.
|
|
VoxelDensityOps::BuildSurfaceStack(
|
|
OutStack, P, Seed, SpineRadius, Manager, TArray<FSurfaceGenerationParams>(), nullptr);
|
|
return true;
|
|
}
|
|
case ECaveGeneratorType::VerticalShafts:
|
|
{
|
|
const FVerticalShaftParams P = Manager->GetVerticalShaftParamsForChunk(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
VoxelDensityOps::BuildVerticalShaftStack(OutStack, P, Seed, SpineRadius, Manager);
|
|
return true;
|
|
}
|
|
case ECaveGeneratorType::FloatingIslands:
|
|
{
|
|
const FFloatingIslandParams P = Manager->GetFloatingIslandParamsForChunk(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
VoxelDensityOps::BuildFloatingIslandStack(OutStack, P, Seed, SpineRadius, Manager);
|
|
return true;
|
|
}
|
|
case ECaveGeneratorType::TunnelNetwork:
|
|
case ECaveGeneratorType::Underwater:
|
|
{
|
|
const FStrateGenerationParams P = Manager->GetGenerationParams(ChunkCoord);
|
|
OutContext.StrateTopWorldZ = P.StrateTopWorldZ;
|
|
OutContext.StrateBottomWorldZ = P.StrateBottomWorldZ;
|
|
VoxelDensityOps::BuildTunnelNetworkStack(OutStack, P, Seed, SpineRadius, Manager);
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool FindChunkSampleRange(int32 AxisOrigin, int32 Step, int32 AxisSize,
|
|
int32 TargetChunk, int32& OutMin, int32& OutMax)
|
|
{
|
|
OutMin = INT32_MAX;
|
|
OutMax = INT32_MIN;
|
|
for (int32 Index = 0; Index < AxisSize; ++Index)
|
|
{
|
|
const int32 Coordinate = AxisOrigin + Index * Step;
|
|
if (FMath::FloorToInt(static_cast<float>(Coordinate) / CHUNK_SIZE) == TargetChunk)
|
|
{
|
|
OutMin = FMath::Min(OutMin, Index);
|
|
OutMax = FMath::Max(OutMax, Index);
|
|
}
|
|
}
|
|
return OutMin != INT32_MAX;
|
|
}
|
|
|
|
static FGridComparison CompareStackChunkBlockWithScalar(
|
|
FVoxelOpStack& Stack, FIntVector GridOrigin, int32 Step,
|
|
const FIntVector& TargetChunk)
|
|
{
|
|
FGridComparison Result;
|
|
int32 MinX = 0, MaxX = 0, MinY = 0, MaxY = 0, MinZ = 0, MaxZ = 0;
|
|
if (!FindChunkSampleRange(GridOrigin.X, Step, MesherGridSize, TargetChunk.X, MinX, MaxX)
|
|
|| !FindChunkSampleRange(GridOrigin.Y, Step, MesherGridSize, TargetChunk.Y, MinY, MaxY)
|
|
|| !FindChunkSampleRange(GridOrigin.Z, Step, MesherGridSize, TargetChunk.Z, MinZ, MaxZ))
|
|
{
|
|
Result.FirstMismatch = TEXT("mesher grid has no samples in the stack's target chunk");
|
|
return Result;
|
|
}
|
|
|
|
const int32 SizeX = MaxX - MinX + 1;
|
|
const int32 SizeY = MaxY - MinY + 1;
|
|
const int32 SizeZ = MaxZ - MinZ + 1;
|
|
const FIntVector Origin = GridOrigin + FIntVector(MinX * Step, MinY * Step, MinZ * Step);
|
|
const FIntVector Last = Origin + FIntVector(
|
|
(SizeX - 1) * Step, (SizeY - 1) * Step, (SizeZ - 1) * Step);
|
|
const FBox Box(
|
|
FVector((float)Origin.X, (float)Origin.Y, (float)Origin.Z),
|
|
FVector((float)Last.X, (float)Last.Y, (float)Last.Z));
|
|
|
|
TArray<int32> ActiveOps;
|
|
const int32 ActiveCount = Stack.BuildActiveOpList(Box, Step, Origin, ActiveOps);
|
|
TArray<FVoxelOpSample> Samples;
|
|
Samples.SetNumUninitialized(SizeX * SizeY * SizeZ);
|
|
for (FVoxelOpSample& Sample : Samples) { Sample = FVoxelOpSample(); }
|
|
|
|
FVoxelOpBlock Block;
|
|
Block.OriginVoxels = Origin;
|
|
Block.Step = Step;
|
|
Block.SizeX = SizeX;
|
|
Block.SizeY = SizeY;
|
|
Block.SizeZ = SizeZ;
|
|
Block.Samples = Samples.GetData();
|
|
Stack.EvalBlock(Block, ActiveOps);
|
|
|
|
for (int32 Z = 0; Z < SizeZ; ++Z)
|
|
for (int32 Y = 0; Y < SizeY; ++Y)
|
|
for (int32 X = 0; X < SizeX; ++X)
|
|
{
|
|
const FIntVector SamplePos = Origin + FIntVector(X * Step, Y * Step, Z * Step);
|
|
const float BlockDensity = Block.At(X, Y, Z).Density;
|
|
const float ScalarDensity = Stack.EvalInternal(
|
|
(float)SamplePos.X, (float)SamplePos.Y, (float)SamplePos.Z);
|
|
Result.BlockDensities.Add(BlockDensity);
|
|
++Result.Samples;
|
|
if (!SameFloatBits(BlockDensity, ScalarDensity))
|
|
{
|
|
++Result.Mismatches;
|
|
if (Result.FirstMismatch.IsEmpty())
|
|
{
|
|
Result.FirstMismatch = FString::Printf(
|
|
TEXT("box=[(%d,%d,%d)..(%d,%d,%d)] active_ops=%d/%d sample=(%d,%d,%d) block=%.9g scalar=%.9g delta=%.9g"),
|
|
Origin.X, Origin.Y, Origin.Z, Last.X, Last.Y, Last.Z,
|
|
ActiveCount, Stack.Num(), SamplePos.X, SamplePos.Y, SamplePos.Z,
|
|
BlockDensity, ScalarDensity, BlockDensity - ScalarDensity);
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static FGridComparison CompareGeneratorBlockWithScalar(
|
|
UVoxelGenerator& Generator, FIntVector Origin, int32 Step)
|
|
{
|
|
FGridComparison Result;
|
|
const int32 Count = MesherGridSize * MesherGridSize * MesherGridSize;
|
|
Result.BlockDensities.SetNumUninitialized(Count);
|
|
Generator.BeginDensityBlock(Origin, Step, MesherGridSize, MesherGridSize, MesherGridSize);
|
|
int32 Index = 0;
|
|
for (int32 Z = 0; Z < MesherGridSize; ++Z)
|
|
for (int32 Y = 0; Y < MesherGridSize; ++Y)
|
|
for (int32 X = 0; X < MesherGridSize; ++X, ++Index)
|
|
{
|
|
const FIntVector SamplePos = Origin + FIntVector(X * Step, Y * Step, Z * Step);
|
|
Result.BlockDensities[Index] = Generator.GetDensityAt(
|
|
(float)SamplePos.X, (float)SamplePos.Y, (float)SamplePos.Z);
|
|
}
|
|
Generator.EndDensityBlock();
|
|
|
|
Index = 0;
|
|
for (int32 Z = 0; Z < MesherGridSize; ++Z)
|
|
for (int32 Y = 0; Y < MesherGridSize; ++Y)
|
|
for (int32 X = 0; X < MesherGridSize; ++X, ++Index)
|
|
{
|
|
const FIntVector SamplePos = Origin + FIntVector(X * Step, Y * Step, Z * Step);
|
|
const float ScalarDensity = Generator.GetDensityAt(
|
|
(float)SamplePos.X, (float)SamplePos.Y, (float)SamplePos.Z);
|
|
++Result.Samples;
|
|
if (!SameFloatBits(Result.BlockDensities[Index], ScalarDensity))
|
|
{
|
|
++Result.Mismatches;
|
|
if (Result.FirstMismatch.IsEmpty())
|
|
{
|
|
Result.FirstMismatch = FString::Printf(
|
|
TEXT("sample=(%d,%d,%d) block=%.9g scalar=%.9g delta=%.9g"),
|
|
SamplePos.X, SamplePos.Y, SamplePos.Z, Result.BlockDensities[Index],
|
|
ScalarDensity, Result.BlockDensities[Index] - ScalarDensity);
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static int32 FindGapChunkZ(const VoxelForgeTest::FTestWorld& World)
|
|
{
|
|
const int32 Min = World.BottomChunkZ - 8;
|
|
const int32 Max = World.TopChunkZ + 8;
|
|
for (int32 ChunkZ = Min; ChunkZ <= Max; ++ChunkZ)
|
|
{
|
|
if (World.StrateManager->IsGapChunk(FIntVector(0, 0, ChunkZ)))
|
|
{
|
|
return ChunkZ;
|
|
}
|
|
}
|
|
return MIN_int32;
|
|
}
|
|
|
|
static int32 CountGapSamples(const VoxelForgeTest::FTestWorld& World,
|
|
FIntVector Origin, int32 Step)
|
|
{
|
|
TSet<int32> SampleChunkZs;
|
|
for (int32 Z = 0; Z < MesherGridSize; ++Z)
|
|
{
|
|
SampleChunkZs.Add(FMath::FloorToInt(
|
|
static_cast<float>(Origin.Z + Z * Step) / CHUNK_SIZE));
|
|
}
|
|
int32 Count = 0;
|
|
for (const int32 ChunkZ : SampleChunkZs)
|
|
{
|
|
if (World.StrateManager->IsGapChunk(FIntVector(0, 0, ChunkZ)))
|
|
{
|
|
++Count;
|
|
}
|
|
}
|
|
return Count;
|
|
}
|
|
|
|
struct FScopedConsoleIntOverride
|
|
{
|
|
IConsoleVariable* Variable = nullptr;
|
|
int32 OriginalValue = 0;
|
|
|
|
explicit FScopedConsoleIntOverride(const TCHAR* Name, int32 Value)
|
|
: Variable(IConsoleManager::Get().FindConsoleVariable(Name))
|
|
{
|
|
if (Variable != nullptr)
|
|
{
|
|
OriginalValue = Variable->GetInt();
|
|
Variable->Set(Value, ECVF_SetByCode);
|
|
}
|
|
}
|
|
|
|
~FScopedConsoleIntOverride()
|
|
{
|
|
if (Variable != nullptr)
|
|
{
|
|
Variable->Set(OriginalValue, ECVF_SetByCode);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FVoxelForgeDensityBlockEquivalenceTest,
|
|
"VoxelForge.Density.OperatorBlockMatchesScalarGrid",
|
|
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FVoxelForgeDensityBlockEquivalenceTest::RunTest(const FString& Parameters)
|
|
{
|
|
(void)Parameters;
|
|
|
|
// The production mesher initializes these once at startup. This headless test does so too,
|
|
// then temporarily disables the fused TunnelNetwork evaluator so the real per-chunk
|
|
// BeginDensityBlock -> BuildActiveOpList -> EvalBlock path is compared against scalar output.
|
|
VoxelGenLOD::InitializeConsoleSwitches();
|
|
FScopedConsoleIntOverride FusedEvaluatorOverride(TEXT("voxel.UseFusedEvaluator"), 0);
|
|
if (FusedEvaluatorOverride.Variable == nullptr)
|
|
{
|
|
AddError(TEXT("voxel.UseFusedEvaluator cvar is unavailable; block route was not forced"));
|
|
return false;
|
|
}
|
|
|
|
static const int32 Seeds[] = {1337, 9176, 17015, 24854, 32693, 40532};
|
|
static const ECaveGeneratorType Archetypes[ArchetypeCount] = {
|
|
ECaveGeneratorType::TunnelNetwork,
|
|
ECaveGeneratorType::FlatPlain,
|
|
ECaveGeneratorType::CrystalChamber,
|
|
ECaveGeneratorType::Maze,
|
|
ECaveGeneratorType::SurfaceWorld,
|
|
ECaveGeneratorType::VerticalShafts,
|
|
ECaveGeneratorType::FloatingIslands,
|
|
ECaveGeneratorType::Underwater,
|
|
};
|
|
|
|
int64 GeneratorSamples = 0;
|
|
int32 GeneratorMismatches = 0;
|
|
FString FirstGeneratorMismatch;
|
|
int64 DirectStackSamples = 0;
|
|
int32 DirectStackMismatches = 0;
|
|
FString FirstDirectStackMismatch;
|
|
int32 DirectStackBlocks = 0;
|
|
int32 GeneratorBlocks = 0;
|
|
int32 ProductionArchetypeBlocks = 0;
|
|
int32 BoundaryBlocks = 0;
|
|
int32 NearOriginBlocks = 0;
|
|
int32 FarOriginBlocks = 0;
|
|
int32 GapCoverageBlocks = 0;
|
|
int32 PassageFocusedBlocks = 0;
|
|
int32 SpecialGapCoverageBlocks = 0;
|
|
int32 EditedSamplesChanged = 0;
|
|
int32 AcceptedEdits = 0;
|
|
int32 ArchetypeLevelCounts[ArchetypeCount][6] = {};
|
|
|
|
for (int32 SeedIndex = 0; SeedIndex < UE_ARRAY_COUNT(Seeds); ++SeedIndex)
|
|
{
|
|
VoxelForgeTest::FTestWorld World;
|
|
World.Build(Seeds[SeedIndex], /*gap chunks=*/2);
|
|
if (!World.IsValid())
|
|
{
|
|
AddError(World.WhyInvalid());
|
|
return false;
|
|
}
|
|
// The fixture uses coarse-LOD edits up to 64 voxels wide. Keep the actual diff layer in
|
|
// the route while allowing those bounded test brushes.
|
|
World.DiffLayer->SetBudget(/*max mods=*/0, /*max radius=*/256.0f,
|
|
/*max accumulated volume=*/0.0f);
|
|
|
|
const TArray<FStrateSlot>& Slots = World.StrateManager->GetLayout();
|
|
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
|
|
if (Slots.Num() < ArchetypeCount || Passages.IsEmpty())
|
|
{
|
|
AddError(FString::Printf(
|
|
TEXT("seed %d is missing an archetype slot or passage fixture"), Seeds[SeedIndex]));
|
|
return false;
|
|
}
|
|
const int32 GapChunkZ = FindGapChunkZ(World);
|
|
if (GapChunkZ == MIN_int32)
|
|
{
|
|
AddError(FString::Printf(TEXT("seed %d produced no gap chunk fixture"), Seeds[SeedIndex]));
|
|
return false;
|
|
}
|
|
|
|
FIntVector GridOrigins[GridSetCount];
|
|
int32 GridSteps[GridSetCount] = {};
|
|
int32 GridArchetypes[GridSetCount] = {};
|
|
FGridComparison BaseResults[GridSetCount];
|
|
|
|
// One complete mesher lattice for each production archetype per seed. Rotating level by
|
|
// seed gives every archetype all levels 0..5 over the six seeds. The sample grids touch a
|
|
// strate edge, and cycle through origin, near-origin, and both far-field signs.
|
|
for (int32 ArchetypeIndex = 0; ArchetypeIndex < ArchetypeCount; ++ArchetypeIndex)
|
|
{
|
|
const int32 Level = (SeedIndex + ArchetypeIndex) % 6;
|
|
const int32 Step = 1 << Level;
|
|
const FStrateSlot& Slot = Slots[ArchetypeIndex];
|
|
const int32 TargetChunkZ = (SeedIndex & 1) ? Slot.BottomChunkZ : Slot.TopChunkZ;
|
|
FIntVector TargetChunk;
|
|
switch ((SeedIndex + ArchetypeIndex) % 4)
|
|
{
|
|
case 0: TargetChunk = FIntVector(0, 0, TargetChunkZ); break;
|
|
case 1: TargetChunk = FIntVector(128, 96, TargetChunkZ); ++FarOriginBlocks; break;
|
|
case 2: TargetChunk = FIntVector(8, -8, TargetChunkZ); ++NearOriginBlocks; break;
|
|
default: TargetChunk = FIntVector(-128, -96, TargetChunkZ); ++FarOriginBlocks; break;
|
|
}
|
|
if ((SeedIndex + ArchetypeIndex) % 4 == 0) { ++NearOriginBlocks; }
|
|
|
|
const int32 GridIndex = ArchetypeIndex;
|
|
GridOrigins[GridIndex] = MakeMesherGridOrigin(TargetChunk, Level);
|
|
GridSteps[GridIndex] = Step;
|
|
GridArchetypes[GridIndex] = ArchetypeIndex;
|
|
|
|
FVoxelOpStack Stack;
|
|
FVoxelOpContext Context;
|
|
if (!BuildProductionStackForArchetype(
|
|
World, Archetypes[ArchetypeIndex], TargetChunk, Stack, Context))
|
|
{
|
|
AddError(FString::Printf(
|
|
TEXT("seed %d archetype %d production stack could not be built"),
|
|
Seeds[SeedIndex], ArchetypeIndex));
|
|
return false;
|
|
}
|
|
Context.Step = 1;
|
|
Stack.PrepareChunk(Context);
|
|
const FGridComparison DirectComparison = CompareStackChunkBlockWithScalar(
|
|
Stack, GridOrigins[GridIndex], Step, TargetChunk);
|
|
DirectStackSamples += DirectComparison.Samples;
|
|
DirectStackMismatches += DirectComparison.Mismatches;
|
|
++DirectStackBlocks;
|
|
if (FirstDirectStackMismatch.IsEmpty() && DirectComparison.Mismatches > 0)
|
|
{
|
|
FirstDirectStackMismatch = FString::Printf(
|
|
TEXT("seed=%d archetype=%d level=%d chunk=%s %s"),
|
|
Seeds[SeedIndex], ArchetypeIndex, Level, *TargetChunk.ToString(),
|
|
*DirectComparison.FirstMismatch);
|
|
}
|
|
|
|
++ArchetypeLevelCounts[ArchetypeIndex][Level];
|
|
++ProductionArchetypeBlocks;
|
|
++BoundaryBlocks;
|
|
|
|
if (!World.StrateManager->UsesOperatorStackForChunk(TargetChunk)
|
|
|| World.StrateManager->GetGeneratorTypeForChunk(TargetChunk)
|
|
!= Archetypes[ArchetypeIndex])
|
|
{
|
|
AddError(FString::Printf(
|
|
TEXT("seed %d archetype %d target chunk did not route to its production operator stack"),
|
|
Seeds[SeedIndex], ArchetypeIndex));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Passage-mouth lattices centered in a real inter-strate gap exercise the routed
|
|
// TunnelNetwork/Underwater block implementation through boundaries and bedrock holes.
|
|
for (int32 PassageGrid = 0; PassageGrid < PassageGridCount; ++PassageGrid)
|
|
{
|
|
const int32 GridIndex = ArchetypeCount + PassageGrid;
|
|
const int32 ArchetypeIndex = PassageGrid == 0 ? 0 : 7;
|
|
const int32 Level = (SeedIndex + (PassageGrid == 0 ? 0 : 7)) % 6;
|
|
const int32 Step = 1 << Level;
|
|
const FVoxelPassage& Passage = Passages[(SeedIndex + PassageGrid) % Passages.Num()];
|
|
const FVector Focus = PassageGrid == 0 ? Passage.UpperPoint : Passage.LowerPoint;
|
|
const FIntVector TargetChunk(
|
|
FMath::FloorToInt(Focus.X / CHUNK_SIZE),
|
|
FMath::FloorToInt(Focus.Y / CHUNK_SIZE),
|
|
GapChunkZ);
|
|
GridOrigins[GridIndex] = MakeMesherGridOrigin(TargetChunk, Level);
|
|
GridSteps[GridIndex] = Step;
|
|
GridArchetypes[GridIndex] = ArchetypeIndex;
|
|
++PassageFocusedBlocks;
|
|
if (CountGapSamples(World, GridOrigins[GridIndex], Step) > 0)
|
|
{
|
|
++GapCoverageBlocks;
|
|
++SpecialGapCoverageBlocks;
|
|
}
|
|
}
|
|
|
|
// No-edit grids. In the two canonical network archetypes this enters the actual worker
|
|
// block session, which partitions each mesher grid by resolved chunk before running the
|
|
// active operator list. The six other archetypes run the scalar stack evaluator, as they
|
|
// do in production outside the block-safe network branch.
|
|
for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex)
|
|
{
|
|
BaseResults[GridIndex] = CompareGeneratorBlockWithScalar(
|
|
*World.Generator.Get(), GridOrigins[GridIndex], GridSteps[GridIndex]);
|
|
GeneratorSamples += BaseResults[GridIndex].Samples;
|
|
GeneratorMismatches += BaseResults[GridIndex].Mismatches;
|
|
++GeneratorBlocks;
|
|
if (FirstGeneratorMismatch.IsEmpty() && BaseResults[GridIndex].Mismatches > 0)
|
|
{
|
|
FirstGeneratorMismatch = FString::Printf(
|
|
TEXT("seed=%d archetype=%d level=%d edited=0 origin=%s %s"),
|
|
Seeds[SeedIndex], GridArchetypes[GridIndex],
|
|
FMath::FloorLog2(GridSteps[GridIndex]), *GridOrigins[GridIndex].ToString(),
|
|
*BaseResults[GridIndex].FirstMismatch);
|
|
}
|
|
}
|
|
|
|
// Put one real diff-layer box edit in every mesher grid, then compare the same exact
|
|
// density lattices again. The scalar pass below runs with these player edits present.
|
|
for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex)
|
|
{
|
|
const int32 Step = GridSteps[GridIndex];
|
|
FVoxelModification Edit;
|
|
Edit.Shape = EVoxelBrushShape::Box;
|
|
Edit.Center = FVector(GridOrigins[GridIndex]) + FVector(17 * Step);
|
|
Edit.BoxExtent = FVector(2.0f * Step);
|
|
Edit.Falloff = 1.0f;
|
|
Edit.Strength = -13.0f;
|
|
const TArray<FIntVector> ChangedChunks = World.DiffLayer->ApplyModification(Edit);
|
|
if (ChangedChunks.IsEmpty())
|
|
{
|
|
AddError(FString::Printf(
|
|
TEXT("seed %d edit for grid %d archetype %d was rejected"),
|
|
Seeds[SeedIndex], GridIndex, GridArchetypes[GridIndex]));
|
|
return false;
|
|
}
|
|
++AcceptedEdits;
|
|
}
|
|
|
|
for (int32 GridIndex = 0; GridIndex < GridSetCount; ++GridIndex)
|
|
{
|
|
const FGridComparison Edited = CompareGeneratorBlockWithScalar(
|
|
*World.Generator.Get(), GridOrigins[GridIndex], GridSteps[GridIndex]);
|
|
GeneratorSamples += Edited.Samples;
|
|
GeneratorMismatches += Edited.Mismatches;
|
|
++GeneratorBlocks;
|
|
if (FirstGeneratorMismatch.IsEmpty() && Edited.Mismatches > 0)
|
|
{
|
|
FirstGeneratorMismatch = FString::Printf(
|
|
TEXT("seed=%d archetype=%d level=%d edited=1 origin=%s %s"),
|
|
Seeds[SeedIndex], GridArchetypes[GridIndex],
|
|
FMath::FloorLog2(GridSteps[GridIndex]), *GridOrigins[GridIndex].ToString(),
|
|
*Edited.FirstMismatch);
|
|
}
|
|
|
|
const int32 SharedCount = FMath::Min(
|
|
Edited.BlockDensities.Num(), BaseResults[GridIndex].BlockDensities.Num());
|
|
for (int32 SampleIndex = 0; SampleIndex < SharedCount; ++SampleIndex)
|
|
{
|
|
if (!SameFloatBits(Edited.BlockDensities[SampleIndex],
|
|
BaseResults[GridIndex].BlockDensities[SampleIndex]))
|
|
{
|
|
++EditedSamplesChanged;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bEveryArchetypeCoveredEveryLevel = true;
|
|
for (int32 ArchetypeIndex = 0; ArchetypeIndex < ArchetypeCount; ++ArchetypeIndex)
|
|
{
|
|
for (int32 Level = 0; Level < 6; ++Level)
|
|
{
|
|
bEveryArchetypeCoveredEveryLevel &= ArchetypeLevelCounts[ArchetypeIndex][Level] > 0;
|
|
}
|
|
}
|
|
|
|
AddInfo(FString::Printf(
|
|
TEXT("BeginDensityBlock grid vs scalar GetDensityAt: seeds=%d archetype_grids=%d all_archetype_levels=%d boundary_grids=%d near_origin=%d far_origin=%d blocks=%d samples=%lld mismatches=%d gap_grids=%d passage_focused=%d accepted_edits=%d edited_samples_changed=%d first=%s"),
|
|
UE_ARRAY_COUNT(Seeds), ProductionArchetypeBlocks,
|
|
bEveryArchetypeCoveredEveryLevel ? 1 : 0, BoundaryBlocks, NearOriginBlocks,
|
|
FarOriginBlocks, GeneratorBlocks, static_cast<long long>(GeneratorSamples),
|
|
GeneratorMismatches, GapCoverageBlocks, PassageFocusedBlocks, AcceptedEdits,
|
|
EditedSamplesChanged,
|
|
FirstGeneratorMismatch.IsEmpty() ? TEXT("none") : *FirstGeneratorMismatch));
|
|
AddInfo(FString::Printf(
|
|
TEXT("chunk-local direct EvalBlock vs EvalInternal: blocks=%d mesher_samples=%lld mismatches=%d first=%s"),
|
|
DirectStackBlocks, static_cast<long long>(DirectStackSamples),
|
|
DirectStackMismatches,
|
|
FirstDirectStackMismatch.IsEmpty() ? TEXT("none") : *FirstDirectStackMismatch));
|
|
|
|
TestEqual(TEXT("all eight production operator archetypes get six complete mesher grids"),
|
|
ProductionArchetypeBlocks,
|
|
static_cast<int32>(UE_ARRAY_COUNT(Seeds)) * ArchetypeCount);
|
|
TestTrue(TEXT("every production operator archetype covers every meshing level 0..5"),
|
|
bEveryArchetypeCoveredEveryLevel);
|
|
TestEqual(TEXT("all production archetype grids touch a strate boundary"),
|
|
BoundaryBlocks, ProductionArchetypeBlocks);
|
|
TestTrue(TEXT("test includes near-origin and both-sign far-field grids"),
|
|
NearOriginBlocks > 0 && FarOriginBlocks > 0);
|
|
TestEqual(TEXT("each passage-focused grid includes a real gap sample"),
|
|
SpecialGapCoverageBlocks,
|
|
static_cast<int32>(UE_ARRAY_COUNT(Seeds)) * PassageGridCount);
|
|
TestEqual(TEXT("passage-focused grids cover both network archetypes per seed"),
|
|
PassageFocusedBlocks,
|
|
static_cast<int32>(UE_ARRAY_COUNT(Seeds)) * PassageGridCount);
|
|
TestEqual(TEXT("all block-session density grids match scalar output bit-for-bit"),
|
|
GeneratorMismatches, 0);
|
|
TestEqual(TEXT("all production stack chunk blocks match scalar bit-for-bit"),
|
|
DirectStackMismatches, 0);
|
|
TestEqual(TEXT("one valid player edit was applied to every tested grid"),
|
|
AcceptedEdits, static_cast<int32>(UE_ARRAY_COUNT(Seeds)) * GridSetCount);
|
|
TestTrue(TEXT("player edits alter samples consumed by the mesher"),
|
|
EditedSamplesChanged > 0);
|
|
return true;
|
|
}
|
|
|
|
#endif // WITH_DEV_AUTOMATION_TESTS
|