feat: TunnelNetwork stage A — the SDF spine, wrapping BuildChunkCache

The last archetype is ~1080 lines with 13 detail modifiers, a two-region
cache and a per-room op override. Porting it whole before anything can be
verified is ~600 unverified lines on top of ~200 — the pattern this
refactor has dodged six times. So: three stages.

Stage A = vertical scale, base rock, cave warp, room graph (+ pits and
chimneys), carve, worms, structural post. 6 ops. It is verifiable NOW
because every detail modifier is amplitude-gated and FStrateGenerationParams
already defaults them all to zero — zeroing SurfaceRoughness sends the
ORIGINAL down exactly the path stage A ported.

TunnelNetwork stays OFF in UsesOperatorStackForChunk until stage C.

The decision that matters: FRoomGraphSource CALLS BuildChunkCache and
EvaluateSDFCached rather than transcribing them. That is where §8.4's
two-region window-invariance discipline lives; a transcription would fork
it, and the fork would be "validated" by a test comparing it to the
original. Only the ~60 lines of glue are transcribed.

FRAME ops are retired. All three candidates are now ported and none needed
one: CaveWarp's scope is exactly one operator (pits/chimneys read unwarped
coords), VerticalScale is a one-line pure function, and the island warp was
already local. Not missing infrastructure — one idea seen three times from
a distance.

Also: check 3 was going to compare two interleaved param sets against the
original, which would have FAILED — the original's SDF cache key has no
params, so it serves B the rooms it built for A. Comparing there measures
its bug, not the port. Rewritten against each stack evaluated alone. The
same reasoning suggests a live production staleness across Gradient
transitions; filed in AUDIT §C2 as SUSPECTED with the check that would
confirm it, since it rests on a premise I have not verified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 18:28:06 +02:00
parent 96e75abe57
commit ef5bda3d8a
7 changed files with 963 additions and 9 deletions
+24
View File
@@ -182,6 +182,30 @@ Seed) **are** seed-guarded, and the strate-index memo **is** version-guarded (`S
`ChangeSeed` mostly survives; **live-edit is where this bites**. Symptom: "I tweaked the strate asset,
regenerated, and one patch kept the old shape."
#### ⚠️ SUSPECTED, NOT PROVEN, 2026-07-28 — the SDF cache may serve stale rooms *within* a strate
Found while porting TunnelNetwork, and stated as a suspicion on purpose: I have reasoned it, not
measured it.
`GetDensityWithParams`' SDF cache key is `(XY search box, StrateIndex, Seed)`. It contains **no
params and no chunk Z**. Meanwhile `GetGenerationParams` *blends* params between neighbouring strates
across a Gradient transition — so two chunks at different Z **inside the same strate** can carry
different `RoomSpacing`/`RoomDensity`/… while sharing an XY box, a strate index and a seed.
If that is right, a worker descending a transition band gets **no rebuild** and evaluates the lower
chunk against the upper chunk's room layout. Same family as `§C2` above and as the overhang
regression of 2026-07-27; the difference is that this one needs no live edit to trigger.
**What would confirm it:** call `GetGenerationParams` for two adjacent chunk Zs inside a
Gradient-transitioned strate and compare the room-placement fields. If they differ, the cache is
being reused across a real param change. **Do that before acting** — the whole thing rests on
"Gradient blending actually varies within a strate", which I have not verified.
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.
`VoxelForge.OpStack.TunnelNetworkSpineEquivalence` check 3 exercises exactly that — and deliberately
does *not* compare against the original there, because the original would fail it.
**Fix:** the getter already exists and is already used elsewhere —
```cpp
const uint32 LV = StrateManager->GetLayoutVersion();