fix(morphology): BuildChunkCache under-bounds room/tunnel collection when Min > Max

Third instance of the same class, and the worst one -- found by the Sol-High
read-only audit (VF-05) and verified against the code before acting.

MaxInfluence, RoomZBuffer and EvaluateSDF's Margin all derive from
MaxRoomRadius / TunnelMaxRadius, while the radii are Lerp(Min, Max, hash), which
yields up to max(Min, Max). A room able to reach a chunk can therefore sit in a
cell the collect region never visited.

Worse than the two fixed earlier today because:
  - it is TunnelNetwork, the largest archetype;
  - BuildChunkCache is called by BOTH density paths (FRoomGraphSource calls it
    rather than transcribing it), so this was never an op-stack bug -- it is in
    the shipped original code and always has been;
  - the failure mode is a window-invariance break (ARCHITECTURE 8.4): whether a
    room exists depends on which chunk you queried from, which in multiplayer
    means two peers generate different geometry from the same seed.

Four bound sites now use RoomRadiusEnvelope / TunnelRadiusEnvelope; the two
duplicated copies of the formula still compute the identical expression. No
Lerp, placement, hash or bStore line changed -- with correctly ordered params
max(Min,Max) == Max, so this is bit-identical. A no-op at correct values is the
acceptance signal.

Also adds a reviewer's header to AUDIT-2026-08-CODEX.md marking which findings I
verified (VF-05 confirmed, VF-02 premise confirmed, VF-03 evidence overstated --
its cited fixture corroboration does not exist) and which are unverified leads.

Not built -- Jahni builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 17:32:09 +02:00
parent 8ef4d7dc09
commit cab8e8fe55
4 changed files with 358 additions and 6 deletions
@@ -124,9 +124,13 @@ void VoxelCaveMorphology::BuildChunkCache(
// MaxInfluence = how far a room body / tunnel TUBE reaches PERPENDICULAR to its
// anchor — NOT its length. A room or tunnel whose anchor lies within MaxInfluence
// of a box can touch a voxel inside that box.
// Envelope conservatif / conservative bound: Lerp accepts inverted endpoints,
// so max(Min, Max) covers either radius without changing the authored roll.
const float RoomRadiusEnvelope = FMath::Max(Params.MinRoomRadius, Params.MaxRoomRadius);
const float TunnelRadiusEnvelope = FMath::Max(Params.TunnelMinRadius, Params.TunnelMaxRadius);
const float MaxInfluence = FMath::Max(
Params.MaxRoomRadius,
Params.TunnelWarpStrength + Params.TunnelMaxRadius
RoomRadiusEnvelope,
Params.TunnelWarpStrength + TunnelRadiusEnvelope
) + Params.SDFBlendRadius;
const float MaxTunnelLen = FMath::Max(Params.MaxTunnelLength, 0.0f);
@@ -165,10 +169,10 @@ void VoxelCaveMorphology::BuildChunkCache(
// Vertical range for room CENTER placement.
//=========================================================================
// Buffer = seal thickness + max room half-height.
// This guarantees the tallest possible room (MaxRoomRadius * RoomHeightRatio)
// This guarantees the tallest possible room (RoomRadiusEnvelope * RoomHeightRatio)
// fits entirely within the seal boundary — no room gets its ceiling or floor
// cut flat by the seal. Smaller rooms have proportionally more margin.
const float RoomZBuffer = Params.MaxRoomRadius * Params.RoomHeightRatio;
const float RoomZBuffer = RoomRadiusEnvelope * Params.RoomHeightRatio;
const float StrateMinZ = Params.StrateBottomWorldZ + Params.BoundarySealThickness + RoomZBuffer;
const float StrateMaxZ = Params.StrateTopWorldZ - Params.BoundarySealThickness - RoomZBuffer;
const float StrateRangeZ = StrateMaxZ - StrateMinZ;
@@ -868,9 +872,11 @@ float VoxelCaveMorphology::EvaluateSDF(
const FStrateGenerationParams& Params,
uint32 Seed, int32 StrateIndex)
{
const float RoomRadiusEnvelope = FMath::Max(Params.MinRoomRadius, Params.MaxRoomRadius);
const float TunnelRadiusEnvelope = FMath::Max(Params.TunnelMinRadius, Params.TunnelMaxRadius);
const float Margin = FMath::Max(
Params.MaxRoomRadius,
Params.TunnelWarpStrength + Params.TunnelMaxRadius
RoomRadiusEnvelope,
Params.TunnelWarpStrength + TunnelRadiusEnvelope
) + Params.SDFBlendRadius;
FChunkSDFCache TempCache;