fix: signed-overflow UB in VoxelHash made cave placement compiler-dependent; all generators LOD0-8; speed matrix (L60)

VoxelHash::Cell/Pair, legacy Cellular3D and the shared Core cellular hash added 0x7FFFFFFF to a signed
int32 before casting to uint32; overflow is undefined, so Clang and GCC placed cave passage 1 differently
(first differing sample at LOD1 tile (0,64,-512)). The bias is now done in unsigned arithmetic, in both the
standalone copy and the Unreal plugin header (Source/VoxelForge/Public/VoxelCaveMorphology.h). Native
caves, motifs and lattice now stream LOD0-8 with skirts and cross-level transitions. New verify.py
--speed-matrix runs every generator on one asserted-identical view (5 interleaved runs): motifs 1.33,
profiles 1.30-1.32, caves 1.41, natural SW 1.67, lattice 1.70, FI 4.93 s whole view. fast/medium/full PASS,
Clang/GCC peer hashes equal. Research R29 (open-edge ledge) and R30 (compiler-divergence suspects).

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 01:50:15 +01:00
1 parent fbae82e024
commit c0c204ba50
15 files changed
+938 -65

No files matched your search

@@ -261,8 +261,12 @@ namespace VoxelHash
FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed)
{
uint32 H = Seed;
H ^= Mix((uint32)(CellX + 0x7FFFFFFF));
H ^= Mix((uint32)(CellY + 0x7FFFFFFF) * 2654435761u);
// Bias in unsigned arithmetic. Adding the signed cell coordinate first overflows for
// positive cells and makes the world hash compiler-dependent.
const uint32 BiasedX = static_cast<uint32>(CellX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(CellY) + 0x7FFFFFFFu;
H ^= Mix(BiasedX);
H ^= Mix(BiasedY * 2654435761u);
return Mix(H);
}
@@ -274,10 +278,14 @@ namespace VoxelHash
int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY);
int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY);
uint32 H = Seed ^ 0xDEADBEEF;
H ^= Mix((uint32)(MinX + 0x7FFFFFFF));
H ^= Mix((uint32)(MinY + 0x7FFFFFFF) * 2654435761u);
H ^= Mix((uint32)(MaxX + 0x7FFFFFFF) * 374761393u);
H ^= Mix((uint32)(MaxY + 0x7FFFFFFF) * 668265263u);
const uint32 BiasedMinX = static_cast<uint32>(MinX) + 0x7FFFFFFFu;
const uint32 BiasedMinY = static_cast<uint32>(MinY) + 0x7FFFFFFFu;
const uint32 BiasedMaxX = static_cast<uint32>(MaxX) + 0x7FFFFFFFu;
const uint32 BiasedMaxY = static_cast<uint32>(MaxY) + 0x7FFFFFFFu;
H ^= Mix(BiasedMinX);
H ^= Mix(BiasedMinY * 2654435761u);
H ^= Mix(BiasedMaxX * 374761393u);
H ^= Mix(BiasedMaxY * 668265263u);
return Mix(H);
}
@@ -477,10 +485,13 @@ namespace VoxelNoise
// Hash the neighbor cell to get a feature point position [0,1)
// Using three different hash mixes for X, Y, Z offsets
const uint32 BiasedX = static_cast<uint32>(NX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(NY) + 0x7FFFFFFFu;
const uint32 BiasedZ = static_cast<uint32>(NZ) + 0x7FFFFFFFu;
uint32 H = VoxelHash::Mix(
(uint32)(NX + 0x7FFFFFFF)
^ VoxelHash::Mix((uint32)(NY + 0x7FFFFFFF) * 2654435761u)
^ VoxelHash::Mix((uint32)(NZ + 0x7FFFFFFF) * 374761393u)
BiasedX
^ VoxelHash::Mix(BiasedY * 2654435761u)
^ VoxelHash::Mix(BiasedZ * 374761393u)
);
float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX;