wip(streaming): LOD rings tile exactly once, cache race, reconcile 104x -- gate stress FAILS 3/4

- Clipmap: finer rings snap to child-pair boundaries, so no two desired tiles of different
  levels overlap. Owner config 635 overlapping pairs -> 0; LOD0 ring 343 -> 512 tiles.
  New test sweeps 6,174 cases; fails on 7486c29 as intended, passes here.
- Shared room-graph cache: Entry->Cache read/written only under the mutex (race fix).
- ReconcileReadyTransitionVisibility indexed: moving p95 32.0 ms -> 0.31 ms.
- Block-vs-scalar density equivalence test: 5.1M samples bit-identical; catches an injected
  optimistic EffectOverBox (5,612 mismatches).

UNVALIDATED for merge: collision-gate stress passed 1 of 4 runs on this code (fails at 4.56 m,
support collision unready at start); it passed on 7486c29. No control run yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-25 02:47:54 +02:00
1 parent 7486c299d4
commit 85a803b873
6 files changed
+1299 -50

No files matched your search

@@ -0,0 +1,385 @@
#if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "VoxelClipmapDesiredTiles.h"
namespace
{
using VoxelClipmapDesiredTiles::FParameters;
struct FOverlapMeasure
{
uint64 PairCount = 0;
uint64 PairwiseVolumeChunkCubed = 0;
};
static FOverlapMeasure MeasureOverlaps(const TArray<FVoxelTileKey>& Tiles)
{
TSet<FVoxelTileKey> TileSet;
int32 MaxLevel = 0;
for (const FVoxelTileKey& Tile : Tiles)
{
TileSet.Add(Tile);
MaxLevel = FMath::Max(MaxLevel, Tile.Level);
}
FOverlapMeasure Result;
for (const FVoxelTileKey& Fine : Tiles)
{
for (int32 CoarseLevel = Fine.Level + 1; CoarseLevel <= MaxLevel; ++CoarseLevel)
{
const int32 Divisor = 1 << (CoarseLevel - Fine.Level);
const FVoxelTileKey Coarse(
VoxelClipmapDesiredTiles::FloorDiv(Fine.Coord, Divisor), CoarseLevel);
if (TileSet.Contains(Coarse))
{
++Result.PairCount;
Result.PairwiseVolumeChunkCubed += (uint64(1) << (3 * Fine.Level));
}
}
}
return Result;
}
static bool IsFootprintCovered(const FVoxelTileKey& Tile,
const TSet<FVoxelTileKey>& CoveringTiles,
int32 MaxLevel, int32 MinChunkZ, int32 MaxChunkZ)
{
const int32 TileMinZ = Tile.Coord.Z * (1 << Tile.Level);
const int32 TileMaxZ = (Tile.Coord.Z + 1) * (1 << Tile.Level) - 1;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
{
return true;
}
if (CoveringTiles.Contains(Tile))
{
return true;
}
// A coarser dyadic tile covers this whole footprint when it is an ancestor.
for (int32 ParentLevel = Tile.Level + 1; ParentLevel <= MaxLevel; ++ParentLevel)
{
const int32 Divisor = 1 << (ParentLevel - Tile.Level);
const FVoxelTileKey Parent(
VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), ParentLevel);
if (CoveringTiles.Contains(Parent))
{
return true;
}
}
if (Tile.Level == 0)
{
return Tile.Coord.Z < MinChunkZ || Tile.Coord.Z > MaxChunkZ;
}
const int32 ChildLevel = Tile.Level - 1;
const FIntVector ChildBase = Tile.Coord * 2;
for (int32 Z = 0; Z < 2; ++Z)
for (int32 Y = 0; Y < 2; ++Y)
for (int32 X = 0; X < 2; ++X)
{
if (!IsFootprintCovered(
FVoxelTileKey(ChildBase + FIntVector(X, Y, Z), ChildLevel),
CoveringTiles, MaxLevel, MinChunkZ, MaxChunkZ))
{
return false;
}
}
return true;
}
static bool CoversEveryLegacyTile(const TArray<FVoxelTileKey>& Legacy,
const TArray<FVoxelTileKey>& Current,
int32 MinChunkZ, int32 MaxChunkZ)
{
TSet<FVoxelTileKey> CurrentSet;
int32 MaxLevel = 0;
for (const FVoxelTileKey& Tile : Current)
{
CurrentSet.Add(Tile);
MaxLevel = FMath::Max(MaxLevel, Tile.Level);
}
for (const FVoxelTileKey& Tile : Legacy)
{
if (!IsFootprintCovered(Tile, CurrentSet, MaxLevel, MinChunkZ, MaxChunkZ))
{
return false;
}
}
return true;
}
// Frozen copy of the 7486c29 selector. It is the coverage oracle and keeps the regression
// test able to prove that an exact tiling still covers every volume the old selector covered.
static void Build7486c29(const FIntVector& Center, int32 MinChunkZ, int32 MaxChunkZ,
const FParameters& Parameters, TArray<FVoxelTileKey>& OutTiles)
{
OutTiles.Reset();
const int32 Radius = FMath::Max(1, Parameters.ClipRadius);
const int32 MaxLevel = FMath::Clamp(Parameters.MaxClipLevel, 0, 8);
const int32 OuterRadius = VoxelClipmapDesiredTiles::OuterShellRadius(
Parameters, Radius, MaxLevel);
int32 SheetLevel = MaxLevel;
int32 SheetRadius = OuterRadius;
VoxelClipmapDesiredTiles::OuterShell(
Parameters, Radius, MaxLevel, SheetLevel, SheetRadius);
const bool bSheetRing = SheetLevel > MaxLevel;
const int32 MaxMCRingLevel = bSheetRing ? SheetLevel - 1 : MaxLevel;
for (int32 Level = 0; Level <= MaxMCRingLevel; ++Level)
{
const int32 Pow = 1 << Level;
const FIntVector CenterLevel = VoxelClipmapDesiredTiles::FloorDiv(Center, Pow);
const FIntVector CenterFiner = Level > 0
? VoxelClipmapDesiredTiles::FloorDiv(Center, Pow >> 1)
: FIntVector::ZeroValue;
const int32 LevelRadius = (Level == MaxLevel && !bSheetRing)
? OuterRadius : Radius;
int32 DeltaZMin = -LevelRadius;
int32 DeltaZMax = LevelRadius;
if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{
DeltaZMin = FMath::Max(
DeltaZMin, VoxelClipmapDesiredTiles::FloorDiv(MinChunkZ, Pow) - CenterLevel.Z);
DeltaZMax = FMath::Min(
DeltaZMax, VoxelClipmapDesiredTiles::FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z);
}
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
for (int32 DeltaY = -LevelRadius; DeltaY <= LevelRadius; ++DeltaY)
for (int32 DeltaX = -LevelRadius; DeltaX <= LevelRadius; ++DeltaX)
{
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ);
const int32 TileMinZ = Tile.Z << Level;
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
{
continue;
}
if (Level > 0)
{
const bool bCovered =
(2 * Tile.X >= CenterFiner.X - Radius)
&& (2 * Tile.X + 1 <= CenterFiner.X + Radius)
&& (2 * Tile.Y >= CenterFiner.Y - Radius)
&& (2 * Tile.Y + 1 <= CenterFiner.Y + Radius)
&& (2 * Tile.Z >= CenterFiner.Z - Radius)
&& (2 * Tile.Z + 1 <= CenterFiner.Z + Radius);
if (bCovered)
{
continue;
}
}
OutTiles.Emplace(Tile, Level);
}
}
if (bSheetRing)
{
const int32 SheetPow = 1 << SheetLevel;
const FIntVector CenterSheet = VoxelClipmapDesiredTiles::FloorDiv(Center, SheetPow);
const FIntVector CenterMC = VoxelClipmapDesiredTiles::FloorDiv(
Center, 1 << MaxMCRingLevel);
const int32 LevelDelta = SheetLevel - MaxMCRingLevel;
int32 DeltaZMin = -SheetRadius;
int32 DeltaZMax = SheetRadius;
if (MinChunkZ != MIN_int32)
{
DeltaZMin = FMath::Max(DeltaZMin,
VoxelClipmapDesiredTiles::FloorDiv(MinChunkZ, SheetPow) - CenterSheet.Z);
DeltaZMax = FMath::Min(DeltaZMax,
VoxelClipmapDesiredTiles::FloorDiv(MaxChunkZ, SheetPow) - CenterSheet.Z);
}
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
for (int32 DeltaY = -SheetRadius; DeltaY <= SheetRadius; ++DeltaY)
for (int32 DeltaX = -SheetRadius; DeltaX <= SheetRadius; ++DeltaX)
{
const FIntVector Tile = CenterSheet + FIntVector(DeltaX, DeltaY, DeltaZ);
const int32 TileMinZ = Tile.Z << SheetLevel;
const int32 TileMaxZ = ((Tile.Z + 1) << SheetLevel) - 1;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
{
continue;
}
const bool bCovered =
((Tile.X << LevelDelta) >= CenterMC.X - Radius)
&& ((((Tile.X + 1) << LevelDelta) - 1) <= CenterMC.X + Radius)
&& ((Tile.Y << LevelDelta) >= CenterMC.Y - Radius)
&& ((((Tile.Y + 1) << LevelDelta) - 1) <= CenterMC.Y + Radius)
&& ((Tile.Z << LevelDelta) >= CenterMC.Z - Radius)
&& ((((Tile.Z + 1) << LevelDelta) - 1) <= CenterMC.Z + Radius);
if (!bCovered)
{
OutTiles.Emplace(Tile, SheetLevel);
}
}
}
}
static FString DescribeMeasure(const FOverlapMeasure& Measure)
{
return FString::Printf(TEXT("pairs=%llu pairwise_volume_chunk3=%llu"),
static_cast<unsigned long long>(Measure.PairCount),
static_cast<unsigned long long>(Measure.PairwiseVolumeChunkCubed));
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeClipmapNoOverlapTest,
"VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeClipmapNoOverlapTest::RunTest(const FString& Parameters)
{
(void)Parameters;
FParameters Owner;
Owner.ClipRadius = 3;
Owner.MaxClipLevel = 5;
Owner.RenderDistanceChunks = 768;
Owner.bFarSheetRing = false;
FParameters Defaults;
TArray<FVoxelTileKey> LegacyOwner, CurrentOwner, LegacyDefaults, CurrentDefaults;
Build7486c29(FIntVector::ZeroValue, MIN_int32, MAX_int32, Owner, LegacyOwner);
VoxelClipmapDesiredTiles::Build(
FIntVector::ZeroValue, MIN_int32, MAX_int32, Owner, CurrentOwner);
Build7486c29(FIntVector::ZeroValue, MIN_int32, MAX_int32, Defaults, LegacyDefaults);
VoxelClipmapDesiredTiles::Build(
FIntVector::ZeroValue, MIN_int32, MAX_int32, Defaults, CurrentDefaults);
const FOverlapMeasure LegacyOwnerOverlap = MeasureOverlaps(LegacyOwner);
const FOverlapMeasure CurrentOwnerOverlap = MeasureOverlaps(CurrentOwner);
const FOverlapMeasure LegacyDefaultOverlap = MeasureOverlaps(LegacyDefaults);
const FOverlapMeasure CurrentDefaultOverlap = MeasureOverlaps(CurrentDefaults);
AddInfo(FString::Printf(
TEXT("owner center=(0,0,0): before tiles=%d %s; after tiles=%d %s"),
LegacyOwner.Num(), *DescribeMeasure(LegacyOwnerOverlap),
CurrentOwner.Num(), *DescribeMeasure(CurrentOwnerOverlap)));
AddInfo(FString::Printf(
TEXT("FParameters defaults center=(0,0,0): before tiles=%d %s; after tiles=%d %s"),
LegacyDefaults.Num(), *DescribeMeasure(LegacyDefaultOverlap),
CurrentDefaults.Num(), *DescribeMeasure(CurrentDefaultOverlap)));
TestTrue(TEXT("7486c29 owner overlap measurement matches the checked source geometry"),
LegacyOwnerOverlap.PairCount == 635
&& LegacyOwnerOverlap.PairwiseVolumeChunkCubed == 594487);
TestTrue(TEXT("7486c29 FParameters-default overlap measurement matches the checked source geometry"),
LegacyDefaultOverlap.PairCount == 508
&& LegacyDefaultOverlap.PairwiseVolumeChunkCubed == 74295);
TestTrue(TEXT("owner desired set has no overlapping level pairs"),
CurrentOwnerOverlap.PairCount == 0 && CurrentOwnerOverlap.PairwiseVolumeChunkCubed == 0);
TestTrue(TEXT("default desired set has no overlapping level pairs"),
CurrentDefaultOverlap.PairCount == 0 && CurrentDefaultOverlap.PairwiseVolumeChunkCubed == 0);
TestTrue(TEXT("owner coverage is identical or larger than 7486c29"),
CoversEveryLegacyTile(LegacyOwner, CurrentOwner, MIN_int32, MAX_int32));
TestTrue(TEXT("default coverage is identical or larger than 7486c29"),
CoversEveryLegacyTile(LegacyDefaults, CurrentDefaults, MIN_int32, MAX_int32));
struct FSweepSettings
{
int32 Radius;
int32 MaxLevel;
int32 RenderDistance;
bool bSheet;
int32 SheetSpan;
};
static const FSweepSettings SweepSettings[] = {
{1, 0, 0, false, 2},
{2, 2, 0, false, 2},
{3, 4, 0, false, 2},
{2, 3, 49, false, 2},
{2, 3, 49, true, 2},
{2, 4, 87, true, 3},
};
int32 SweptCases = 0;
int32 OverlapCases = 0;
int32 CoverageFailures = 0;
uint64 TotalOverlapPairs = 0;
FString FirstFailure;
const int32 CenterMin = -3;
const int32 CenterMax = 3;
for (int32 Z = CenterMin; Z <= CenterMax; ++Z)
for (int32 Y = CenterMin; Y <= CenterMax; ++Y)
for (int32 X = CenterMin; X <= CenterMax; ++X)
for (const FSweepSettings& Sweep : SweepSettings)
{
FParameters P;
P.ClipRadius = Sweep.Radius;
P.MaxClipLevel = Sweep.MaxLevel;
P.RenderDistanceChunks = Sweep.RenderDistance;
P.bFarSheetRing = Sweep.bSheet;
P.FarSheetSpanLevels = Sweep.SheetSpan;
// Exercise the unbounded view, a tight local clamp, and a clamp crossing a tile boundary.
for (int32 ClampCase = 0; ClampCase < 3; ++ClampCase)
{
int32 MinZ = MIN_int32;
int32 MaxZ = MAX_int32;
if (ClampCase == 1)
{
MinZ = Z - 2;
MaxZ = Z + 3;
}
else if (ClampCase == 2)
{
const int32 Band = VoxelClipmapDesiredTiles::FloorDiv(Z, 1 << Sweep.MaxLevel);
MinZ = (Band << Sweep.MaxLevel) - 1;
MaxZ = MinZ + 6;
}
const FIntVector Center(X, Y, Z);
TArray<FVoxelTileKey> Legacy, Current;
Build7486c29(Center, MinZ, MaxZ, P, Legacy);
VoxelClipmapDesiredTiles::Build(Center, MinZ, MaxZ, P, Current);
++SweptCases;
const FOverlapMeasure CurrentOverlap = MeasureOverlaps(Current);
if (CurrentOverlap.PairCount != 0)
{
++OverlapCases;
TotalOverlapPairs += CurrentOverlap.PairCount;
if (FirstFailure.IsEmpty())
{
FirstFailure = FString::Printf(
TEXT("overlap center=(%d,%d,%d) radius=%d level=%d distance=%d sheet=%d clamp=%d: %s"),
X, Y, Z, Sweep.Radius, Sweep.MaxLevel, Sweep.RenderDistance,
Sweep.bSheet ? 1 : 0, ClampCase, *DescribeMeasure(CurrentOverlap));
}
}
if (!CoversEveryLegacyTile(Legacy, Current, MinZ, MaxZ))
{
++CoverageFailures;
if (FirstFailure.IsEmpty())
{
FirstFailure = FString::Printf(
TEXT("coverage center=(%d,%d,%d) radius=%d level=%d distance=%d sheet=%d clamp=%d"),
X, Y, Z, Sweep.Radius, Sweep.MaxLevel, Sweep.RenderDistance,
Sweep.bSheet ? 1 : 0, ClampCase);
}
}
}
}
AddInfo(FString::Printf(
TEXT("sweep cases=%d centers=343 settings=%d clamps=3 overlap_cases=%d overlap_pairs=%llu coverage_failures=%d first=%s"),
SweptCases, UE_ARRAY_COUNT(SweepSettings), OverlapCases,
static_cast<unsigned long long>(TotalOverlapPairs), CoverageFailures,
FirstFailure.IsEmpty() ? TEXT("none") : *FirstFailure));
TestEqual(TEXT("all swept desired sets have zero overlapping pairs"), OverlapCases, 0);
TestEqual(TEXT("all swept desired sets cover the legacy footprint"), CoverageFailures, 0);
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS
@@ -0,0 +1,592 @@
#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, /*operator stack=*/true);
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 legacy density routes still run their production scalar
// evaluator, as they do 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
@@ -24,6 +24,27 @@ namespace VoxelClipmapDesiredTiles
FloorDiv(Value.Z, Divisor));
}
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{
return -FloorDiv(-Value, Divisor);
}
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
FIntVector& OutMin, FIntVector& OutMax)
{
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax)
{
const int32 BaseMin = CenterAxis - Radius;
const int32 BaseEnd = CenterAxis + Radius + 1;
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1;
};
AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
AlignAxis(Center.Z, OutMin.Z, OutMax.Z);
}
FORCEINLINE int32 OuterShellRadius(const FParameters& Parameters, int32 Radius, int32 MaxLevel)
{
if (Parameters.RenderDistanceChunks <= 0)
@@ -81,18 +102,35 @@ namespace VoxelClipmapDesiredTiles
OuterShell(Parameters, Radius, MaxLevel, SheetLevel, SheetRadius);
const bool bSheetRing = SheetLevel > MaxLevel;
const int32 MaxMCRingLevel = bSheetRing ? SheetLevel - 1 : MaxLevel;
FIntVector PreviousRingMin = FIntVector::ZeroValue;
FIntVector PreviousRingMax = FIntVector::ZeroValue;
FIntVector MCRingMin = FIntVector::ZeroValue;
FIntVector MCRingMax = FIntVector::ZeroValue;
for (int32 Level = 0; Level <= MaxMCRingLevel; ++Level)
{
const int32 Pow = 1 << Level;
const FIntVector CenterLevel = FloorDiv(Center, Pow);
const FIntVector CenterFiner = Level > 0
? FloorDiv(Center, Pow >> 1) : FIntVector::ZeroValue;
const int32 LevelRadius = (Level == MaxLevel && !bSheetRing)
? OuterRadius : Radius;
int32 DeltaZMin = -LevelRadius;
int32 DeltaZMax = LevelRadius;
// Every finer shell must end on a complete child-pair boundary so a coarser tile is
// either wholly inside it or wholly outside it. The final MC ring uses the sheet's
// grouping width when a sheet ring follows it; otherwise the ordinary LOD boundary
// only needs pair alignment.
const int32 RingAlignment = Level < MaxMCRingLevel
? 2
: (bSheetRing ? (1 << (SheetLevel - MaxMCRingLevel)) : 1);
FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
if (Level == MaxMCRingLevel)
{
MCRingMin = RingMin;
MCRingMax = RingMax;
}
int32 DeltaZMin = RingMin.Z - CenterLevel.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z);
@@ -100,8 +138,10 @@ namespace VoxelClipmapDesiredTiles
}
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
for (int32 DeltaY = -LevelRadius; DeltaY <= LevelRadius; ++DeltaY)
for (int32 DeltaX = -LevelRadius; DeltaX <= LevelRadius; ++DeltaX)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y;
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
{
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ);
const int32 TileMinZ = Tile.Z << Level;
@@ -114,12 +154,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0)
{
const bool bCovered =
(2 * Tile.X >= CenterFiner.X - Radius)
&& (2 * Tile.X + 1 <= CenterFiner.X + Radius)
&& (2 * Tile.Y >= CenterFiner.Y - Radius)
&& (2 * Tile.Y + 1 <= CenterFiner.Y + Radius)
&& (2 * Tile.Z >= CenterFiner.Z - Radius)
&& (2 * Tile.Z + 1 <= CenterFiner.Z + Radius);
(2 * Tile.X >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z);
if (bCovered)
{
continue;
@@ -128,14 +168,17 @@ namespace VoxelClipmapDesiredTiles
OutTiles.Emplace(Tile, Level);
}
PreviousRingMin = RingMin;
PreviousRingMax = RingMax;
}
if (bSheetRing)
{
const int32 SheetPow = 1 << SheetLevel;
const FIntVector CenterSheet = FloorDiv(Center, SheetPow);
const FIntVector CenterMC = FloorDiv(Center, 1 << MaxMCRingLevel);
const int32 LevelDelta = SheetLevel - MaxMCRingLevel;
const int32 MCPerSheet = 1 << LevelDelta;
int32 DeltaZMin = -SheetRadius;
int32 DeltaZMax = SheetRadius;
@@ -160,12 +203,12 @@ namespace VoxelClipmapDesiredTiles
}
const bool bCovered =
((Tile.X << LevelDelta) >= CenterMC.X - Radius)
&& ((((Tile.X + 1) << LevelDelta) - 1) <= CenterMC.X + Radius)
&& ((Tile.Y << LevelDelta) >= CenterMC.Y - Radius)
&& ((((Tile.Y + 1) << LevelDelta) - 1) <= CenterMC.Y + Radius)
&& ((Tile.Z << LevelDelta) >= CenterMC.Z - Radius)
&& ((((Tile.Z + 1) << LevelDelta) - 1) <= CenterMC.Z + Radius);
(Tile.X * MCPerSheet >= MCRingMin.X)
&& ((Tile.X + 1) * MCPerSheet - 1 <= MCRingMax.X)
&& (Tile.Y * MCPerSheet >= MCRingMin.Y)
&& ((Tile.Y + 1) * MCPerSheet - 1 <= MCRingMax.Y)
&& (Tile.Z * MCPerSheet >= MCRingMin.Z)
&& ((Tile.Z + 1) * MCPerSheet - 1 <= MCRingMax.Z);
if (!bCovered)
{
OutTiles.Emplace(Tile, SheetLevel);
@@ -3407,6 +3407,16 @@ namespace
// unknown/evicted proof data therefore falls back to sampling and cannot change the mesh.
constexpr uint64 SharedRoomGraphCacheBudgetBytes = 256ull * 1024ull * 1024ull;
static TSharedPtr<const FChunkSDFCache, ESPMode::ThreadSafe>
SnapshotSharedRoomGraphCache(
const TSharedPtr<FSharedRoomGraphCacheEntry, ESPMode::ThreadSafe>& Entry)
{
FScopeLock Lock(&GSharedRoomGraphCacheMutex);
return Entry.IsValid()
? Entry->Cache
: TSharedPtr<const FChunkSDFCache, ESPMode::ThreadSafe>();
}
int32 SharedRoomGraphCacheBuildBank(int32 RegionSize)
{
return RegionSize <= 2 * CHUNK_SIZE ? 0 : 1;
@@ -3593,8 +3603,11 @@ namespace
Entry->RegionSize = RegionSize;
Entry->LastUse = 0;
Entry->AllocatedBytes = AllocatedBytes;
{
FScopeLock Lock(&GSharedRoomGraphCacheMutex);
Entry->Cache = BuiltCache;
}
}
else
{
FScopeLock Lock(&GSharedRoomGraphCacheMutex);
@@ -3647,7 +3660,7 @@ namespace
return Entry;
}
if (Entry->Cache.IsValid())
if (SnapshotSharedRoomGraphCache(Entry).IsValid())
{
return Entry;
}
@@ -4119,9 +4132,13 @@ namespace
if (SampleStep > 1)
{
TSharedPtr<const FChunkSDFCache, ESPMode::ThreadSafe> SharedRoomGraphCache =
S.SharedCacheEntry.IsValid()
? SnapshotSharedRoomGraphCache(S.SharedCacheEntry)
: TSharedPtr<const FChunkSDFCache, ESPMode::ThreadSafe>();
const bool bSharedCacheMatches =
S.SharedCacheEntry.IsValid()
&& S.SharedCacheEntry->Cache.IsValid()
&& SharedRoomGraphCache.IsValid()
&& S.SharedCacheEntry->Seed == SeedU
&& S.SharedCacheEntry->StrateIndex == StrateIdx
&& S.SharedCacheEntry->ParamsFingerprint == ParamsFingerprint
@@ -4143,8 +4160,9 @@ namespace
(float)(RegionMinY + RegionSize) + CacheExpansion,
P,
ERoomGraphBuildSite::OpShared);
SharedRoomGraphCache = SnapshotSharedRoomGraphCache(S.SharedCacheEntry);
}
S.ActiveCache = S.SharedCacheEntry->Cache.Get();
S.ActiveCache = SharedRoomGraphCache.Get();
}
else
{
@@ -5109,7 +5127,9 @@ namespace
* VOXEL_NOISE_SCALE * VF_PerlinAbsBound + 2.0f),
P,
ERoomGraphBuildSite::ClassifierShared);
B.ActiveCache = SharedCache->Cache.Get();
const TSharedPtr<const FChunkSDFCache, ESPMode::ThreadSafe>
SharedRoomGraphCache = SnapshotSharedRoomGraphCache(SharedCache);
B.ActiveCache = SharedRoomGraphCache.Get();
B.CacheWindowSharedEntry = SharedCache;
B.CacheWindowSharedRegionMinX = SharedRegionMinX;
B.CacheWindowSharedRegionMinY = SharedRegionMinY;
@@ -5165,7 +5185,8 @@ namespace
}
else if (B.bCacheWindowUsesShared)
{
B.ActiveCache = B.CacheWindowSharedEntry->Cache.Get();
B.ActiveCache = SnapshotSharedRoomGraphCache(
B.CacheWindowSharedEntry).Get();
}
// Large root boxes are only a cache warm-up. Their interval is intentionally still
// unknown; proof resumes at bounded descendants where all bounds below are finite.
+213 -26
View File
@@ -592,6 +592,8 @@ void AVoxelWorld::RegenerateAllChunks()
bAllChunksLoaded = false;
DesiredSorted.Reset();
CriticalDesiredTiles.Reset();
DesiredTransitionDescendants.Reset();
DesiredTransitionMaxLevel = 0;
DesiredStamped.Reset();
++DesiredEpoch;
TransitionHold.Reset();
@@ -1421,6 +1423,7 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
VoxelForgeStartupTrace::Finish(TEXT("end_play_before_steady_state"));
}
LogReadyTransitionVisibilityMeasure();
Super::EndPlay(EndPlayReason);
}
@@ -1429,6 +1432,17 @@ void AVoxelWorld::BeginPlay()
VoxelForgeStartupTrace::BeginFromCommandLine();
VoxelForgeStartupTrace::FStageScope StartupTraceStage(TEXT("BeginPlay"));
Super::BeginPlay();
int32 MeasureReadyTransitionVisibility = 0;
FParse::Value(FCommandLine::Get(),
TEXT("voxel.MeasureReadyTransitionVisibility="), MeasureReadyTransitionVisibility);
bMeasureReadyTransitionVisibility = MeasureReadyTransitionVisibility != 0;
ReadyTransitionVisibilitySamples.Reset();
FarFieldSubmitSamples.Reset();
if (bMeasureReadyTransitionVisibility)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeReadyVisibilityMeasure] enabled=1 instrumentation=command_line"));
}
VoxelCaveMorphology::ConfigurePlayerFitMemoFromCommandLine();
VoxelCaveMorphology::ResetPlayerFitMemoStats();
bShuttingDown.store(false, std::memory_order_relaxed);
@@ -3724,6 +3738,90 @@ void AVoxelWorld::LogVisualStalenessAudit()
VisualAuditLODOverlapMaxPairs);
}
void AVoxelWorld::LogReadyTransitionVisibilityMeasure()
{
if (!bMeasureReadyTransitionVisibility)
{
return;
}
if (!ReadyTransitionVisibilitySamples.IsEmpty())
{
TArray<FReadyTransitionVisibilitySample> Sorted = ReadyTransitionVisibilitySamples;
Sorted.Sort([](const FReadyTransitionVisibilitySample& A,
const FReadyTransitionVisibilitySample& B)
{
return A.Cycles < B.Cycles;
});
const int32 P50Index = Sorted.Num() / 2;
const int32 P95Index = FMath::Clamp(
FMath::CeilToInt(static_cast<float>(Sorted.Num()) * 0.95f) - 1,
0, Sorted.Num() - 1);
const FReadyTransitionVisibilitySample& P95 = Sorted[P95Index];
const FReadyTransitionVisibilitySample& Max = Sorted.Last();
int32 MinComponents = MAX_int32, MaxComponents = 0;
int32 MinDesired = MAX_int32, MaxDesired = 0;
int64 TotalComponents = 0, TotalDesired = 0;
for (const FReadyTransitionVisibilitySample& Sample : Sorted)
{
MinComponents = FMath::Min(MinComponents, Sample.TileComponentCount);
MaxComponents = FMath::Max(MaxComponents, Sample.TileComponentCount);
MinDesired = FMath::Min(MinDesired, Sample.DesiredTileCount);
MaxDesired = FMath::Max(MaxDesired, Sample.DesiredTileCount);
TotalComponents += Sample.TileComponentCount;
TotalDesired += Sample.DesiredTileCount;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeReadyVisibilityMeasure] samples=%d p50_ms=%.6f p95_ms=%.6f max_ms=%.6f "
"p95_components=%d p95_desired=%d components_range=%d..%d desired_range=%d..%d "
"components_mean=%.1f desired_mean=%.1f"),
Sorted.Num(),
FPlatformTime::ToSeconds64(Sorted[P50Index].Cycles) * 1000.0,
FPlatformTime::ToSeconds64(P95.Cycles) * 1000.0,
FPlatformTime::ToSeconds64(Max.Cycles) * 1000.0,
P95.TileComponentCount, P95.DesiredTileCount,
MinComponents, MaxComponents, MinDesired, MaxDesired,
static_cast<double>(TotalComponents) / Sorted.Num(),
static_cast<double>(TotalDesired) / Sorted.Num());
}
if (!FarFieldSubmitSamples.IsEmpty())
{
TArray<FFarFieldSubmitSample> Sorted = FarFieldSubmitSamples;
Sorted.Sort([](const FFarFieldSubmitSample& A, const FFarFieldSubmitSample& B)
{
return A.Cycles < B.Cycles;
});
const int32 P50Index = Sorted.Num() / 2;
const int32 P95Index = FMath::Clamp(
FMath::CeilToInt(static_cast<float>(Sorted.Num()) * 0.95f) - 1,
0, Sorted.Num() - 1);
const FFarFieldSubmitSample& P95 = Sorted[P95Index];
const FFarFieldSubmitSample& Max = Sorted.Last();
int32 MinDesired = MAX_int32, MaxDesired = 0;
int32 MinLevel = MAX_int32, MaxLevel = 0;
for (const FFarFieldSubmitSample& Sample : Sorted)
{
MinDesired = FMath::Min(MinDesired, Sample.DesiredTileCount);
MaxDesired = FMath::Max(MaxDesired, Sample.DesiredTileCount);
MinLevel = FMath::Min(MinLevel, Sample.MaxDesiredLevel);
MaxLevel = FMath::Max(MaxLevel, Sample.MaxDesiredLevel);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeFarFieldSubmitMeasure] samples=%d p50_ms=%.6f p95_ms=%.6f max_ms=%.6f "
"p95_desired=%d p95_max_level=%d desired_range=%d..%d levels=%d..%d"),
Sorted.Num(),
FPlatformTime::ToSeconds64(Sorted[P50Index].Cycles) * 1000.0,
FPlatformTime::ToSeconds64(P95.Cycles) * 1000.0,
FPlatformTime::ToSeconds64(Max.Cycles) * 1000.0,
P95.DesiredTileCount, P95.MaxDesiredLevel,
MinDesired, MaxDesired, MinLevel, MaxLevel);
}
}
void AVoxelWorld::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
@@ -3932,9 +4030,9 @@ void AVoxelWorld::Tick(float DeltaTime)
}
// Complete the one-shot diagnostic request only after the player's level-0 centre tile is
// loaded and idle. Waiting for any coarse tile would take the async-neighbour branch and would
// not measure the normal synchronous centre remesh that makes a local edit feel expensive.
// No pawn is a distinct state from a pawn at (0,0,0); keep the diagnostic fallback for
// loaded and idle. The edit fixture selects an already-visible level-0 geometry tile, so it
// measures the normal synchronous centre remesh without depending on a coarse overlay being
// visible. No pawn is distinct from a pawn at (0,0,0); retain the actor-location fallback for
// headless/editor use without making the world-origin pawn skip streaming.
FVector ModificationPosition = bHasPlayer ? PlayerLastPos : GetActorLocation();
FVoxelTileKey ModificationCenterTile(
@@ -3962,17 +4060,8 @@ void AVoxelWorld::Tick(float DeltaTime)
}
if (GVoxelForgeTestModificationLoadedGeometryTarget)
{
bool bHasVisibleCoarseOverlay = false;
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
{
if (Pair.Key.Level > 0 && Pair.Value && Pair.Value->IsVisible()
&& VF_TileFootprintsOverlap(CandidateTile, Pair.Key))
{
bHasVisibleCoarseOverlay = true;
break;
}
}
if (!bHasVisibleCoarseOverlay)
URealtimeMeshComponent* CandidateComponent = TileComponents.FindRef(CandidateTile);
if (!CandidateComponent || !CandidateComponent->IsVisible())
{
continue;
}
@@ -5575,12 +5664,32 @@ void AVoxelWorld::HideOverlappingDesiredVisuals(const FVoxelTileKey& RetiredTile
void AVoxelWorld::ReconcileReadyTransitionVisibility()
{
const bool bMeasure = bMeasureReadyTransitionVisibility;
const uint64 MeasureStartCycles = bMeasure ? FPlatformTime::Cycles64() : 0;
const int32 ComponentCountAtStart = bMeasure ? TileComponents.Num() : 0;
const int32 DesiredCountAtStart = bMeasure ? DesiredSorted.Num() : 0;
const auto RecordMeasure = [this, bMeasure, MeasureStartCycles,
ComponentCountAtStart, DesiredCountAtStart]()
{
if (!bMeasure)
{
return;
}
FReadyTransitionVisibilitySample& Sample =
ReadyTransitionVisibilitySamples.AddDefaulted_GetRef();
Sample.Cycles = FPlatformTime::Cycles64() - MeasureStartCycles;
Sample.TileComponentCount = ComponentCountAtStart;
Sample.DesiredTileCount = DesiredCountAtStart;
};
if (bAllChunksLoaded && TransitionHold.Num() == 0 && PendingUnload.Num() == 0)
{
RecordMeasure();
return;
}
if (DesiredSorted.Num() == 0 || TileComponents.Num() == 0)
{
RecordMeasure();
return;
}
@@ -5590,6 +5699,11 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
// queue below.
TArray<FVoxelTileKey> ReadyToHide;
ReadyToHide.Reserve(TileComponents.Num());
auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel)
{
const int32 Divisor = 1 << (AncestorLevel - Tile.Level);
return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel);
};
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
{
const FVoxelTileKey& OldTile = Pair.Key;
@@ -5606,9 +5720,12 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
bool bHasReplacement = false;
bool bAllReplacementsLoaded = true;
for (const FVoxelTileKey& NewTile : DesiredSorted)
// A coarser desired tile can only overlap this tile by containing it. Test those exact
// ancestors directly, then test desired finer tiles from the prebuilt ancestor index.
for (int32 Level = OldTile.Level + 1; Level <= DesiredTransitionMaxLevel; ++Level)
{
if (!VF_TileFootprintsOverlap(OldTile, NewTile))
const FVoxelTileKey NewTile = GetAncestorAtLevel(OldTile, Level);
if (!IsDesired(NewTile))
{
continue;
}
@@ -5619,6 +5736,21 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
break;
}
}
if (bAllReplacementsLoaded)
{
if (const TArray<FVoxelTileKey>* NewDescendants = DesiredTransitionDescendants.Find(OldTile))
{
for (const FVoxelTileKey& NewTile : *NewDescendants)
{
bHasReplacement = true;
if (!LoadedTiles.Contains(NewTile))
{
bAllReplacementsLoaded = false;
break;
}
}
}
}
if (bHasReplacement && bAllReplacementsLoaded)
{
ReadyToHide.Add(OldTile);
@@ -5634,6 +5766,26 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
// only part of the transition. Reveal them only after every overlapping retired component has
// entered PendingUnload (and was therefore hidden above). Collision visibility is independent:
// the component remains queryable until the budgeted UnloadTile call.
TSet<FVoxelTileKey> VisibleRetiredTiles;
TSet<FVoxelTileKey> VisibleRetiredAncestors;
int32 MaxVisibleRetiredLevel = DesiredTransitionMaxLevel;
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& OldPair : TileComponents)
{
const FVoxelTileKey& OldTile = OldPair.Key;
URealtimeMeshComponent* OldComp = OldPair.Value;
if (IsDesired(OldTile) || PendingUnload.Contains(OldTile)
|| !OldComp || !OldComp->IsVisible())
{
continue;
}
VisibleRetiredTiles.Add(OldTile);
MaxVisibleRetiredLevel = FMath::Max(MaxVisibleRetiredLevel, OldTile.Level);
for (int32 Level = OldTile.Level + 1; Level <= DesiredTransitionMaxLevel; ++Level)
{
VisibleRetiredAncestors.Add(GetAncestorAtLevel(OldTile, Level));
}
}
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& NewPair : TileComponents)
{
const FVoxelTileKey& NewTile = NewPair.Key;
@@ -5643,19 +5795,13 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
continue;
}
bool bBlockedByVisibleRetiredTile = false;
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& OldPair : TileComponents)
bool bBlockedByVisibleRetiredTile = VisibleRetiredAncestors.Contains(NewTile);
for (int32 Level = NewTile.Level + 1;
!bBlockedByVisibleRetiredTile && Level <= MaxVisibleRetiredLevel; ++Level)
{
const FVoxelTileKey& OldTile = OldPair.Key;
if (IsDesired(OldTile) || PendingUnload.Contains(OldTile)
|| !OldPair.Value || !OldPair.Value->IsVisible())
{
continue;
}
if (VF_TileFootprintsOverlap(NewTile, OldTile))
if (VisibleRetiredTiles.Contains(GetAncestorAtLevel(NewTile, Level)))
{
bBlockedByVisibleRetiredTile = true;
break;
}
}
if (!bBlockedByVisibleRetiredTile)
@@ -5663,6 +5809,8 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
NewComp->SetVisibility(true);
}
}
RecordMeasure();
}
// Integer floor-division (correct for negatives), scalar + vector.
@@ -5855,6 +6003,36 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
}
}
// Build a dyadic ancestor index once per desired-set epoch. Different-level tile overlap is
// containment because every tile footprint is power-of-two aligned. This lets the per-frame
// transition pass inspect only actual overlapping candidates.
DesiredTransitionDescendants.Reset();
DesiredTransitionMaxLevel = 0;
for (const FVoxelTileKey& Key : DesiredSorted)
{
DesiredTransitionMaxLevel = FMath::Max(DesiredTransitionMaxLevel, Key.Level);
}
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
{
DesiredTransitionMaxLevel = FMath::Max(DesiredTransitionMaxLevel, Pair.Key.Level);
}
// A worker from the previous desired epoch can finish late with a component at a level no
// longer present in TileComponents when this index is built. Index through the supported
// maximum sheet level (MaxClipLevel 8 + FarSheetSpanLevels 4) so that such a retired tile still
// finds all of its desired descendants when the result is reconciled.
constexpr int32 MaxSupportedTileLevel = 12;
const int32 DescendantIndexMaxLevel = FMath::Max(
DesiredTransitionMaxLevel, MaxSupportedTileLevel);
for (const FVoxelTileKey& Key : DesiredSorted)
{
for (int32 Level = Key.Level + 1; Level <= DescendantIndexMaxLevel; ++Level)
{
const int32 Divisor = 1 << (Level - Key.Level);
const FVoxelTileKey Ancestor(VF_FloorDiv(Key.Coord, Divisor), Level);
DesiredTransitionDescendants.FindOrAdd(Ancestor).Add(Key);
}
}
if (VoxelForgeStartupTrace::IsActive())
{
int32 LOD0Count = 0;
@@ -6433,6 +6611,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
// FAR FIELD — preserve the clipmap's level ordering explicitly. DesiredSorted is
// distance-sorted for the critical/near work, but a level pass prevents a level-5 horizon
// tile from being admitted ahead of an unfinished level-1/2 bridge tile.
const uint64 FarFieldMeasureStartCycles = bMeasureReadyTransitionVisibility
? FPlatformTime::Cycles64() : 0;
int32 MaxDesiredLevel = 0;
for (const FVoxelTileKey& T : DesiredSorted)
{
@@ -6451,6 +6631,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
if (PendingTiles.Num() >= CoarseTaskCap) break;
}
if (bMeasureReadyTransitionVisibility)
{
FFarFieldSubmitSample& Sample = FarFieldSubmitSamples.AddDefaulted_GetRef();
Sample.Cycles = FPlatformTime::Cycles64() - FarFieldMeasureStartCycles;
Sample.DesiredTileCount = DesiredSorted.Num();
Sample.MaxDesiredLevel = MaxDesiredLevel;
}
// Bande de strate changée : re-gen budgétée des tuiles grossières concernées (le vieux
// mesh reste visible jusqu'au résultat — même schéma que RemeshDirtyChunks).
+21
View File
@@ -560,6 +560,7 @@ private:
void MaybeFinishHeadlessStreamingTest();
void UpdateVisualStalenessAudit();
void LogVisualStalenessAudit();
void LogReadyTransitionVisibilityMeasure();
void RecordTileHash(const FVoxelTileKey& Tile, const FIntVector& OriginVoxels,
int32 Step, int32 Cells, int32 BandChunkLo, int32 BandChunkHi,
bool bSheetTile, const FVoxelMeshData& MeshData);
@@ -989,10 +990,30 @@ public:
bool bSampleStacksStarted = false;
TArray<FVoxelTileKey> DesiredSorted; // critical floor prefix, then true-pawn distance-first
TArray<FVoxelTileKey> CriticalDesiredTiles; // occupied/support tile, then heading tile
// Ancestor -> desired descendants, rebuilt with DesiredSorted. Reconciliation uses this
// index to test transition coverage without rescanning the full desired set per old tile.
TMap<FVoxelTileKey, TArray<FVoxelTileKey>> DesiredTransitionDescendants;
int32 DesiredTransitionMaxLevel = 0;
uint32 DesiredEpoch = 0; // increments whenever DesiredStamped is rebuilt
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
float PeakObservedPawnSpeedCmPerSecond = 0.0f;
struct FReadyTransitionVisibilitySample
{
uint64 Cycles = 0;
int32 TileComponentCount = 0;
int32 DesiredTileCount = 0;
};
struct FFarFieldSubmitSample
{
uint64 Cycles = 0;
int32 DesiredTileCount = 0;
int32 MaxDesiredLevel = 0;
};
bool bMeasureReadyTransitionVisibility = false;
TArray<FReadyTransitionVisibilitySample> ReadyTransitionVisibilitySamples;
TArray<FFarFieldSubmitSample> FarFieldSubmitSamples;
// Clean performance accounting. LOD 0..8 covers every marching-cubes clip level; sheet tiles
// above the configured MC range never enter the outer classifier.
static constexpr int32 TrackedClassifierLODCount = 9;