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>
5.2 KiB
Codex task 006 — the same Min > Max under-bound, in BuildChunkCache (both density paths)
Owner: Codex (Model Luna, xHigh) · Orchestrator: Claude · Branch: experimental
Status: specified, not started
Kind: ⚠️ correctness of a collection bound. Third instance of this class; the worst of the three.
Credit: found by the Sol-High read-only audit (AUDIT-2026-08-CODEX.md, VF-05) and verified
against the code before being specified.
Why this exists
CODEX-TASK-004 fixed two cell-sweep pads that assumed the field named Max* was numerically the
larger one. The same defect exists in VoxelCaveMorphology.cpp, and it matters more, for three
reasons:
- It is
TunnelNetwork— the largest and most-used archetype. BuildChunkCacheis called by both density paths: the originalswitchandFRoomGraphSource, which deliberately calls it rather than transcribing it. This is not an operator-stack bug — it is in the shipped original code and always has been.- Its failure mode is a window-invariance break (
ARCHITECTURE §8.4), not just a missing room: whether a room exists depends on which chunk you queried from. In a multiplayer game that means two peers generate different geometry from the same seed.
The mechanism
Radii are interpolated:
Room.RadiusXY = FMath::Lerp(Params.MinRoomRadius, Params.MaxRoomRadius, SizeFactor); // ~262
const float RadA = FMath::Lerp(Params.TunnelMinRadius, Params.TunnelMaxRadius, FactorA); // ~467
const float RadB = FMath::Lerp(Params.TunnelMinRadius, Params.TunnelMaxRadius, FactorB); // ~468
FMath::Lerp(A, B, t) with t ∈ [0,1] yields anywhere in [min(A,B), max(A,B)] — it does not
require A ≤ B. But every bound derived from those radii reads only the Max* field:
| site | line | expression |
|---|---|---|
MaxInfluence |
~127–130 | FMath::Max(Params.MaxRoomRadius, Params.TunnelWarpStrength + Params.TunnelMaxRadius) + Params.SDFBlendRadius |
CollectMargin |
~152 | 2.0f * MaxTunnelLen + MaxInfluence (inherits it — no separate edit needed) |
RoomZBuffer |
~171 | Params.MaxRoomRadius * Params.RoomHeightRatio |
EvaluateSDF's Margin |
~871–874 | the same expression as MaxInfluence, duplicated |
With MinRoomRadius > MaxRoomRadius, rooms larger than MaxInfluence are generated, so a room that
can reach a chunk may sit in a cell the collect region never visited. RoomReachesSearchBox uses the
actual radius and is therefore correct — but it can only test rooms that were collected at all.
The fix
Derive bound-only envelopes and use them at all four sites:
const float RoomRadiusEnvelope = FMath::Max(Params.MinRoomRadius, Params.MaxRoomRadius);
const float TunnelRadiusEnvelope = FMath::Max(Params.TunnelMinRadius, Params.TunnelMaxRadius);
MaxInfluence→FMath::Max(RoomRadiusEnvelope, Params.TunnelWarpStrength + TunnelRadiusEnvelope) + Params.SDFBlendRadiusRoomZBuffer→RoomRadiusEnvelope * Params.RoomHeightRatioEvaluateSDF'sMargin→ the same corrected expression.
⚠️ MaxInfluence and EvaluateSDF's Margin are the same formula written twice. They must stay
identical. If a shared helper is natural here, use one — two copies of a rule that must agree is a
bug factory, and this file already has the duplication. If you introduce a helper, keep it local to
this translation unit and do not change either call site's semantics.
⛔ DO NOT reorder the Lerp endpoints
Swapping to Lerp(min, max, t) maps the same hash t to a different radius for the same room,
which changes generated geometry and breaks the eight bit-for-bit equivalence tests. Only the
bounds may become conservative. The three Lerp calls must not be touched at all.
This is the same rule as tasks 003 and 004, and it is the third time it applies.
⚠️ Invariants
- No
Lerpline changes. No room/tunnel placement, hashing, orbStorelogic changes. If your diff touchesRoom.RadiusXY,RadA,RadB, orRoomReachesSearchBox, stop and say so. - The change is strictly conservative: a larger envelope collects more cells, never fewer.
- Exactly one file:
Source/VoxelForge/Private/VoxelCaveMorphology.cpp. - Comments are French + English; match the file. Say why the envelope is
max(Min, Max)— the next reader must not "simplify" it back toMaxRoomRadius.
Acceptance
- One file changed, a handful of lines.
- With correctly ordered params,
max(Min, Max) == Max, so every number is bit-identical and this is a NO-OP. That is the acceptance signal. The eight equivalence tests, every box-verdict line and everyviolationscount must be unchanged after the build. A moved number means the diff did something it should not have. - It only changes behaviour for an asset whose range is inverted — which is precisely the case that was silently producing window-dependent geometry.
Notes for the reviewer (Claude)
- Confirm all four bound sites use the envelopes, and that
CollectMargininherits rather than being edited separately. - Confirm
MaxInfluenceandEvaluateSDF'sMarginstill compute the identical expression. - Confirm no
Lerpargument order changed anywhere in the file.