docs(opstack): close the unattended run -- the post-8/8 batch and the reviewer pass

Four commits after 8 of 8, logged in OPSTACK-PROGRESS with the same shape as the first entry:
what each touches, what breaks first, and the numbers to read before the colours.

Records three things that only became visible while doing the work:

1. DECOMPOSITION 0.2 frames the worm as THE blocker for tile skipping. It is one of three. The
   subtraction also had no first term (nothing declared how solid the rock was), and the twelve
   detail modifiers were the bigger drag -- not through their amplitudes but because they declared
   a direction where they are provably Identity, being gated on the SDF the room source writes.

2. A safety property that was checked rather than assumed: no box query anywhere in the operator
   library touches per-worker memo state. That is what makes it safe for ClassifyTile to build and
   fold its own stack without clobbering the caches the density path depends on.

3. The reviewer pass over every ported operator found no transcription error. It did confirm the
   four things a future reader would otherwise have to re-derive: the operator order matches the
   original line for line, EffectiveZ is recomputed per op but bit-identically, the terrace's
   SDFBlendRadius is equal on both paths because no ApplyTo writes it, and LocalParams() is lazy so
   it reproduces the original's cost profile rather than merely its value.

Next action is now one thing rather than a queue: make FRoomGraphSource::EffectOverBox answer
spatially. Both of this batch's mechanism commits were built to receive it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 04:08:16 +02:00
parent 6a8390f11f
commit 46b507a261
+112
View File
@@ -2283,3 +2283,115 @@ Then, in the order agreed: settle `AUDIT §C2`'s suspected item (answerable by r
`GetGenerationParams` and `FStrateGenerationParams::Lerp` — no build needed), the worm amplitude cap
(`DECOMPOSITION §0.2`, its own commit, extends `BoxVerdictFold`), and then make `ClassifyTile` consume
`ClassifyBox`. Perf stays parked until Jahni unparks it.
---
## 2026-07-28 — after 8 of 8: the fold carries numbers, and `ClassifyTile` finally consumes `ClassifyBox`
Same unattended run, continued past the transition. Four more commits, still zero builds.
```
64d0e11 AUDIT §C2's suspected item settled — CONFIRMED, and it is the default config (docs only)
353d504 the box fold carries NUMBERS — amplitude bounds alongside EVoxelOpEffect (§0.2)
239c037 the twelve detail modifiers inherit the room source's box verdict
6a8390f ClassifyTile consumes ClassifyBox for cave archetypes — the T1.d prize
```
**Test filter: `VoxelForge`. Now 14 tests** — the new one is
`VoxelForge.OpStack.ClassifyTileSoundness`.
### `AUDIT §C2` — the suspicion was right, and it needed no build to settle
Four links, each read in the source rather than inferred: `Alpha` depends on chunk Z *inside* a
slot; `Lerp` blends every room-placement field; `Gradient` + `TransitionBlendChunks = 2` are the
**defaults**; and the strate index is per-slot, so the cache key cannot notice. A worker that builds
`(X,Y,Z1)` then `(X,Y,Z2)` in one strate evaluates the second chunk against the first chunk's rooms.
The part that matters more than the seam: **the result depends on which chunk that worker happened
to build first** — a window-invariance break, and in multiplayer two peers can generate different
geometry from the same seed. Not fixed here: the fix is a params CRC in the *original* path's SDF
cache key, a live-generation change that wants a build in front of it.
**Why nothing caught it:** the fixture sets `TransitionType = Hard` on every strate on purpose. The
one configuration the tests never build is the default one.
### The numeric fold, and the thing `§0.2` does not say out loud
`§0.2` frames the worm as *the* blocker: a fielded carve has no spatial bound, so it answers
`CarveOnly` on every box and kills `AllSolid` everywhere. True — and the amplitude fix is exactly as
described (`t ∈ [0,1]`, `Mask ∈ [0,1]` ⇒ at most `WormStrength`). But porting it surfaced a second
half that was missing:
1. **The subtraction had no first term.** Nothing declared *how solid* the rock was. Hence
`ForcedMarginOverBox`, which `FConstantFieldSource` answers exactly (`|Value|`).
2. **The twelve detail modifiers were the bigger drag, and not because of their amplitudes.** They
are all gated on the SDF the room source writes, so where the source proves no cave reaches the
box they are **Identity**, not "bounded" — yet each declared `Both`/`FillOnly`/`CarveOnly` and
killed the hypothesis just as hard. They already held the pointer since C1; they simply were not
asking. `VF_NoCaveOverBox` fixes twelve declarations in one place.
Backwards compatibility is structural, not promised: `FLT_MAX` and `0` are the defaults, so an op
that overrides nothing subtracts `FLT_MAX` from a margin of `0` and dies exactly as before.
### `ClassifyTile` — the prize, and the only change on this run that could make a hole
Where it used to `return Mixed` without a call for cave archetypes, it now builds the strate's stack
and folds `ClassifyBox`. **SurfaceWorld and bedrock gaps keep their hand-written proofs** — an
exact-lattice column test beats any box bound, so the stack has nothing to offer there.
**The load-bearing decision:** `GetDensityAt`'s build switch was extracted into
`VF_BuildOpStackForChunk` and both callers now use it. A second copy would be the worst bug
available in this file — a tile skipped on the verdict of a stack that is not the one producing its
density *is* a hole. A "keep these in sync" comment would not have been enough.
Six guards, all failing to `Mixed`. The one that matters is **params bit-identical across every
chunk coord the box touches**, and it exists *because of* the `§C2` finding two commits earlier: a
tile straddling a blend band genuinely cannot be represented by one stack. The others: opt-in true on
every chunk (not just the triggering one), one cave slot per tile, no mixed cave/surface/gap tile, a
27-chunk-coord cap, and the disturbances folded by hand since `§10.2` leaves them outside the stack.
**A safety property worth recording, because it was checked rather than assumed:** no box query in
the whole operator library touches per-worker memo state — `EffectOverBox` and `ClassifyBox` are
pure in every op. So the classifier building and folding its own stack cannot clobber the caches the
density path depends on.
**What it buys:** Maze, FlatPlain/CrystalChamber, VerticalShafts and FloatingIslands can prove tiles
in production. TunnelNetwork and Underwater still prove nothing — their chain dies at
`FRoomGraphSource`, which answers `Both`. Making it answer spatially means building the SDF cache for
the queried box, which is *now worth it*: a skipped tile saves 30k+ density evaluations, and both
the amplitude fold and the modifiers' Identity inheritance are already in place to receive it.
**That is the single highest-value thing left.**
### Fixture: a cross-test hazard that was hidden by an accident
`FTestWorld::Build` gains `bUseOperatorStack` (default false), and every test world now gets a
**process-unique `LayoutVersion`**. `PassagesVersion` is per-instance and starts at 0, so two
`FTestWorld`s both reported `1` — and `GetDensityAt`'s per-chunk caches are keyed on
`(ChunkCoord, LayoutVersion)`. One world could be served the previous world's params *and its
`CP_UseOpStack` flag*. Invisible for as long as every world agreed the flag was false; the first
world that ticks it removes the coincidence, in both directions.
### The reviewer pass, done as the last step
Re-read every ported operator against the original block, as a reviewer rather than the author.
**No transcription error found.** What the pass did confirm, and what is worth knowing:
- operator order in `BuildTunnelNetworkStack` matches the original **line for line** (roughness →
terrace → lines → ribs → overhang → cliff → scallop → arch → columns → domes → pinch → floor bias);
- `EffectiveZ` is recomputed per operator instead of once — same expression, same operands, so
bit-identical;
- the terrace's `SDFBlendRadius` comes from strate params in the port and from the *shadowed* copy in
the original, which are equal because no `ApplyTo` writes that field;
- `LocalParams()` is materialised lazily by the first modifier that asks, which reproduces the
original's cost profile (one struct copy per voxel **inside the gate**, none in deep rock).
### Next single action
Unchanged: build, run the `VoxelForge` filter, read the coverage numbers before the colours. Then
the two numbers new to this batch — `VoxelForge.OpStack.ClassifyTileSoundness`'s **count of tiles
actually brute-forced** (zero would mean it proved nothing about the new wiring, and it errors on
that), and the verdict counts compared against `VoxelForge.Determinism.ClassifyTileSoundness`, whose
difference *is* the T1.d gain.
After that: make `FRoomGraphSource::EffectOverBox` answer spatially. Everything else is now waiting
on it, and the two commits above were built to receive it.