feat: height-space operator family — SurfaceWorld step 1, and C10 is solved

Maze and Slab now report BIT-IDENTICAL: FPSemantics = Precise, set for
cross-platform play, dissolved the ULP residue. Hypothesis 3 had the right
mechanism all along — under /fp:fast the compiler transforms by surrounding
context with no isolable axis, which is exactly why five one-variable experiments
all came back negative. Removing the permission removed the difference. Nobody
solved C10; C9 got fixed for an unrelated reason and C10 fell out of it.

SurfaceWorld step 1 forced an architectural decision. DECOMPOSITION section 5 notes
the height ops operate on Z values rather than density, then lists them as children
of FHeightfieldSource. Writing them made the consequence unavoidable: they do not
fit IVoxelDensityOp. No input Z (they produce one), XY-pure per column rather than
per voxel, and they write neither channel. Forcing them in would need a per-voxel
channel for a column property, or one opaque op — section 2.5's failure mode.

So height space gets its own contract: VoxelHeightOp.h (FVoxelHeightSample with
Height + Relief, IVoxelHeightOp, FVoxelHeightStack) and five ops. Relief is the
original's M — produced by the structural source, consumed by the terrace gate.
Section 0.1 found density needed a second channel; this found terrain needs a
second space.

The type system now forbids for free what AUDIT 6.3 warns about: a height stack
cannot hold Z-dependent data because there is no Z in the signature.

Deliberately staged — this touches nothing on the density path. If height space had
not decomposed cleanly, it shows up here for one test rather than after building the
adapter, the column cache integration and the dispatch on top.

The test runs twice; the second pass is load-bearing because the F20 terrain ops are
off by default, so a defaults-only run leaves all four modifiers untested. It also
brute-forces MaxDisplacement, since a false bound would later be a hole.

ComputeSurfaceTerrainZ moved private -> public for the test, same justification as
GetSlabDensity. Old declaration removed.

UNVERIFIED: not compiled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:06:50 +02:00
parent 25df4fceff
commit 921a9fb666
10 changed files with 990 additions and 13 deletions
+68
View File
@@ -1207,3 +1207,71 @@ headers. Everything else in the module compiled, so this is the whole tail: one
path, checked against `ARCHITECTURE §8.10`. Then back to the opstack: `SurfaceWorld` (§5).
---
## 2026-07-27 — ✅ C10 SOLVED (it WAS /fp:fast). SurfaceWorld opened: height space needed its own family.
**Maze and Slab are now BIT-IDENTICAL to their originals.** `FPSemantics = Precise`, set for
cross-platform play, dissolved the ULP residue.
**So hypothesis 3 had the right mechanism all along, and every experiment built on it was doomed by
construction.** Under `/fp:fast` the compiler reassociates and contracts *by surrounding context*,
with **no single isolable axis** — which is exactly why five carefully-designed one-variable tests
all came back negative while the difference stayed. There was no variable to find. Removing the
*permission* removed the difference.
**The lesson is not the one I expected.** Nobody solved C10; C9 got fixed for an unrelated
requirement and C10 fell out of it. Six more builds of bisecting source would have found nothing,
because the answer was a **build setting nobody was looking at**. Parking a question whose every
consequence is measured and benign was right on its own terms *and* right in hindsight — the
information was not obtainable along the path I was on, at any price. Recorded in `AUDIT §C10`.
**Immediate consequence:** the equivalence tests are now much sharper instruments. Any diff at all is
a real finding rather than noise to grade. The ULP machinery stays as a float-model regression alarm.
### SurfaceWorld, step 1 of 2 — and it forced an architectural decision
`§5` says the height ops *"operate on Z values in the column, not on density"* and then lists them as
children of `FHeightfieldSource`. Writing them made the consequence unavoidable: **they do not fit
`IVoxelDensityOp` at all.** No input Z (they produce one), XY-pure per column rather than per voxel,
and they write neither density nor SDF. The two ways to force them in were a per-voxel third channel
for what is a **column** property, or one opaque op — `§2.5`'s named failure mode.
**So height space got its own contract:** `VoxelHeightOp.h``FVoxelHeightSample` (`Height` +
`Relief`), `IVoxelHeightOp`, `FVoxelHeightStack`, and five ops in `VoxelHeightOpStack.cpp`.
`Relief` is the original's `M`: produced by the structural source, consumed by the terrace gate.
Same shape of lesson as `§0.1` — that one found density needed a second *channel*; this found
terrain needs a second *space*.
**A property the type system now gives for free:** a height stack **cannot** hold Z-dependent data,
because there is no Z in the signature to put there. `AUDIT §6.3` warns that Z-dependent data
smuggled into `FSurfaceColumn` silently corrupts every chunk in the vertical stack and that
`ValidateDeterminism` would not catch it. That hazard is now a type error instead of a convention.
**Deliberately staged.** This step touches **nothing** on the density path — no `FHeightfieldSource`
adapter, no `FSkyCapSource`, no `FOverhangShelfMod`, no column cache, no wiring. If height space had
not decomposed cleanly, that would show up here for the price of one test rather than after building
the adapter, the cache integration and the dispatch on top of it. Same method Phase 1 used on
density, applied to the question Phase 2 actually raised.
**The test runs twice, and the second pass is the one that matters:** the F20 terrain ops are **off
by default**, so a defaults-only run would exercise the structural source and leave all four
modifiers — i.e. everything new — untested. The second pass turns them all on. It also brute-forces
`MaxDisplacement` against observed movement, because a false bound would later be a hole.
`WaterLevelRelative` must be set for that pass or `FBeachHeightMod` early-outs and the fifth op is
never touched — a green test that measured nothing.
**Also:** `ComputeSurfaceTerrainZ` moved from private to public on `UVoxelGenerator` (same
justification as `GetSlabDensity` / `GetMazeDensity` — exposed for isolated tests). Its old private
declaration was removed; two declarations of one member would not compile.
**UNVERIFIED:** none of this is compiled. Likely error spots: the new `.cpp`/`.h` pair being picked
up; `FVoxelHeightStack` as a move-only local; the non-owning `const IVoxelHeightOp**` out-param in
`MakeStructuralHeightSource` and its `static_cast` back down in `MakeCliffHeightMod`; `FVector2D`
members being double in UE5 (cast at every use); and the `ComputeSurfaceTerrainZ` access move.
**Next single action:** build + run `SurfaceHeightEquivalence`. If green, step 2 — `FHeightfieldSource`
(the adapter that turns a column into density, preserving the T1.a cache), `FSkyCapSource`,
`FOverhangShelfMod` (the one genuinely 3D op here), then biome blending and the wiring. `§C1`
(bounded seed offsets) still open.
---