Files
Fr0zkaandClaude Opus 5.5 c0c204ba50 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
2026-10-26 01:50:15 +01:00

6.3 KiB
Raw Permalink Blame History

Ranked suspects

Rank Suspect and fit Cheapest confirming experiment
1 — cave segment query arithmetic — ShapeWorldInternal.h:7124–7199. The cave field chooses a nearest segment using computed distances, a 1e-12 m tie tolerance, and BVH pruning. Small differences in hypot or intermediate arithmetic could change the selected segment, its floor/grade data, or whether a node is visited. That fits stable, spatially repeatable failures on a small set of cave tiles. For one bad tile, find the first differing density lattice coordinate. At that point log the BVH’s visited/pruned nodes and selected segment, then compare with a full segment scan in index order.
2 — cave detection in batched sampling — ShapeWorldInternal.h:3157–3224, 10177–10220. The new X8 and Z-run paths test both manager and resolved cave bounds before falling back to scalar density. A boundary classification or fallback difference could affect coarse samples at particular Z values. The L60 report says broader bulk-versus-scalar diagnostics were tried, so a targeted trace is more useful than repeating that toggle. At the first differing coordinate, record its manager/resolved stratum, batch route, and scalar result bits.
3 — coarse meshing and skirts — VoxelMarchingCubesMesher.cpp:1239–1305, 2070–2172. A density difference near zero can change cell classification or interpolation. Skirts also make extra continuous-position DensityAt calls, with depth scaled by the mesh step. This is a downstream explanation if density hashes match. Print the density, mesh, payload, and topology hash comparisons separately. If density matches but mesh output differs, regenerate one bad tile with skirts disabled and compare the surface mesh.

LOD1–2 path and other checked constructs

The L60 log reports the same resolved-layout fingerprint, 3c14454ca74615d0, for the cave runs across GCC and Clang. That points away from different resolved cave inputs, though a fingerprint is not a byte-for-byte comparison. Each compiler is internally stable across worker counts, and the failures recur on the same six LOD1 and eight LOD2 keys. The report says earlier diagnostics covered segment ordering/ties and bulk-versus-scalar sampling. (Linux60 report, run log)

generateTile uses extent = CHUNK_SIZE << level; in the normal coarse path, LOD1 and LOD2 use steps 2 and 4. Type-4 shelf features can force step 1 on overlapping tiles, while hybrid sharp-feature handling is LOD0-only. The tile still goes through the same marching-cubes mesher. LOD1’s step-2 proof path differs from LOD2’s step-4 path, so proof behavior alone does not explain both levels. Skirts run at these LODs; connector shells are gated to LOD6+, and far stratum shells to LOD7+.

Other legal sources of compiler variation checked:

  • DensityGrid is allocated with SetNumUninitialized; a broken proof-skip invariant could expose unread values. The hash code reads individual mesh fields and density float bits, not struct padding. It does not hash raw object bytes. (mesher allocation, hashing)
  • C++ does not specify the order in which function arguments are evaluated. I found no output-affecting call in these paths with side effects across arguments. (C++ call rules)
  • The cave evaluator uses sqrt and hypot; I found no pow, exp, sin, or atan2 in its runtime field path. GCC can use MPFR to fold constant-argument math builtins, but the matching layout fingerprint makes recipe-planning math a weaker suspect here. (GCC notes)
  • Integer lattice coordinates convert exactly to float within the scene’s range; the cave evaluator then uses doubles. Long-double bounds calculations are present in cave queries. On this Linux target, GCC and Clang likely share the platform ABI and math libraries, but BuildLinux.sh does not pin or report Clang’s standard-library choice.
  • The changed segment std::sort comparator breaks equal centers by segment index, giving a total order for valid segments. So differing std::sort handling of equivalent keys is a weak explanation there. A separate stratum sort compares only bottom Z, so duplicate bottoms could leave equivalent elements in implementation-dependent order. (sort rules, stable sort rules)
  • Signed overflow and invalid shifts would be undefined behavior, but the LOD1–8 extent shifts are in range. No concrete overflow in the affected path stands out.
  • The peer comparator reports only an aggregate failure: it checks density, mesh, payload, and topology hashes, not material hash. Splitting those results is the cheapest first discriminator. (SameTileOutput)

MSVC notes

The segment math, library math variation, argument-order latitude, equivalent-key sort behavior, and any undefined uninitialized/overflow behavior can also affect Windows builds. MSVC’s long double has the same precision and representation as double, unlike the extended-precision Linux ABI, so the long-double query calculations are a particular portability risk. (MSVC type documentation)

CoreNamespacePreamble.h disables FP contraction for Clang, GCC, and MSVC; the Windows result still depends on the project’s effective /fp mode and runtime math implementation. (preamble, MSVC FP modes)