feat(opstack): FRoomGraphSource::EffectOverBox answers spatially -- and AUDIT C2 is fixed
The chain no longer dies at the room source. The criterion is the per-voxel cull
lifted from point to box, which can fail for exactly one reason: Eval starts at
MinSDF = FLT_MAX and only lowers it through a primitive that survives its own
cull, so if no cached primitive survives its cull anywhere in the box, Sdf stays
FLT_MAX across the whole box.
FSdfConvertOp already returned Identity ("la source a repondu pour la paire") and
the twelve detail modifiers already inherited through VF_NoCaveOverBox. One
function learned to answer and fourteen operators became provable -- what the C1
wiring was built for.
Three deliberate choices, all erring toward CPU rather than toward a hole:
- |Perlin3D| <= 2, derived from GradDot + the convex hull of a trilinear lerp,
instead of the header's observed "~[-1,1]". A verdict resting on an observation
is the hole this file spends its life avoiding.
- the op pool is passed to BuildChunkCache, not nullptr: the bake reads OpParams
to place pits and chimneys, so nullptr would under-bound the cache and could
return Identity over a real pit.
- the search box is wider than Eval's, giving a superset of primitives.
The verdict is memoised per box (all twelve modifiers ask the same question), and
the cache is a SECOND per-worker cache so classification cannot disturb a live
generation's hot cache.
AUDIT C2, confirmed 2026-07-28, is fixed on the switch path in the same breath:
GetDensityWithParams now takes required ParamsFingerprint + LayoutVersion. The
alternative this audit section used to recommend -- add chunk Z to the key -- is
insufficient (Interleaved makes Alpha depend on chunk XY too) and destructive
(chunk XY is deliberately absent so gradient probes don't thrash the box, ARCH
8.10). The CRC is taken once per chunk where the params memo already lives, so
the per-voxel cost is two integer compares. The three test call sites pass it
too, so the oracle stops sharing the defect it tests.
Check 4 of the tunnel test no longer asserts "0 proved" -- that assertion would
now forbid the gain. It brute-forces every proved tile voxel by voxel instead and
reports the count, because a false verdict leaves no geometry and no collision
behind it.
The second debt (per-room ops can raise a modifier's amplitude above the strate
params a box bound reads) turned out to be DORMANT, not live: where the source
proves Identity the modifiers' gate never opens, and where it answers Both it
supplies no MaxCarveOverBox so nothing is provable anyway. It goes live the day
the source gains one. Written at the site.
Unbuilt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+27
-6
@@ -221,12 +221,33 @@ evaluates the second chunk against **the room list baked from the first chunk's
|
||||
(so "which archetype owns this chunk" stays unambiguous), which switches the blend off entirely.
|
||||
The one configuration the tests never build is the default one.
|
||||
|
||||
**Fix (unimplemented, deliberately — this is the `switch` path, not the port):** fold the params into
|
||||
the SDF cache key exactly as `FRoomGraphSource` already does — `FCrc::MemCrc32` over the params
|
||||
struct, plus `LayoutVersion`. `FStrateGenerationParams` is pure POD, so a memory CRC cannot produce a
|
||||
false *match*; at worst padding causes a needless rebuild. Err on CPU, never on a wrong room.
|
||||
Alternatively add chunk Z to the key, which is coarser (it rebuilds on every Z step even outside a
|
||||
blend band) but needs no CRC.
|
||||
#### ✅ FIXED 2026-07-28 (the SDF-cache half) — pending build
|
||||
|
||||
The params now travel into the key, and the shape of the fix is worth recording because the obvious
|
||||
version of it was the wrong one.
|
||||
|
||||
`GetDensityWithParams` takes **two new required arguments**, `ParamsFingerprint` and `LayoutVersion`,
|
||||
and both go into the `bNeedRebuild` test next to the existing `(box, strate, seed)`.
|
||||
|
||||
- **Required, not defaulted.** A caller that forgets must fail to compile rather than silently
|
||||
inherit the hole — the same discipline that put `LayoutVersion` inside `FVoxelOpContext`.
|
||||
- **The CRC is computed once per chunk, not per voxel.** `FCrc::MemCrc32` over ~300 bytes on the
|
||||
hottest path in the plugin would have been a real regression; instead it is taken where the params
|
||||
memo already lives (`CP_TunnelFP`, refreshed in the same block that refetches `CP_Tunnel`), so the
|
||||
per-voxel cost is two integer compares.
|
||||
- **Why not "add chunk Z to the key" (the alternative this section used to offer):** it is not
|
||||
actually cheaper *and* it is not sufficient. `Interleaved` makes `Alpha` depend on chunk **XY**
|
||||
too, and chunk XY is *not* pinned by the existing key — the box deliberately outlives the chunk so
|
||||
that `WorldX ± 1` gradient probes don't thrash it (`ARCHITECTURE §8.10`). Pinning chunk XY to fix
|
||||
the params would have destroyed that invariant. The fingerprint fixes the cause and leaves the box
|
||||
reuse intact: what forces a rebuild now is a *real* params change, once per chunk in a transition
|
||||
band, which is the number of rebuilds this cache should always have done.
|
||||
|
||||
The three test call sites pass `VF_FP(P)` so the **oracle no longer shares the defect under test** —
|
||||
see the rewritten note at check 3 of `VoxelForgeOpStackTunnelTest.cpp`.
|
||||
|
||||
**Still open in this section:** the `OC_Chunk` / `BM_Chunk` / `FChunkBiomeCache` sites listed above.
|
||||
Those are the live-edit staleness half, not the determinism half, and they are untouched.
|
||||
|
||||
The operator-stack port does **not** inherit this: `FRoomGraphSource` folds a `FCrc::MemCrc32`
|
||||
fingerprint of the params (plus `LayoutVersion`) into its key, so differing params force a rebuild.
|
||||
|
||||
Reference in New Issue
Block a user