588f9e0294feeb9e5f74bbd6b417ab699ea700d9
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ef5bda3d8a |
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> |
||
|
|
96e75abe57 |
feat: port FloatingIslands — the stack that runs backwards
6 of 8 archetypes ported. This one starts from VOID and FILLS where the other four start from ROCK and CARVE, which is what it was worth doing: neither end of the pile needed a new operator, only the opposite sign. FConstantRockSource -> FConstantFieldSource(+/-Base) AllSolid <-> AllAir FSdfCarveOp -> FSdfConvertOp(Sign = +/-1) carve <-> fill FSdfRoughnessMod 4th archetype, unchanged Only the island blob source is new. Multiplying by +/-1 is exact in IEEE-754, so the three already-green ports are bit-for-bit untouched. ClassifyBox can return AllAir for the first time in the plugin, and an island strate is by construction mostly empty — the test counts AllSolid and AllAir separately so an aggregate cannot hide whether that fired. Two bounds that would have been holes if assumed rather than derived: the island bound is one-sided (a hairline thread of matter hangs below each island down its axis, so only the TOP may reject), and the domain warp displaces X and Y independently, so the pad needs WarpAmp*sqrt(2). Also: AUDIT C1 was NOT closed. The 2026-07-27 sweep matched `SeedF * K` and this archetype's warp spells it `(float)S * K`, so one site survived — at seed 2e9 the warp flattens and every island snaps back to a perfect circle. Fixed in both paths in one pass so the equivalence test stays a valid oracle. Expect island silhouettes to change at large seeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
921a9fb666 |
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> |
||
|
|
51d8db842b |
test: fix the ULP yardstick — it measured output density, not where the error is born
The tuned pass warned: 121/20000 differ, 6 over the bound, worst 1.72e-05, still 0 isosurface crossings. The port is fine; the bound was wrong. It was 16 * max(|Old|, 1) * FLT_EPSILON — ULPs on the OUTPUT density. But density is min(Z - Floor, Ceil - Z), so near the isosurface the output tends to 0 while the intermediates are in the hundreds. Rounding born at scale ~400 judged against a yardstick of scale 1: 400x too tight, and tightest exactly where the test looks hardest. Large amplitudes are what expose it, which is why the tuned pass earned its place immediately. Measured rather than assumed: amplitudes rose x2.25-3.33 and the deltas rose x4.5, with the worst delta at 0.345 ULP of |Z| — sub-ULP at the scale it is born in. Error proportional to amplitude is ordinary rounding. A wrong noise offset or a missing abs() would move the surface by voxels, four orders of magnitude above this. The bound now scales with max(|Old|, |Z|, strate Z bounds), and the warning prints the discriminator instead of just the alarm: the density at the offending sample and the delta in ULPs of the working scale. A few ULP at near-zero density is cancellation; thousands is drift. That distinction is now readable rather than re-derivable at a build apiece. The box verdicts held under the worst case: 32/60 proved uniform, 0 unsound, under tripled ceiling roughness and 3x the columns — exactly the case that stresses the Max(CeilZ - noise, FloorSurface + 2) clamp. Also recorded in DECOMPOSITION section 3: FlatPlain and CrystalChamber render identical in the live world because nothing in the content distinguishes them. The merge loses no distinction; it reveals there was none. UNVERIFIED: the corrected bound. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
644339def5 |
feat: Phase 2 first port — FlatPlain + CrystalChamber collapse into one op
Jahni closed OPSTACK-DECOMPOSITION 3.1: the slab noise Z term was not intentional character. Phase 1 also closed — the visual A/B on Maze passed. Two changes, deliberately together, kept attributable by the test: 1. Design: GetSlabDensity's floor and ceiling noise lose their Z terms. A floor height no longer depends on the altitude you sample it from. The ceiling keeps its + 3000.0f, which is a decorrelation offset, not a Z term. The world re-tunes once — a different slice of the noise field, not a worse one. 2. Refactor: the now-XY-pure function ports to FSlabVoidSource + FGridColumnMod plus the three structural ops. BuildSlabStack has NO branch on archetype because GetSlabDensity never had one — CrystalChamber is FlatPlain with a bigger CeilingRoughness. 8 archetypes -> 7. SlabEquivalence compares against the reference AS IT IS NOW and runs the whole battery on both slots, so green means the port is a pure refactor and any visual delta is attributable to the Z-term removal alone. The attribution comes from the test, not from splitting it across two builds. The payoff 3.1 was actually about: FSlabVoidSource::ClassifyBox is exact and needs no sampling. FBM is contractually [-1,1], so both surfaces live in Z bands with known bounds — a tile below the floor band is provably solid, a tile between the bands provably air. ClassifyTile proves zero tiles for these archetypes today. FGridColumnMod answers Identity when no column reaches the box, which is what lets the source's AllAir verdict survive the fold. UNVERIFIED: not compiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c188ee8262 |
docs: OPSTACK-DECOMPOSITION.md — the per-archetype op breakdown (Q1 + Q2)
All 8 archetypes read line by line and broken into field source / combiners / detail modifiers / structural post, with every FStrateGenerationParams field traced to the op that will own it. Three findings that change the sequencing: - The op contract needs an SDF channel alongside density. Rooms, pits and chimneys are SmoothMin'd in SDF space before a single carve, and three of the four SDF archetypes add roughness to the SDF rather than to density. A single-channel Eval can only overwrite, which is also why cross-source SmoothUnion -- 'a maze inside a mountain that looks like it belongs' -- is not expressible without it. Recommended before the Maze port; not applied, it is Jahni's call. - Worm tunnels are why TunnelNetwork can never skip a tile. A fielded 3D-noise carve with no bounds forces CarveOnly everywhere, killing AllSolid for the most-used archetype. But its amplitude is trivially bounded by WormStrength, so a scalar cap recovers deep-rock skipping -- suggests one numeric bound belongs in Phase 2, not Phase 3 as the plan has it. - Disturbances already carry lattice bounds that ClassifyTile discards (it only tests ChasmDensity > 0 strate-wide). Making them ops with real Identity tests is a tile-skipping win for SurfaceWorld available independently of everything else. Q2 param audit: every field claimed except WaterLevelRelative, which is a render/water property misfiled in the density struct and Lerp'd across strate boundaries. Also flags three op names that mean different things in the cave and surface structs and will collide the moment ops become assets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |