fix: sanitizer sweep across all generators; bound signed shifts/division/overflow at coordinate limits (L61); R31

New verify.py sanitize tier (Clang address,undefined,float-cast-overflow; GCC address,undefined;
-fno-sanitize-recover) runs 19 cases per compiler at LOD0-8 across every generator, passages and the
crossing: 0 runtime reports; a planted signed-overflow control is caught by both. A static scan fixed
INT_MIN negation in floor division, unbounded signed LOD shifts and coordinate-times-extent overflow in
VoxelClipmapDesiredTiles.h and VoxelTypes.h (plugin and standalone), and VoxelWorld.cpp, VoxelContentManager.cpp
and VoxelDensityVolume.cpp (plugin only, uncompiled here). Supported-input output unchanged (version 211).
fast/medium/full PASS. Research R31 (self-checked): far-LOD 4-8 cut plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-26 05:19:18 +01:00
1 parent c0c204ba50
commit 135957831b
13 files changed
+760 -132

No files matched your search

@@ -4,6 +4,27 @@
namespace VoxelClipmapDesiredTiles namespace VoxelClipmapDesiredTiles
{ {
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE int32 ClampInt32(int64 Value)
{
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
}
struct FParameters struct FParameters
{ {
int32 ClipRadius = 3; int32 ClipRadius = 3;
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor) FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
{ {
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor) FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor) FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{ {
return -FloorDiv(-Value, Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
} }
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment, FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis, const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax) int32& OutAxisMin, int32& OutAxisMax)
{ {
const int32 BaseMin = CenterAxis - Radius; const int64 SafeRadius = FMath::Max<int64>(0, Radius);
const int32 BaseEnd = CenterAxis + Radius + 1; const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment; const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1; const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
}; };
AlignAxis(Center.X, OutMin.X, OutMax.X); AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y); AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
{ {
return Radius; return Radius;
} }
return FMath::Max(Radius, const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel); const int64 Scale = static_cast<int64>(uint32{1} << static_cast<uint32>(SafeLevel));
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
+ Scale - 1;
const int64 DesiredRadius = Numerator / Scale;
return static_cast<int32>(FMath::Clamp<int64>(
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
} }
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel, FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
return; return;
} }
OutMinChunkZ = CenterChunkZ - ViewDistanceDown; OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp; OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
if (bHasStrate) if (bHasStrate)
{ {
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks; OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks; OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
} }
} }
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
for (int32 Level = 0; Level <= MaxLevel; ++Level) for (int32 Level = 0; Level <= MaxLevel; ++Level)
{ {
const int32 Pow = 1 << Level; const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(Center, Pow); const FIntVector CenterLevel = FloorDiv(Center, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius; const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
FIntVector RingMin, RingMax; FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax); AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
int32 DeltaZMin = RingMin.Z - CenterLevel.Z; int64 TileMinZ = RingMin.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z; int64 TileMaxZ = RingMax.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32) if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{ {
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z); TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z); TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
} }
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ) for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y; for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY) for (int64 X = RingMin.X; X <= RingMax.X; ++X)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
{ {
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ); const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
const int32 TileMinZ = Tile.Z << Level; static_cast<int32>(Z));
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1; const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ) const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
{ {
continue; continue;
} }
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0) if (Level > 0)
{ {
const bool bCovered = const bool bCovered =
(2 * Tile.X >= PreviousRingMin.X) (2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X) && (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y) && (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y) && (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z) && (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z); && (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
if (bCovered) if (bCovered)
{ {
continue; continue;
@@ -559,9 +559,13 @@ int32 UVoxelContentManager::RegionSize() const
// truncation, a negative-coord cell is queued under one region but routed back to another → its march // truncation, a negative-coord cell is queued under one region but routed back to another → its march
// result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only // result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only
// NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here. // NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here.
static FORCEINLINE int32 FloorDivPos(int32 A, int32 R) static FORCEINLINE int32 FloorDivPos(int64 A, int32 R)
{ {
return (A >= 0) ? (A / R) : -(((-A) + R - 1) / R); if (R <= 0) { return 0; }
const int64 Quotient = A / R;
const int64 Remainder = A % R;
return static_cast<int32>(FMath::Clamp<int64>(
Remainder < 0 ? Quotient - 1 : Quotient, MIN_int32, MAX_int32));
} }
static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R) static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
@@ -573,10 +577,10 @@ static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell, static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell,
int32 FarR, int32 R) int32 FarR, int32 R)
{ {
const int32 MinX = FloorDivPos(PlayerCell.X - FarR, R); const int32 MinX = FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R);
const int32 MaxX = FloorDivPos(PlayerCell.X + FarR, R); const int32 MaxX = FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R);
const int32 MinY = FloorDivPos(PlayerCell.Y - FarR, R); const int32 MinY = FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R);
const int32 MaxY = FloorDivPos(PlayerCell.Y + FarR, R); const int32 MaxY = FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R);
return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY; return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY;
} }
@@ -1102,17 +1106,22 @@ void UVoxelContentManager::RebuildDesiredCells(FDecoGrid& G, const FIntPoint& Pl
const int32 R = RegionSize(); const int32 R = RegionSize();
// Desired regions = every region whose footprint touches the radius-FarR cell box around the player. // Desired regions = every region whose footprint touches the radius-FarR cell box around the player.
const FIntPoint RMin(FloorDivPos(PlayerCell.X - FarR, R), const FIntPoint RMin(FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R),
FloorDivPos(PlayerCell.Y - FarR, R)); FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R));
const FIntPoint RMax(FloorDivPos(PlayerCell.X + FarR, R), const FIntPoint RMax(FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R),
FloorDivPos(PlayerCell.Y + FarR, R)); FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R));
TSet<FIntPoint> DesiredRegions; TSet<FIntPoint> DesiredRegions;
DesiredRegions.Reserve((RMax.X - RMin.X + 1) * (RMax.Y - RMin.Y + 1)); constexpr int64 MaxRegionReserve = 4096;
for (int32 ry = RMin.Y; ry <= RMax.Y; ++ry) const int64 WidthX = static_cast<int64>(RMax.X) - RMin.X + 1;
for (int32 rx = RMin.X; rx <= RMax.X; ++rx) const int64 WidthY = static_cast<int64>(RMax.Y) - RMin.Y + 1;
const int64 ReserveCount = WidthY > 0 && WidthX > MaxRegionReserve / WidthY
? MaxRegionReserve : FMath::Max<int64>(0, WidthX * WidthY);
DesiredRegions.Reserve(static_cast<int32>(ReserveCount));
for (int64 ry = RMin.Y; ry <= RMax.Y; ++ry)
for (int64 rx = RMin.X; rx <= RMax.X; ++rx)
{ {
DesiredRegions.Add(FIntPoint(rx, ry)); DesiredRegions.Add(FIntPoint(static_cast<int32>(rx), static_cast<int32>(ry)));
} }
// Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal). // Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal).
@@ -1464,8 +1473,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u)); const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u));
if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue; if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue;
const int32 Span = FMath::Max(0, Comp.CountMax - Comp.CountMin); const int64 Span = FMath::Max<int64>(0,
const int32 Count = Comp.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(CH ^ 0x1234u)) * (float)(Span + 1)); static_cast<int64>(Comp.CountMax) - Comp.CountMin);
const uint64 CountRange = static_cast<uint64>(Span) + 1u;
const float CountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(CH ^ 0x1234u)) * static_cast<float>(CountRange);
const int64 CountWide = static_cast<int64>(Comp.CountMin)
+ static_cast<int64>(CountOffset);
const int32 Count = static_cast<int32>(FMath::Clamp<int64>(
CountWide, MIN_int32, MAX_int32));
for (int32 ii = 0; ii < Count; ++ii) for (int32 ii = 0; ii < Count; ++ii)
{ {
if (CompBudget <= 0) break; if (CompBudget <= 0) break;
@@ -1514,8 +1530,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu)); const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu));
if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue; if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue;
const int32 SubSpan = FMath::Max(0, Sub.CountMax - Sub.CountMin); const int64 SubSpan = FMath::Max<int64>(0,
const int32 SubCount = Sub.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(SH ^ 0x1234u)) * (float)(SubSpan + 1)); static_cast<int64>(Sub.CountMax) - Sub.CountMin);
const uint64 SubCountRange = static_cast<uint64>(SubSpan) + 1u;
const float SubCountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(SH ^ 0x1234u)) * static_cast<float>(SubCountRange);
const int64 SubCountWide = static_cast<int64>(Sub.CountMin)
+ static_cast<int64>(SubCountOffset);
const int32 SubCount = static_cast<int32>(FMath::Clamp<int64>(
SubCountWide, MIN_int32, MAX_int32));
for (int32 sj = 0; sj < SubCount; ++sj) for (int32 sj = 0; sj < SubCount; ++sj)
{ {
if (CompBudget <= 0) break; if (CompBudget <= 0) break;
@@ -155,9 +155,11 @@ int32 UVoxelDensityVolume::NumLevels() const
FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B) FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B)
{ {
// True floor division (B > 0). FMath::DivideAndRoundDown truncates toward zero for negatives — // True floor division (B > 0). Remainder adjustment handles INT_MIN without negating it.
// a footgun the content manager hit too (see FloorDivPos there). Cells span the origin, so floor. if (B <= 0) { return 0; }
return (A >= 0) ? (A / B) : -(((-A) + B - 1) / B); const int32 Quotient = A / B;
const int32 Remainder = A % B;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity) FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity)
@@ -180,7 +182,8 @@ void UVoxelDensityVolume::EnsureAllocated()
for (int32 L = 0; L < N; ++L) for (int32 L = 0; L < N; ++L)
{ {
FClipLevel& Lv = Levels[L]; FClipLevel& Lv = Levels[L];
Lv.Step = 1 << L; Lv.Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(L, 0, 8)));
Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX); Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
Lv.bHasData = false; Lv.bHasData = false;
Lv.Density.SetNumZeroed(Count); // start all-air (0) Lv.Density.SetNumZeroed(Count); // start all-air (0)
@@ -632,7 +635,8 @@ void UVoxelDensityVolume::ProcessOneFill(const FPendingFill& F)
if (!Gen) return; if (!Gen) return;
if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return; if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return;
const int32 Step = 1 << F.Level; const int32 Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(F.Level, 0, 8)));
FFillResult R; FFillResult R;
R.Level = F.Level; R.Level = F.Level;
R.Epoch = F.Epoch; R.Epoch = F.Epoch;
+60 -27
View File
@@ -82,6 +82,17 @@
#include <atomic> #include <atomic>
#include <unordered_set> #include <unordered_set>
static FORCEINLINE int32 VF_ClipLevelScale(int32 Level)
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
static FORCEINLINE int32 VF_ClipLevelExtentVoxels(int32 Level)
{
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE)
* static_cast<uint32>(VF_ClipLevelScale(Level)));
}
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile, static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
const FVector& BoundsMin, const FVector& BoundsMax); const FVector& BoundsMin, const FVector& BoundsMax);
static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B); static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B);
@@ -6240,17 +6251,19 @@ static void VF_AppendTouchingAncestors(
const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates) const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates)
{ {
if (AncestorLevel <= Tile.Level) { return; } if (AncestorLevel <= Tile.Level) { return; }
const int32 Divisor = 1 << (AncestorLevel - Tile.Level); const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
const auto FloorDiv = [Divisor](int32 Value) const auto FloorDiv = [Divisor](int64 Value)
{ {
return Value >= 0 ? Value / Divisor : -(((-Value) + Divisor - 1) / Divisor); const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return static_cast<int32>(Remainder < 0 ? Quotient - 1 : Quotient);
}; };
const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value) const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value)
{ {
TArray<int32, TInlineAllocator<2>> Candidates; TArray<int32, TInlineAllocator<2>> Candidates;
int32 First = FloorDiv(Value); int32 First = FloorDiv(Value);
if ((Value % Divisor) == 0) { --First; } if ((Value % Divisor) == 0) { --First; }
const int32 Last = FloorDiv(Value + 1); const int32 Last = FloorDiv(static_cast<int64>(Value) + 1);
for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); } for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); }
return Candidates; return Candidates;
}; };
@@ -6338,7 +6351,7 @@ void AVoxelWorld::UpdateVisualStalenessAudit()
const FVector& WorldPosition, int32 Level, auto& OutCandidates) const FVector& WorldPosition, int32 Level, auto& OutCandidates)
{ {
const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition); const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition);
const float TileExtentVoxels = static_cast<float>(CHUNK_SIZE * (1 << Level)); const float TileExtentVoxels = static_cast<float>(VF_ClipLevelExtentVoxels(Level));
TArray<int32, TInlineAllocator<2>> XCandidates; TArray<int32, TInlineAllocator<2>> XCandidates;
TArray<int32, TInlineAllocator<2>> YCandidates; TArray<int32, TInlineAllocator<2>> YCandidates;
TArray<int32, TInlineAllocator<2>> ZCandidates; TArray<int32, TInlineAllocator<2>> ZCandidates;
@@ -7182,7 +7195,7 @@ void AVoxelWorld::Tick(float DeltaTime)
{ {
const FIntVector Min = SteadyLevelMins.FindChecked(Level); const FIntVector Min = SteadyLevelMins.FindChecked(Level);
const FIntVector Max = SteadyLevelMaxs.FindChecked(Level); const FIntVector Max = SteadyLevelMaxs.FindChecked(Level);
const int32 ExtentVoxels = CHUNK_SIZE << Level; const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0; const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres; const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres; const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres;
@@ -7663,10 +7676,20 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
}; };
for (const VoxelForgeCore::TileKey& Key : InitialDesired) for (const VoxelForgeCore::TileKey& Key : InitialDesired)
{ {
const int32 Extent = 32 << Key.lod; const int32 Extent = VF_ClipLevelExtentVoxels(Key.lod);
const int32 OriginX = Key.coordinate.x * Extent; const int64 OriginXWide = static_cast<int64>(Key.coordinate.x) * Extent;
const int32 OriginY = Key.coordinate.y * Extent; const int64 OriginYWide = static_cast<int64>(Key.coordinate.y) * Extent;
const int32 OriginZ = Key.coordinate.z * Extent; const int64 OriginZWide = static_cast<int64>(Key.coordinate.z) * Extent;
if (OriginXWide < MIN_int32 || OriginYWide < MIN_int32 || OriginZWide < MIN_int32
|| OriginXWide + Extent > MAX_int32 || OriginYWide + Extent > MAX_int32
|| OriginZWide + Extent > MAX_int32)
{
OutFailureReason = TEXT("Core initial clipmap exceeds the supported voxel-coordinate range.");
return false;
}
const int32 OriginX = static_cast<int32>(OriginXWide);
const int32 OriginY = static_cast<int32>(OriginYWide);
const int32 OriginZ = static_cast<int32>(OriginZWide);
IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent); IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent);
IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent); IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent);
IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent); IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent);
@@ -10074,8 +10097,11 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile)
{ {
BandChunkLo = MeshBandChunkLo; BandChunkLo = MeshBandChunkLo;
BandChunkHi = MeshBandChunkHi; BandChunkHi = MeshBandChunkHi;
BandVoxLo = MeshBandChunkLo * CHUNK_SIZE; BandVoxLo = static_cast<int32>(FMath::Clamp<int64>(
BandVoxHi = (MeshBandChunkHi + 1) * CHUNK_SIZE - 1; static_cast<int64>(MeshBandChunkLo) * CHUNK_SIZE, MIN_int32, MAX_int32));
BandVoxHi = static_cast<int32>(FMath::Clamp<int64>(
(static_cast<int64>(MeshBandChunkHi) + 1) * CHUNK_SIZE - 1,
MIN_int32, MAX_int32));
} }
FChunkResult Result; FChunkResult Result;
@@ -10238,7 +10264,7 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
ReadyToHide.Reserve(TileComponents.Num()); ReadyToHide.Reserve(TileComponents.Num());
auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel) auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel)
{ {
const int32 Divisor = 1 << (AncestorLevel - Tile.Level); const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel); return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel);
}; };
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents) for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
@@ -10361,7 +10387,10 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
// Integer floor-division (correct for negatives), scalar + vector. // Integer floor-division (correct for negatives), scalar + vector.
static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D) static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D)
{ {
return V >= 0 ? (V / D) : -(((-V) + D - 1) / D); if (D <= 0) { return 0; }
const int32 Quotient = V / D;
const int32 Remainder = V % D;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D) static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D)
{ {
@@ -11441,13 +11470,13 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
{ {
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter); VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
} }
const int32 PowMax = 1 << MaxLevel; const int32 PowMax = VF_ClipLevelScale(MaxLevel);
const FIntVector CMax = VF_FloorDiv(Center, PowMax); const FIntVector CMax = VF_FloorDiv(Center, PowMax);
FVector CV; FVector CV;
if (bUseVoxelForgeCore) if (bUseVoxelForgeCore)
{ {
const double TileExtentVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level); const double TileExtentVoxels = static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
CV = FVector( CV = FVector(
(static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels, (static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels,
(static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels, (static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels,
@@ -11463,9 +11492,9 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax), VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax),
VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax)); VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax));
return FMath::Abs(TMax.X - CMax.X) <= ROuter return FMath::Abs(static_cast<int64>(TMax.X) - CMax.X) <= ROuter
&& FMath::Abs(TMax.Y - CMax.Y) <= ROuter && FMath::Abs(static_cast<int64>(TMax.Y) - CMax.Y) <= ROuter
&& FMath::Abs(TMax.Z - CMax.Z) <= ROuter; && FMath::Abs(static_cast<int64>(TMax.Z) - CMax.Z) <= ROuter;
} }
int32 AVoxelWorld::GetMaxConcurrentTasks() const int32 AVoxelWorld::GetMaxConcurrentTasks() const
@@ -12058,7 +12087,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num())); LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num()));
for (const FDesiredLevelSummary& Summary : LevelSummaries) for (const FDesiredLevelSummary& Summary : LevelSummaries)
{ {
const int32 ExtentVoxels = CHUNK_SIZE << Summary.Level; const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Summary.Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0; const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres; const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres; const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres;
@@ -12109,8 +12138,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
for (const FVoxelTileKey& T : LoadedTiles) for (const FVoxelTileKey& T : LoadedTiles)
{ {
if (T.Level < CutMin) continue; if (T.Level < CutMin) continue;
const int32 CLo = T.Coord.Z << T.Level; const int64 LevelScale = VF_ClipLevelScale(T.Level);
const int32 CHi = ((T.Coord.Z + 1) << T.Level) - 1; const int64 CLo = static_cast<int64>(T.Coord.Z) * LevelScale;
const int64 CHi = (static_cast<int64>(T.Coord.Z) + 1) * LevelScale - 1;
const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi; const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi;
const bool bInNew = CLo >= NewLo && CHi <= NewHi; const bool bInNew = CLo >= NewLo && CHi <= NewHi;
const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi; const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi;
@@ -12330,7 +12360,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
} }
Heading = Heading.GetSafeNormal(); Heading = Heading.GetSafeNormal();
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition); const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
const double TileSizeCm = static_cast<double>(CHUNK_SIZE << Level) const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
* GetActiveVoxelSizeCm(); * GetActiveVoxelSizeCm();
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B) LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
{ {
@@ -12788,7 +12818,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0 const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0
? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9) ? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9)
: (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2); : (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2);
const int32 Extent = CHUNK_SIZE << Tile.Level; const int32 Extent = VF_ClipLevelExtentVoxels(Tile.Level);
const int32 Cells = (Tile.Level < FullRes) const int32 Cells = (Tile.Level < FullRes)
? CHUNK_SIZE ? CHUNK_SIZE
@@ -12853,7 +12883,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
// et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts // et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts
// ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles, // ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles,
// LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue. // LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue.
if (Step > (BandChunkHi - BandChunkLo + 1) * CHUNK_SIZE) const int64 BandHeightVoxels =
(static_cast<int64>(BandChunkHi) - BandChunkLo + 1) * CHUNK_SIZE;
if (static_cast<int64>(Step) > BandHeightVoxels)
{ {
FChunkResult Empty; FChunkResult Empty;
Empty.Tile = Tile; Empty.Tile = Tile;
@@ -13988,7 +14020,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
// Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires. // Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires.
const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm()) const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm())
/ VOXEL_SIZE; / VOXEL_SIZE;
const double TileExtentCoreVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level); const double TileExtentCoreVoxels =
static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
const int32 MinOwnerChunkX = FMath::FloorToInt( const int32 MinOwnerChunkX = FMath::FloorToInt(
Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE); Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE);
const int32 MinOwnerChunkY = FMath::FloorToInt( const int32 MinOwnerChunkY = FMath::FloorToInt(
@@ -15180,7 +15213,7 @@ void AVoxelWorld::RemeshDirtyChunks(const TArray<FIntVector>& DirtyCoords, const
// BackgroundHigh; the old component remains visible until its post-edit replacement is applied. // BackgroundHigh; the old component remains visible until its post-edit replacement is applied.
auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool
{ {
const int32 Span = 1 << Tile.Level; const int32 Span = VF_ClipLevelScale(Tile.Level);
const FIntVector MinChunk = Tile.Coord * Span; const FIntVector MinChunk = Tile.Coord * Span;
const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span); const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span);
for (const FIntVector& Coord : DirtyCoords) for (const FIntVector& Coord : DirtyCoords)
+21 -4
View File
@@ -332,16 +332,33 @@ struct FVoxelTileKey
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {} FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
// Cell size (sampling step) in voxels, and tile extent in voxels per axis. // Cell size (sampling step) in voxels, and tile extent in voxels per axis.
int32 StepVoxels() const { return 1 << Level; } int32 StepVoxels() const
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; } {
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
int32 ExtentVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
}
// Min-corner of the tile in VOXEL coords (what the mesher takes). // Min-corner of the tile in VOXEL coords (what the mesher takes).
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); } FIntVector OriginVoxels() const
{
const int64 Extent = ExtentVoxels();
const auto ScaleAxis = [Extent](int32 Axis)
{
return static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
};
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
}
// Tile centre in world cm (for distance sorting / LOD selection). // Tile centre in world cm (for distance sorting / LOD selection).
FVector CenterCm() const FVector CenterCm() const
{ {
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE; const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext); return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
} }
+19 -3
View File
@@ -13,6 +13,8 @@ COMPILER="clang++-19"
VARIANT="clang19" VARIANT="clang19"
FP_CONTRACT="off" FP_CONTRACT="off"
JOBS=12 JOBS=12
SANITIZERS=""
EXCLUDE_UNSIGNED_OVERFLOW=0
while (($#)); do while (($#)); do
case "$1" in case "$1" in
@@ -20,6 +22,8 @@ while (($#)); do
--variant=*) VARIANT="${1#*=}" ;; --variant=*) VARIANT="${1#*=}" ;;
--fp-contract=*) FP_CONTRACT="${1#*=}" ;; --fp-contract=*) FP_CONTRACT="${1#*=}" ;;
--jobs=*) JOBS="${1#*=}" ;; --jobs=*) JOBS="${1#*=}" ;;
--sanitize=*) SANITIZERS="${1#*=}" ;;
--exclude-unsigned-integer-overflow) EXCLUDE_UNSIGNED_OVERFLOW=1 ;;
*) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;; *) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;;
esac esac
shift shift
@@ -60,11 +64,22 @@ INCLUDES=(
"-I$ROOT/Source/VoxelForge/Public" "-I$ROOT/Source/VoxelForge/Public"
"-I$ROOT/Source/VoxelForge/Private" "-I$ROOT/Source/VoxelForge/Private"
) )
OPT_LEVEL=-O2
LINK_FLAGS=()
if [[ -n "$SANITIZERS" ]]; then
OPT_LEVEL=-O1
LINK_FLAGS+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=undefined)
if ((EXCLUDE_UNSIGNED_OVERFLOW)); then
LINK_FLAGS+=(-fno-sanitize=unsigned-integer-overflow)
fi
fi
FLAGS=( FLAGS=(
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math -std=c++20 "$OPT_LEVEL" -mavx2 -mfma -fno-fast-math
-fexceptions -pthread "${INCLUDES[@]}" -fexceptions -pthread "${INCLUDES[@]}"
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" "-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
) )
FLAGS+=("${LINK_FLAGS[@]}")
if [[ "$FP_CONTRACT" != "default" ]]; then if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT") FLAGS+=("-ffp-contract=$FP_CONTRACT")
fi fi
@@ -131,7 +146,7 @@ PIDS=()
CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a" CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a"
"$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}" "$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}"
OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT" OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT" "$COMPILER" -pthread "${LINK_FLAGS[@]}" "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT" CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \ "$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \ "-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
@@ -145,4 +160,5 @@ if [[ "$POST_BUILD_STAMP" != "$SOURCE_STAMP" ]]; then
fi fi
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT" printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT" printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS" printf 'Compiler: %s | variant: %s | fp-contract: %s | sanitizers: %s | jobs: %s\n' \
"$COMPILER" "$VARIANT" "$FP_CONTRACT" "${SANITIZERS:-off}" "$JOBS"
@@ -0,0 +1,65 @@
# L61 sanitizer sweep
## Builds and coverage
- Clang 19: `address,undefined,float-cast-overflow`; GCC: `address,undefined`. Both used `-fno-sanitize-recover=undefined`, `-ffp-contract=off`, and two build jobs.
- Clang's `integer` group was tried with unsigned overflow excluded. libstdc++ `std::rotl` emitted a non-actionable shift diagnostic, so the group is omitted from the standing stage.
- ASan runs used `taskset -c 0-11 prlimit --rss=6442450944` to allow its shadow address space to map. Linux RSS limits are advisory; the measured descendant-tree peak from the complete comparable sweep was 1.798 GiB. The corrected final rerun used the same limit mode.
- Each compiler ran 19 cases at LOD0–8: natural SW, FI, all three profiles, caves, motifs, lattice, both passage styles, and the live surface crossing. General generators used two camera views; the suite also covers seeded random cameras. Total: 38 runs, 46 passing checks, no ASan/UBSan reports. No edit fixture was present.
## Findings
The generator runs found no runtime sanitizer issue. The boundary findings below came from the requested static scan; they were fixed at the source.
| File:line | Kind and trigger | Fix |
|---|---|---|
| `VoxelClipmapDesiredTiles.h:35,49,57-68,129-171` (plugin and standalone) | Signed negation in floor/ceil division failed for `INT_MIN`; center/radius, ring, and tile-bound arithmetic could overflow at coordinate limits. | Quotient/remainder division, 64-bit intermediates and clamped bounds, bounded unsigned LOD shifts, and wide tile loops. |
| `VoxelWorld.cpp:85-93,6254-6267,7679-7693,10100-10104,10388-10394,11473-11497,12141-12147,12886-12888` (plugin only) | Signed LOD shifts, biased division, origin/band calculations, and coordinate differences were unsafe for invalid levels or edge coordinates. | Central bounded unsigned scale/extent helpers; 64-bit arithmetic and clamping; reject initial clipmaps outside the supported voxel range. |
| `VoxelContentManager.cpp:562-584,1109-1125,1476-1484,1533-1542` (plugin only) | `-INT_MIN`, player-cell ± radius, region-size products, and authored count spans could overflow. | Remainder-adjusted floor division, widened coordinates/count spans, capped reserve, and clamped count conversion. |
| `VoxelDensityVolume.cpp:156-162,185-186,638-639` (plugin only) | Floor division negated `INT_MIN`; signed LOD shifts depended on valid levels. | Remainder-adjusted division and bounded unsigned shifts. |
| `VoxelTypes.h:335-363` (plugin and standalone) | Signed shifts and coordinate-times-extent could overflow; `CenterCm` repeated the signed LOD shift. | Clamp LOD to 0–8, use unsigned shifts and 64-bit coordinate multiplication with clamping; derive `CenterCm` from the safe extent helper. |
## Unreal-only static scan
Scanned all 30 `Source/VoxelForge/Private/*.cpp` files. Standalone compiles 15; the other 15 were reviewed statically and are not compiled here: `VoxelAtmosphereManager.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`, `VoxelForgeCoreAdapter.cpp`, `VoxelForgeModule.cpp`, `VoxelRedefinedDensityField.cpp`, `VoxelSeasonAsset.cpp`, `VoxelSeasonManifest.cpp`, `VoxelStackSampler.cpp`, `VoxelStartupTrace.cpp`, `VoxelStats.cpp`, `VoxelStrateDefinition.cpp`, `VoxelStratePreview.cpp`, `VoxelStrateRecipe.cpp`, and `VoxelWorld.cpp`.
No signed-bias formulas or unbounded LOD shifts remain in that scan. Remaining private-source shifts are bounded (for example, `VoxelGenerator.cpp:900` clamps LOD to 0–5; marching-cubes shifts are fixed bit masks). The Unreal files could not be compiled here.
## Control, version, and hashes
The test-only `Saved/Linux/l61/sanitizer/signed-overflow-control.cpp` evaluates `INT_MAX + 1`. Both compilers report signed integer overflow, and the stage requires that diagnostic; logs are `signed-overflow-control-clang.log` and `signed-overflow-control-gcc.log`.
Version remains **211**. Supported-input output is unchanged; only invalid or out-of-range arithmetic is bounded.
- Source stamp: `c22b877c586423fbe67cdffb8ab762e3fe6e20e91897685447113fcb23944035`
- Clang headless: `400b4a66a4d3f76f584f57aefed882bcfc736db563815145518583ac007bd57c`
- GCC headless: `8f782ca4c73b6fc0a942fa06db4149426e58beaacd2bb9185974be4167a614b6`
- Clang sanitizer: `2a1618d25904b1c3620cfcd0e37d59133fccd349df347442396bd0be42f7b21c`
- GCC sanitizer: `6c70cc1c5c843b24de7f82e19a44947cdcd1d8bd4ba01a448027a230ebe5d3e7`
## Standing tier and results
Added a dedicated `verify.py sanitize` tier with a 1,200-second budget and the option to include it in full via `verify.py full --sanitize`.
| Tier | Timeout | Result |
|---|---:|---|
| Sanitize | 1,200 s | PASS, 787.3 s |
| Fast | 900 s | PASS, 251.4 s |
| Medium | 2,400 s | PASS, 1,554.2 s |
| Full + five-run speed matrix | 3,600 s | PASS, 1,709.3 s |
The full tier checked Clang/GCC and W1/W8/W12 determinism, random cameras, the 30-cell set, baseline/candidate cost pairs, and five interleaved timings for all eight generator views. Expected black-image negative-control exits were handled by passing control checks.
## Trials and failed attempts
Used all 15 verification attempts: seven discarded sanitizer attempts, the initial sanitizer pass, fast/medium/full, then the corrected sanitizer pass and final fast/medium/full. Discarded attempts were two missing-`/usr/bin/time` wrapper failures, the noisy integer-sanitizer diagnostic, a source-stamp change during a run, two stale-binary refusals, and a LeakSanitizer ptrace failure. A separate Clang release build briefly failed on a duplicate `ExtentVoxels()` declaration; it was corrected and rebuilt. Final tiers have no unexpected failed runs.
## Orchestrator review (Claude)
Accepted. The stamp matches the final Clang and GCC builds (`c22b877c…`); the sanitize, fast, medium and full tiers
all pass. The runtime sweep is clean (38 runs, 0 reports), and the planted signed-overflow control is caught by both
compilers. **The Unreal-only edits** (`VoxelWorld.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`) can't
be compiled here. I reviewed the `VoxelWorld.cpp` diff: the helpers are `static FORCEINLINE`, they use UE types
(`int32`, `int64`, `FMath::Clamp`, `MIN_int32`/`MAX_int32`), and their LOD clamp of 0–8 matches
`UVoxelSettings::MaxClipLevel` (`ClampMax = "8"`) and `CoreMaxClipLevel = 8`, so they don't change supported
behaviour. These go into the owner's Windows build list.
@@ -0,0 +1,79 @@
# VoxelForge — R31 self-check: far-LOD work
## Measurement basis
The L55 figures below are **worker-seconds**, summed across tiles. They are not wall time or predicted savings. The L60 matrix is useful for whole-view comparisons, but it has no matching per-LOD stage breakdown for every generator.
The far LOD4–8 totals and component sums are derived from the logged rows:
| Run | Worker sum | Column evaluation | MC exclusive | Ground exclusive | Cap exclusive | Skirts | Assembly | Unassigned remainder |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| Natural SW | 7.878235 | 2.699576 | 2.248091 | 0.000440 | 0.870358 | 0.789370 | 0.025267 | 1.245133 |
| Floating Islands | 13.653194 | 2.812932 | 3.709855 | 0.000605 | 0.942610 | 0.981823 | 0.028482 | 5.176887 |
| flatField profile | 4.675822 | — | — | — | — | — | — | — |
SW sums are derived from the five LOD rows: [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>), [LOD5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9561>), [LOD6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9562>), [LOD7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9563>), [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9564>). FI sums use [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9565>), [LOD5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9566>), [LOD6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9567>), [LOD7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9568>), [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9569>). flatField’s total is derived from [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:46>) through [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:50>).
The remainder is calculated, not a named timer. It includes work the summary does not allocate to those bins. Also, R28 calls SW’s 2.699576 column total “worker-ms”; the raw rows show that it is **worker-seconds** (2,699.576 ms).
## Far-LOD inventory and visibility
`generateTile` selects the coarse cell count and step at [VoxelForgeCore.cpp:2121](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2121>), then calls `GenerateMesh` at [VoxelForgeCore.cpp:2642](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2642>). MC produces the sampled density surface, including terrain and passage geometry. Those surfaces can appear in the far view and horizon; generation does not establish that a particular tile is visible from the near-field camera.
- **Connector repair:** `AppendFarConnectorShells` returns below LOD6 (`if(Key.Level<6||Step<=0)return;`) and tests four radial directions ([FarShellsInternal.h:247](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:247>), [lines 248–300](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:248>)). It marks missing source-surface crossings and emits wall panels ([lines 369–373](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:369>), [lines 567–587](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:567>)). The stations are 128 voxels at LOD6 and at least 256 at LOD7–8 ([lines 314–317](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:314>)). These walls can be seen through a passage or opening; the logs do not show that they are always visible from the near-field camera.
L55 gives no LOD-aggregate connector cost. Its per-tile LOD6 row reports `connector_ms=17.641` and `column_ms=15.935` ([sw.log:3779](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:3779>)); those timers overlap. A separate L52 log records `connector_shell_ms=1334.783` and `connector_column_compute_ms=1113.385` for LOD6 ([final-surface-world.log:99](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:99>)). That 1.335 worker-seconds is an **inclusive historical timer**, not extra work to add to the column total or to L55.
- **Far stratum shells:** `AppendFarStratumShells` returns below LOD7, below step 4, or without a manager (`if(Key.Level<7||Step<4||!World.Manager)return;`) ([FarShellsInternal.h:1054](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1054>)). It compares source stratum crossings with mesh hits and emits subdivided patches for missing crossings ([lines 1096–1111](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1096>), [lines 1131–1180](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1131>)); `generateTile` calls it at [VoxelForgeCore.cpp:2738](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2738>). The L52 log records 6.837 ms at LOD7 and 21.687 ms at LOD8 ([line 103](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:103>), [line 107](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:107>)); their 28.524 ms sum is derived and belongs to that older run. Current code can skip this repair at LOD7+ for native-surface-horizon tiles without edits or island overlap ([VoxelForgeCore.cpp:2322](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2322>)). Separate boundary floor and ceiling functions still run at [VoxelForgeCore.cpp:2847](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2847>).
- **Cap refinement:** `RefineFarNativeCap` applies from LOD4 when an owner generator exists ([VoxelForgeCore.cpp:697](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:697>)); it builds cap surfaces from the heightfield samples ([lines 728–740](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:728>), [lines 798–859](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:798>)). Caps can be seen from inside a cave or through an opening; they are not proven invisible from the exterior camera. L55’s cap-exclusive worker costs are 0.870358 SW and 0.942610 FI, derived in the table. A separate L52 log reports LOD8 `mc_grid_ms=1348.084` ([final-surface-world.log:107](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:107>)); that is an older-run grid cost, not the L55 total.
- **Ground refinement:** `RefineFarNativeGround` applies from LOD5 ([VoxelForgeCore.cpp:394](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:394>)). It reconstructs the ground profile on a grid and replaces nearby coarse upward MC faces ([lines 399–409](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:399>), [lines 582–588](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:582>)). The ground can affect the far terrain and horizon silhouette. The **ground-refinement-exclusive** L55 cost is only 0.440 ms SW and 0.605 ms FI, derived above. The much larger column-evaluation bin is shared across MC and refinement work; it is not a ground-only cost.
- **Skirts:** the mesher extrudes skirts from boundary edges, with depth based on `SkirtCells` and mesh step ([VoxelMarchingCubesMesher.cpp:2067](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2067>), [line 2072](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2072>)). The viewer marks a face visible when there is no same-LOD neighbor and filters triangles against that mask ([StandaloneViewer.cpp:132](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:132>), [lines 833–836](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:833>)). Only exposed faces can render; same-LOD covered faces are already hidden. L55’s skirt totals are 0.789370 SW and 0.981823 FI worker-seconds, derived above.
## Projected size at the clip radius
Linux15 uses 0.000613592 rad/px and closest display distances of 192, 384, 768, 1,536 and 3,072 m for LOD4–8 ([gate basis](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>), [swap distances](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:46>)). With the 25 cm voxel and 16 coarse cells in the owner data, the derived one-pixel footprints and lattice intervals are:
| LOD | Distance | 1 px in world | MC cell interval | Cap interval | Ground interval | Connector station |
|---:|---:|---:|---:|---:|---:|---:|
| 4 | 192 m | 0.118 m | 8 m ≈ 68 px | 4 m ≈ 34 px | — | — |
| 5 | 384 m | 0.236 m | 16 m ≈ 68 px | 32 m ≈ 136 px | 8 m ≈ 34 px | — |
| 6 | 768 m | 0.471 m | 32 m ≈ 68 px | 32 m ≈ 68 px | 16 m ≈ 34 px | 32 m ≈ 68 px |
| 7 | 1,536 m | 0.942 m | 64 m ≈ 68 px | 32 m ≈ 34 px | 32 m ≈ 34 px | 64 m ≈ 68 px |
| 8 | 3,072 m | 1.885 m | 128 m ≈ 68 px | 128 m ≈ 68 px interior; 32 m ≈ 17 px at boundary | same | 128 m ≈ 68 px |
These are **derived projected lattice intervals**, not measured geometry error or proof of visibility. Orientation, connector direction, terrain occlusion and the camera view change the projection. No component-by-component near-camera visibility capture was found. The sizes do not support calling the current grids or connector stations “subpixel” based on distance alone.
## Exact cuts
- **Reuse an identical heightfield column:** exact if the recipe, plan, context and integer XY coordinates match. The shared cache key contains recipe/plan identities and hashes plus XY, and deliberately omits LOD ([SharedColumnCache.h:35](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h:35>)); the cache is enabled by default and scoped during tile generation ([VoxelForgeCore.h:1743](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h:1743>), [VoxelForgeCore.cpp:2013](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2013>)). The invariant is the same pure column result bit-for-bit. The L55 run already had the shared cache on ([sw.log:2](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:2>)), so only remaining misses or evictions are an opportunity.
- **Reuse coincident MC density samples:** exact for identical field identity, edits and world-space XYZ sample coordinates, if the same scalar is reused and each tile retains its existing traversal, interpolation and gradient arithmetic. The invariant is the exact sampled float. Because the clipmap tiles partition the volume, this is generally boundary or nested-lattice reuse, not a way to reuse an entire child tile for its parent. No per-generator saving is measured.
- **Skip skirt faces hidden by the active mask:** exact for the rendered mesh with that fixed mask, because the viewer already drops those triangles. The invariant is the visible index set. The mesh payload and hashes change; the mask must be part of cache identity and geometry must be regenerated when neighbor coverage changes ([StandaloneViewer.cpp:4846](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:4846>)).
None of those proves it safe to remove cap, ground or connector geometry. Those surfaces can be visible in some views.
## Ranked plan and savings
The logs support opportunity ceilings and one modeled skirt estimate, **not a credible expected realized saving per generator**. Worker-seconds cannot be read as wall-time reduction.
1. **Audit and reduce exact column/sample misses.** The measured column pool is 2.699576 SW and 2.812932 FI worker-seconds. The flatField run has 4.675822 worker-seconds across the entire far pool but no reliable stage split. These are ceilings, not expected savings; the cache is already active. R14’s LOD8 overlap report recorded 32,738 repeated neighboring XY evaluations, including 30,446 cross-worker repeats ([Round14:41](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:41>)); it did not price those repeats in worker-seconds.
2. **Skip hidden skirt faces for a fixed neighbor mask.** R23 measured 87.4% hidden SW and 87.0% hidden FI skirt triangles, then modeled savings by applying those fractions to timer totals ([Research23:54](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research23/REPORT.md:54>)). Applying them to the L55 far-LOD skirt timers gives a **cross-run modeled gross** of about 0.690 SW and 0.854 FI worker-seconds; actual savings will be lower because triangle share is not time share and edge scanning remains. flatField has no comparable reliable skirt timer.
3. **Test further LOD8 sampling changes or an LOD7–8 merge only as versioned geometry changes.** No saving is forecast. Round14 already moved LOD8 interior sampling to MC spacing while retaining 128-voxel boundary strips; its fresh W12 comparison reports 3.206 versus 13.534 worker-seconds for the default and step-128 control ([Round14:47](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:47>), [Round14:49](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:49>)). That gain is already harvested, not available again. Any further change needs a far-horizon silhouette gate.
4. **Do not coarsen connector stations on current evidence.** The LOD6 connector timer is an historical 1.335 worker-second inclusive ceiling from L52, not a forecast. The two-times station test failed: 1,349 holes and 1,690 floaters, with hole IDs increasing from 8 to 221 ([Linux15:53](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:53>)). At the calculated clip distances, nominal axial station spacing is about 68 px when transverse to view; it is not generally subpixel.
5. **Defer far jobs only for perceived readiness.** Reordering can change when far tiles appear but saves **0 worker-seconds** if all tiles are still generated. L60’s whole-view wall matrix does not provide per-generator far-stage savings for periodic hills, mirrored relief, Motifs, Caves or Lattice; I would not transfer SW numbers to them.
## Gates and remaining uncertainty
For connector or refinement changes, keep the Linux15 screen-space gate: 1 px maximum normal displacement, shading delta at most 1/255, no missing surface, hole, crack or floater, and no worse adjacent-LOD swap ([Linux15:23](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)). Doubling refinement already failed badly across LOD4–8 ([Linux15:33](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:33>)).
Retain Round14’s ground/cap agreement checks, cap-band census, 2,520/2,520 upward-ray coverage and worker-count determinism ([Round14:88](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:88>), [Round14:90](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:90>), [Round14:92](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:92>), [Round14:96](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:96>)). Add matched 1440p horizon captures with a measured silhouette-pixel delta for any far-grid or LOD-merge change; Round14 has captures, but they are not a quantitative silhouette gate.
Assumptions: projected sizes use the Linux15 nearest-display distances and the 25 cm, 16-coarse-cell owner settings; they are not per-tile occlusion measurements. L55, L52 and L53 use different runs and tile counts, so their figures are not interchangeable. L55 aggregates connector and stratum repair poorly; the LOD6 per-tile connector timer overlaps column computation. No scripts, tests, builds, binaries or tree changes were made.
@@ -0,0 +1,40 @@
# R31 — Far LOD (4–8) inventory and cuts
Worker-seconds below are summed CPU time across workers, not wall time. The L55, R28 and L60 runs have different tile counts; treat them as cost evidence, not a matched before/after comparison.
## Inventory and visibility
Far LODs are not automatically offscreen. On the 25 cm owner plan, the L15 gate puts the closest displayed tiles at about 192, 384, 768, 1,536 and 3,072 m for LOD4–8. At the owner’s 0.61 mrad/pixel reference, one pixel spans roughly 0.12, 0.23, 0.47, 0.94 and 1.87 m. The corresponding MC cell widths are about 8–128 m. That spacing alone does not predict surface error, but it rules out treating the far rings as invisible: terrain silhouettes, exposed caps and tunnel openings can reach the near-field camera. [Linux15 gate](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)
| Work | Output and visibility | Measured cost |
|---|---|---:|
| Grid MC — [`generateTile`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2642>) | Coarse 3D field surface: terrain, strata and passage walls. It contributes to horizon silhouettes and can be directly visible inside passages. | SW LOD4–8 MC exclusive work totals **2.248 worker-s**; LOD8 alone is **1.244 s**. This is the full MC stage excluding columns and skirts, not just its grid-fill timer. |
| Connector repair — [`AppendFarConnectorShells`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:244>) | Detects missing coarse passage walls and adds shell panels. LOD6+ uses axial samples and four cross-section directions; these walls can be visible down a passage. | R22 reports **1.335 worker-s** at SW LOD6. L55 per-tile records show long connector spans (one LOD6 tile: 17.6 ms) alongside 15.9 ms of column computation. These timers overlap; don’t add them as independent costs. Native surface horizon tiles at LOD7–8 can skip far-shell repair ([code](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2322>)). |
| Cap refinement — [`RefineFarNativeCap`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:693>) | Adds downward-facing ceiling triangles from the cap heightfield. Visible from below or within exposed strata. | **0.870 worker-s** across SW LOD4–8, excluding its column work. |
| Ground columns/refinement — [`RefineFarNativeGround`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:389>) | LOD5+ samples columns to add the ground sheet and replace nearby upward-facing MC faces. The resulting ground and horizon remain visible from above. | Ground-refinement geometry itself is only about **0.00044 worker-s** exclusive in the SW summary. Its heightfield evaluations are counted in the larger column pool. |
| Skirts — [`EmitSkirts`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2130>) | Extends tile edges to cover cracks. Only faces exposed by the active neighbor mask need drawing; outer clip edges can remain visible. | **0.789 worker-s** and **425,784 triangles** across SW LOD4–8. The viewer already filters skirt faces by neighbor mask ([mask logic](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:105>)). |
The L55 SW LOD4–8 pool is **7.878 worker-s**: LOD4 1.426, LOD5 0.553, LOD6 1.940, LOD7 1.103, LOD8 2.857. The measured column-evaluation share is **2.700 worker-s**. Remaining work includes MC, cap refinement, skirts and shell/boundary work; connector timers overlap column computation. [L55 per-LOD rows](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>) [R28 cost ledger](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research28/REPORT.md:20>)
## Cuts and ranked plan
1. **Reuse exact samples across tiles and LODs.** Reuse heightfield or density results only for identical recipe, plan and integer sample coordinates, preserving the calculation order. This can leave output unchanged. But the shared heightfield cache is already on by default, so **2.700 worker-s is only the ceiling for eliminating all remaining SW far-column computation, not expected duplicate work**. Count actual cross-LOD misses before crediting savings. [Cache path](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2100>)
2. **Skip skirt faces hidden by the current neighbor mask.** This preserves rendered output for a fixed neighbor set, but changes mesh payload and hashes. Pass the mask before extrusion, include it in cache identity, and rebuild if coverage changes. Applying the prior 87.4% hidden-triangle share to SW far skirts suggests **at most 0.690 worker-s gross**; deleting every far skirt is an absolute **0.789 worker-s** ceiling. The estimate is optimistic: triangle share is not a time share, and edge scanning remains. [R23 skirt model](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research23/REPORT.md:54>)
3. **Try bounded screen-space refinement changes.** This changes far geometry, so require peer determinism and a recipe version bump. Uniformly doubling refinement spacing is already rejected: LOD4–8 exceeded the 1 px geometry and shading gates and introduced holes and cracks. Connector station doubling also opened holes. Credit **zero savings** to those settings. The narrower cap-refinement budget at stake for SW is 0.870 worker-s, but no safe fraction is measured. [Linux15 results](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:33>)
The existing LOD8 change is already a successful cut, not future headroom: the interior now uses MC spacing (512 voxels / 128 m at 25 cm), retaining 128-voxel boundary strips. R14 measured the 8-tile BuildHost LOD8 p95 falling **76.7%**, from 59.848 to 13.933 ms; its fresh Standalone comparison reduced LOD8 work from 13.534 to 3.206 worker-s. Further coarsening should be treated as a new candidate. [Round14](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:7>)
4. **Defer far tiles only for perceived readiness.** This saves **0 worker-s**; it only moves when the work runs. Measure near-ring readiness and first useful presentation separately from whole-view completion.
**Per-generator savings evidence:** SW has the breakdown above. R28 measured **4.676 worker-s** for flatField LOD4–8, but without the same stage split. L55 measured **13.653 worker-s** for Floating Islands LOD4–8; its far skirt pool is 0.982 worker-s, or roughly 0.854 worker-s under the same optimistic hidden-triangle model. Do not transfer SW column or cap costs to FI. For periodic hills, mirrored relief, Motifs, Caves and Lattice, L60 reports whole-view wall times but no comparable far worker-stage breakdown; a per-generator savings estimate is not supported by these logs. [R28 generator ledger](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research28/REPORT.md:12>) [L60 matrix](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux60/REPORT.md:23>)
## Gates
- Run matched LOD4–8 views and enforce the existing **≤1 px maximum geometry error**, **≤1/255 shading delta**, no-worse adjacent-LOD swaps, and no missing surface, hole, crack or floater. Keep the negative controls that prove the checks catch regressions. [Linux15 gate](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)
- For connectors, compare stable tile/ray/station IDs and test all four directions. For caps, check the existing ray coverage and ground/cap agreement. Review matched near and horizon captures for silhouette changes.
- Require Clang/GCC and W1/W8/W12 peer hashes. Bump recipe/cache identity for changed mesh payload or geometry.
- Compare unprofiled whole-view medians and per-LOD worker totals on matched tile sets; don’t infer wall savings by dividing every worker delta by twelve.
I read code, reports, logs and the web. Unreal’s documentation also describes LOD selection in terms of projected screen size, which supports using the owner’s screen-space gate here. [Epic LOD documentation](https://dev.epicgames.com/documentation/en-us/unreal-engine/creating-and-using-lods-in-unreal-engine) No scripts, tests, builds, binaries or tree changes were made.
@@ -4,6 +4,27 @@
namespace VoxelClipmapDesiredTiles namespace VoxelClipmapDesiredTiles
{ {
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE int32 ClampInt32(int64 Value)
{
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
}
struct FParameters struct FParameters
{ {
int32 ClipRadius = 3; int32 ClipRadius = 3;
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor) FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
{ {
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor) FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor) FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{ {
return -FloorDiv(-Value, Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
} }
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment, FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis, const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax) int32& OutAxisMin, int32& OutAxisMax)
{ {
const int32 BaseMin = CenterAxis - Radius; const int64 SafeRadius = FMath::Max<int64>(0, Radius);
const int32 BaseEnd = CenterAxis + Radius + 1; const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment; const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1; const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
}; };
AlignAxis(Center.X, OutMin.X, OutMax.X); AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y); AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
{ {
return Radius; return Radius;
} }
return FMath::Max(Radius, const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel); const int64 Scale = static_cast<int64>(uint32{1} << static_cast<uint32>(SafeLevel));
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
+ Scale - 1;
const int64 DesiredRadius = Numerator / Scale;
return static_cast<int32>(FMath::Clamp<int64>(
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
} }
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel, FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
return; return;
} }
OutMinChunkZ = CenterChunkZ - ViewDistanceDown; OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp; OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
if (bHasStrate) if (bHasStrate)
{ {
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks; OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks; OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
} }
} }
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
for (int32 Level = 0; Level <= MaxLevel; ++Level) for (int32 Level = 0; Level <= MaxLevel; ++Level)
{ {
const int32 Pow = 1 << Level; const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(Center, Pow); const FIntVector CenterLevel = FloorDiv(Center, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius; const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
FIntVector RingMin, RingMax; FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax); AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
int32 DeltaZMin = RingMin.Z - CenterLevel.Z; int64 TileMinZ = RingMin.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z; int64 TileMaxZ = RingMax.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32) if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{ {
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z); TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z); TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
} }
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ) for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y; for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY) for (int64 X = RingMin.X; X <= RingMax.X; ++X)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
{ {
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ); const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
const int32 TileMinZ = Tile.Z << Level; static_cast<int32>(Z));
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1; const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ) const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
{ {
continue; continue;
} }
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0) if (Level > 0)
{ {
const bool bCovered = const bool bCovered =
(2 * Tile.X >= PreviousRingMin.X) (2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X) && (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y) && (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y) && (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z) && (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z); && (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
if (bCovered) if (bCovered)
{ {
continue; continue;
@@ -332,16 +332,33 @@ struct FVoxelTileKey
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {} FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
// Cell size (sampling step) in voxels, and tile extent in voxels per axis. // Cell size (sampling step) in voxels, and tile extent in voxels per axis.
int32 StepVoxels() const { return 1 << Level; } int32 StepVoxels() const
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; } {
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
int32 ExtentVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
}
// Min-corner of the tile in VOXEL coords (what the mesher takes). // Min-corner of the tile in VOXEL coords (what the mesher takes).
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); } FIntVector OriginVoxels() const
{
const int64 Extent = ExtentVoxels();
const auto ScaleAxis = [Extent](int32 Axis)
{
return static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
};
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
}
// Tile centre in world cm (for distance sorting / LOD selection). // Tile centre in world cm (for distance sorting / LOD selection).
FVector CenterCm() const FVector CenterCm() const
{ {
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE; const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext); return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
} }
+16 -4
View File
@@ -17,7 +17,13 @@ from source_stamp import source_stamp
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[3]
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l39")) OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l39"))
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
def limit_prefix() -> list[str]:
mode = os.environ.get("VOXELFORGE_LIMIT_MODE", "as")
if mode not in ("as", "rss"):
raise RuntimeError(f"unsupported process limit mode: {mode}")
return ["taskset", "-c", "0-11", "prlimit", f"--{mode}=6442450944", "--"]
RECIPE_RE = re.compile( RECIPE_RE = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
re.M) re.M)
@@ -57,7 +63,7 @@ def bounded_run(command: list[str], *, timeout: int = 1800,
stdout: Any = subprocess.PIPE) -> subprocess.CompletedProcess[str] | None: stdout: Any = subprocess.PIPE) -> subprocess.CompletedProcess[str] | None:
timeout = min(1800, max(1, int(timeout))) timeout = min(1800, max(1, int(timeout)))
try: try:
return subprocess.run([*LIMIT, "timeout", "--signal=KILL", f"{timeout}s", return subprocess.run([*limit_prefix(), "timeout", "--signal=KILL", f"{timeout}s",
*map(str, command)], *map(str, command)],
cwd=cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT,
text=True, encoding="utf-8", errors="replace", timeout=timeout, text=True, encoding="utf-8", errors="replace", timeout=timeout,
@@ -147,7 +153,12 @@ class VerificationRunner:
raise RuntimeError(f"missing executable: {binary}") raise RuntimeError(f"missing executable: {binary}")
digest = sha256_file(binary) digest = sha256_file(binary)
expected_stamp = expected_stamp or self.binary_stamp_overrides.get(str(binary), self.expected) expected_stamp = expected_stamp or self.binary_stamp_overrides.get(str(binary), self.expected)
proc = bounded_run([str(binary), "--print-source-stamp"], timeout=15) probe_env = None
if "sanitize" in binary.name.lower():
probe_env = os.environ.copy()
probe_env["ASAN_OPTIONS"] = "detect_leaks=0:abort_on_error=1:symbolize=1"
probe_env["LSAN_OPTIONS"] = "detect_leaks=0"
proc = bounded_run([str(binary), "--print-source-stamp"], timeout=15, env=probe_env)
output = proc.stdout or "" if proc is not None else "" output = proc.stdout or "" if proc is not None else ""
embedded = first_stamp(output) embedded = first_stamp(output)
if proc is None or proc.returncode != 0 or embedded is None: if proc is None or proc.returncode != 0 or embedded is None:
@@ -202,6 +213,7 @@ class VerificationRunner:
workers: int = 12, timeout: int = 1800, workers: int = 12, timeout: int = 1800,
layout_path: Path | None = None, layout_path: Path | None = None,
max_level: int = -1, max_level: int = -1,
clip_radius: int = 3,
expected_strate_styles: dict[str, int] | None = None, expected_strate_styles: dict[str, int] | None = None,
script_inputs: list[Path] | None = None, script_inputs: list[Path] | None = None,
allow_nonzero: bool = False, allow_nonzero: bool = False,
@@ -215,7 +227,7 @@ class VerificationRunner:
clean_flags = [flag for flag in flags if not flag.startswith("--resolved-layout-output=")] clean_flags = [flag for flag in flags if not flag.startswith("--resolved-layout-output=")]
command = [str(binary), str(data), f"--workers={workers}", command = [str(binary), str(data), f"--workers={workers}",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--force-generate", f"--clip-radius={clip_radius}", "--max-clip-level=8",
f"--max-level={max_level}", f"--max-level={max_level}",
f"--recipe={recipe}", *clean_flags, f"--resolved-layout-output={layout_path}"] f"--recipe={recipe}", *clean_flags, f"--resolved-layout-output={layout_path}"]
log = OUT / f"{name}.log" log = OUT / f"{name}.log"
+259 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Run the reusable VoxelForge verification tiers: fast, medium, or full.""" """Run the reusable VoxelForge verification tiers: fast, medium, full, or sanitize."""
from __future__ import annotations from __future__ import annotations
import argparse import argparse
@@ -33,7 +33,7 @@ OWNER = ROOT / "Standalone/data/owner_world.json"
OUT_DEFAULT = ROOT / "Saved/Linux/l42" OUT_DEFAULT = ROOT / "Saved/Linux/l42"
PARSER = argparse.ArgumentParser(description=__doc__) PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("tier", choices=("fast", "medium", "full")) PARSER.add_argument("tier", choices=("fast", "medium", "full", "sanitize"))
PARSER.add_argument("--out", type=Path, default=OUT_DEFAULT, PARSER.add_argument("--out", type=Path, default=OUT_DEFAULT,
help="artifact directory (default: Saved/Linux/l41)") help="artifact directory (default: Saved/Linux/l41)")
PARSER.add_argument("--baseline", default="HEAD", PARSER.add_argument("--baseline", default="HEAD",
@@ -42,7 +42,11 @@ PARSER.add_argument("--overall-timeout", type=int, default=7200,
help="maximum tier wall time in seconds (default: 7200; each child is capped at 1800)") help="maximum tier wall time in seconds (default: 7200; each child is capped at 1800)")
PARSER.add_argument("--speed-matrix", action="store_true", PARSER.add_argument("--speed-matrix", action="store_true",
help="run five interleaved full-view timings for every generator") help="run five interleaved full-view timings for every generator")
PARSER.add_argument("--sanitize", action="store_true",
help="in the full tier, also run the standalone short sanitizer sweep")
ARGS = PARSER.parse_args() ARGS = PARSER.parse_args()
if ARGS.sanitize and ARGS.tier != "full":
PARSER.error("--sanitize is available only with the full tier; use the sanitize tier on its own")
OUT = ARGS.out.resolve() OUT = ARGS.out.resolve()
ROUND_NAME = OUT.name ROUND_NAME = OUT.name
os.environ["VOXELFORGE_VERIFY_OUT"] = str(OUT) os.environ["VOXELFORGE_VERIFY_OUT"] = str(OUT)
@@ -2548,6 +2552,253 @@ def run_gcc_supplement(runner: VerificationRunner) -> None:
run_motif_determinism(runner, GCC, compare_reference=True) run_motif_determinism(runner, GCC, compare_reference=True)
SANITIZER_STAGE_SECONDS = 1200
# Clang's integer group diagnoses the defined unsigned left shift inside libstdc++ std::rotl.
# Keep the Clang sweep on UBSan/ASan plus float-cast checks; GCC still runs the requested integer core.
CLANG_SANITIZERS = "address,undefined,float-cast-overflow"
def _sanitize_camera_flag(camera: tuple[float, float, float, float, float]) -> str:
return "--camera=" + ",".join(
[*(f"{value:.6f}" for value in camera[:3]),
*(f"{value:.3f}" for value in camera[3:])])
def _sanitize_build(runner: VerificationRunner, compiler: str, variant: str,
sanitizer_flags: str, *, exclude_unsigned: bool,
deadline: float) -> Path:
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "tmp").mkdir(parents=True, exist_ok=True)
build_root = OUT / "sanitizer-build" / variant
binary_dir = OUT / "bin"
env = os.environ.copy()
env.update({
"TMPDIR": str(OUT / "tmp"),
"VOXELFORGE_VERIFY_OUT": str(OUT),
"VOXELFORGE_BUILD_ROOT": str(build_root),
"VOXELFORGE_BIN_DIR": str(binary_dir),
"VOXELFORGE_LIMITED_BUILD": "1",
})
command = ["bash", str(ROOT / "Standalone/BuildLinux.sh"),
f"--compiler={compiler}", f"--variant={variant}",
"--fp-contract=off", "--jobs=2", f"--sanitize={sanitizer_flags}"]
if exclude_unsigned:
command.append("--exclude-unsigned-integer-overflow")
log = OUT / f"sanitizer-build-{variant}.log"
remaining = max(1, int(deadline - time.monotonic()))
with log.open("w", encoding="utf-8") as stream:
proc = bounded_run(command, timeout=min(600, remaining), env=env, stdout=stream)
ok = proc is not None and proc.returncode == 0
runner.require(f"sanitizer-build-{variant}", ok,
f"compiler={compiler} flags={sanitizer_flags} jobs=2 fp-contract=off "
f"limit={os.environ.get('VOXELFORGE_LIMIT_MODE', 'as')} log={log}")
binary = binary_dir / f"VoxelForgeCoreHeadless-{variant}"
runner.probe_binary(binary)
return binary
def _sanitizer_control(runner: VerificationRunner, compiler: str,
binary_tag: str, sanitizer_flags: str,
*, exclude_unsigned: bool, deadline: float) -> None:
control_dir = OUT / "sanitizer"
control_dir.mkdir(parents=True, exist_ok=True)
source = control_dir / "signed-overflow-control.cpp"
source.write_text(
"#include <climits>\n"
"int main() { volatile int maximum = INT_MAX; volatile int one = 1; "
"volatile int result = maximum + one; return result == 0; }\n",
encoding="utf-8")
executable = control_dir / f"signed-overflow-control-{binary_tag}"
compile_command = [compiler, "-std=c++20", "-O1", "-fno-fast-math",
"-ffp-contract=off", f"-fsanitize={sanitizer_flags}",
"-fno-sanitize-recover=undefined", str(source), "-o", str(executable)]
if exclude_unsigned:
compile_command.insert(-2, "-fno-sanitize=unsigned-integer-overflow")
compile_log = control_dir / f"signed-overflow-control-{binary_tag}-build.log"
compile_remaining = max(1, int(deadline - time.monotonic()))
with compile_log.open("w", encoding="utf-8") as stream:
built = bounded_run(compile_command, timeout=min(60, compile_remaining), stdout=stream)
runner.require(f"sanitizer-control-build-{binary_tag}",
built is not None and built.returncode == 0,
f"test-only INT_MAX+1 compiled with {sanitizer_flags}")
run_log = control_dir / f"signed-overflow-control-{binary_tag}.log"
run_remaining = max(1, int(deadline - time.monotonic()))
with run_log.open("w", encoding="utf-8") as stream:
caught = bounded_run([str(executable)], timeout=min(30, run_remaining), stdout=stream)
output = run_log.read_text(encoding="utf-8", errors="replace")
runner.require(f"sanitizer-control-caught-{binary_tag}",
caught is not None and caught.returncode != 0
and "signed integer overflow" in output,
f"signed overflow exited {caught.returncode if caught else 'TIMEOUT'}; log={run_log}")
def run_sanitizer_stage(runner: VerificationRunner) -> None:
"""Run small random-camera LOD 0..8 sweeps through both instrumented builds."""
previous = {key: os.environ.get(key) for key in (
"VOXELFORGE_LIMIT_MODE", "ASAN_OPTIONS", "LSAN_OPTIONS", "UBSAN_OPTIONS")}
os.environ["VOXELFORGE_LIMIT_MODE"] = "rss"
os.environ["ASAN_OPTIONS"] = "detect_leaks=0:abort_on_error=1:symbolize=1"
os.environ["LSAN_OPTIONS"] = "detect_leaks=0"
os.environ["UBSAN_OPTIONS"] = "print_stacktrace=1:halt_on_error=1"
deadline = time.monotonic() + SANITIZER_STAGE_SECONDS
failures: list[str] = []
try:
clang_binary = _sanitize_build(runner, "clang++-19",
f"{ROUND_NAME}-clang-sanitize",
CLANG_SANITIZERS,
exclude_unsigned=True, deadline=deadline)
gcc_binary = _sanitize_build(runner, "g++",
f"{ROUND_NAME}-gcc-sanitize", "address,undefined",
exclude_unsigned=False, deadline=deadline)
_sanitizer_control(runner, "clang++-19", "clang",
CLANG_SANITIZERS,
exclude_unsigned=True, deadline=deadline)
_sanitizer_control(runner, "g++", "gcc", "address,undefined",
exclude_unsigned=False, deadline=deadline)
profile_paths = {profile: OUT / "fixtures" / f"style-override-profile-{profile}.json"
for profile in PROFILE_NAMES}
if not all(path.is_file() for path in profile_paths.values()):
runner.run_stage("sanitizer-build-profile-fixtures", LINUX53 / "BuildProfileFixturesL53.py",
["--out", str(OUT / "fixtures")], binary=CLANG, data=OWNER,
recipe="surface-world", style=None, env=env_for(),
script_inputs=[OWNER])
for profile, fixture in profile_paths.items():
manifest = OUT / "fixtures" / f"style-override-profile-{profile}-manifest.json"
if not manifest.is_file():
name = f"sanitizer-profile-camera-manifest-{profile}"
runner.run_cpp(name, CLANG, fixture, "surface-world",
[f"--world-assembler-manifest-output={manifest}",
"--render-distance-chunks=0"], expected_style=None,
expected_strate_styles={"0": 2, "1": 1}, workers=1,
max_level=-1, clip_radius=0,
layout_path=OUT / "layouts" / f"{name}.json")
caves_fixture = OUT / "fixtures" / "caves.json"
if not caves_fixture.is_file():
caves_fixture = build_caves_fixture(runner, CLANG)
lattice_fixture = OUT / "fixtures" / "lattice-l58.json"
if not lattice_fixture.is_file():
lattice_fixture = build_lattice_fixture(runner, CLANG)
motif_fixture = OUT / "fixtures" / "style-override-motifs-slice1.json"
if not motif_fixture.is_file():
motif_fixture = build_motif_fixture(runner)
cave_layout = OUT / "layouts" / "native-caves-field-clang.json"
lattice_layout = OUT / "layouts" / "native-lattice-field-clang.json"
motif_layout = OUT / "layouts" / "motif-slice1-capsule-reachability-clang.json"
if not cave_layout.is_file():
_, cave_layout = run_native_caves_field_audit(runner, CLANG)
if not lattice_layout.is_file():
_, lattice_layout = run_native_lattice_field_audit(runner, CLANG)
if not motif_layout.is_file():
_, motif_layout = run_motif_fixture_audits(runner, CLANG)
owner_cameras = seeded_camera_positions(5)
cases: list[tuple[str, Path, str, list[str], int | None,
dict[str, int] | None, list[tuple[float, float, float, float, float]]]] = [
("natural-sw", OWNER, "surface-world", [], 2, {"0": 2, "1": 1},
[owner_cameras[0], owner_cameras[3]]),
("floating-islands", OWNER, "floating-islands", [], 2, {"0": 2, "1": 1},
[owner_cameras[2], owner_cameras[4]]),
]
for profile in PROFILE_NAMES:
cases.append((f"profile-{profile}", profile_paths[profile], "surface-world",
[], 2, {"0": 2, "1": 1}, profile_camera_positions(profile, count=2)))
cases.extend([
("caves", caves_fixture, "caves", [], 2, {"0": 2, "1": 1},
cave_camera_positions(cave_layout, count=2)),
("motifs", motif_fixture, "surface-world", [], None, {"0": 2, "1": 1},
[row[1] for row in motif_camera_rows(motif_layout) if row[0].startswith("random")]),
("lattice", lattice_fixture, "lattice", ["--lattice-strate=1"], None,
{"0": 2, "1": 1, "2": 1}, lattice_camera_positions(lattice_layout, count=2)),
])
binaries = (("clang", clang_binary), ("gcc", gcc_binary))
for compiler_tag, binary in binaries:
for generator, data, recipe, base_flags, style, strate_styles, cameras in cases:
for camera_index, camera in enumerate(cameras):
if time.monotonic() >= deadline:
failures.append("sanitizer stage time budget expired")
break
name = (f"sanitize-{compiler_tag}-{generator}-camera{camera_index}-"
f"lod0-8")
flags = [*base_flags, "--stream-hash-tiles", "--render-distance-chunks=0",
_sanitize_camera_flag(camera)]
try:
_, output = runner.run_cpp(name, binary, data, recipe, flags,
expected_style=style, expected_strate_styles=strate_styles,
workers=1, max_level=-1, clip_radius=0,
timeout=min(180, max(1, int(deadline - time.monotonic()))),
layout_path=OUT / "layouts" / f"{name}.json")
lod_rows = full_lod_stream_rows(output)
lod_ok = (set(lod_rows) == set(range(9))
and all(lod_rows[level]["tiles"] > 0 for level in range(9)))
runner.require(f"{name}:lod0-8-coverage", lod_ok,
"positive tile sample at each of LOD0 through LOD8")
except VerificationFailure as error:
failures.append(f"{name}: {error}")
# Exercise passage resolution and a live 24 m camera crossing under both runtimes.
for recipe, flags, style in (
("floating-islands", ["--audit-resolved-layout-passages"], 2),
("surface-world", ["--landmark-chamber-demo", "--audit-resolved-layout-passages"], 2),
):
name = f"sanitize-{compiler_tag}-passages-{recipe}-lod0-8"
camera = owner_cameras[0 if recipe == "surface-world" else 2]
try:
_, output = runner.run_cpp(name, binary, OWNER, recipe,
[*flags, "--stream-hash-tiles", "--render-distance-chunks=0",
_sanitize_camera_flag(camera)], expected_style=style,
expected_strate_styles={"0": 2, "1": 1}, workers=1,
max_level=-1, clip_radius=0,
timeout=min(180, max(1, int(deadline - time.monotonic()))),
layout_path=OUT / "layouts" / f"{name}.json")
lod_rows = full_lod_stream_rows(output)
runner.require(f"{name}:lod0-8-coverage",
set(lod_rows) == set(range(9))
and all(lod_rows[level]["tiles"] > 0 for level in range(9)),
"passage path streamed at each of LOD0 through LOD8")
except VerificationFailure as error:
failures.append(f"{name}: {error}")
crossing_name = f"sanitize-{compiler_tag}-surface-crossing-lod0-8"
try:
_, crossing_output = runner.run_cpp(crossing_name, binary, OWNER,
"surface-world", ["--stream-hash-tiles", "--render-distance-chunks=0",
"--camera=-12,-118,-20,0,0", "--camera-step=24,0,0", "--region-crossing"],
expected_style=2, expected_strate_styles={"0": 2, "1": 1},
workers=1, max_level=-1, clip_radius=0,
timeout=min(180, max(1, int(deadline - time.monotonic()))),
layout_path=OUT / "layouts" / f"{crossing_name}.json")
lod_rows = full_lod_stream_rows(crossing_output)
moving = re.search(r"^\[MovingTileLatency\] entering_tiles=(\d+)",
crossing_output, re.M)
crossing_ok = (set(lod_rows) == set(range(9))
and all(lod_rows[level]["tiles"] > 0 for level in range(9))
and moving is not None and int(moving.group(1)) > 0)
runner.require(f"{crossing_name}:crossing-and-lod0-8", crossing_ok,
f"entering_tiles={moving.group(1) if moving else 'MISSING'}")
except VerificationFailure as error:
failures.append(f"{crossing_name}: {error}")
edit_fixtures = [path for base in (ROOT / "Standalone/data", OUT / "fixtures")
if base.is_dir() for path in base.rglob("*.json")
if "edit" in path.name.lower()]
runner.require("sanitizer-edit-fixture", True,
(f"found {len(edit_fixtures)} edit fixture(s): "
+ ", ".join(str(path) for path in edit_fixtures)) if edit_fixtures
else "no edit fixture exists under Standalone/data or this run's fixtures")
runner.require("sanitizer-stage-findings", not failures,
"; ".join(failures) if failures else
"all Clang/GCC ASan+UBSan generator, passage, crossing, and random-camera cases passed")
finally:
for key, value in previous.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
def _tier_deadline(_signum: int, _frame: object) -> None: def _tier_deadline(_signum: int, _frame: object) -> None:
raise VerificationFailure( raise VerificationFailure(
f"{ARGS.tier} tier exceeded its {ARGS.overall_timeout}s overall timeout") f"{ARGS.tier} tier exceeded its {ARGS.overall_timeout}s overall timeout")
@@ -2566,7 +2817,9 @@ def main() -> int:
for binary in (CLANG, GCC, RENDERER): for binary in (CLANG, GCC, RENDERER):
if binary.is_file(): if binary.is_file():
runner.probe_binary(binary) runner.probe_binary(binary)
if ARGS.tier == "fast": if ARGS.tier == "sanitize":
run_sanitizer_stage(runner)
elif ARGS.tier == "fast":
owners = run_owner_passages(runner, (CLANG,)) owners = run_owner_passages(runner, (CLANG,))
run_landmark_reachability(runner, (CLANG,)) run_landmark_reachability(runner, (CLANG,))
run_regression_controls(runner) run_regression_controls(runner)
@@ -2594,6 +2847,9 @@ def main() -> int:
if ARGS.speed_matrix: if ARGS.speed_matrix:
run_speed_matrix(runner) run_speed_matrix(runner)
if ARGS.sanitize:
run_sanitizer_stage(runner)
runner.assert_binaries_current() runner.assert_binaries_current()
runner.assert_current_tree("at tier completion") runner.assert_current_tree("at tier completion")
elapsed = time.monotonic() - started elapsed = time.monotonic() - started