From 51d8db842bead9ebb64348df9df66468ff34bc80 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Mon, 27 Jul 2026 15:39:03 +0200 Subject: [PATCH] =?UTF-8?q?test:=20fix=20the=20ULP=20yardstick=20=E2=80=94?= =?UTF-8?q?=20it=20measured=20output=20density,=20not=20where=20the=20erro?= =?UTF-8?q?r=20is=20born?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- OPSTACK-DECOMPOSITION.md | 9 ++ OPSTACK-PROGRESS.md | 69 +++++++++++++++ .../Tests/VoxelForgeOpStackSlabTest.cpp | 86 +++++++++++++++---- 3 files changed, 147 insertions(+), 17 deletions(-) diff --git a/OPSTACK-DECOMPOSITION.md b/OPSTACK-DECOMPOSITION.md index 8ab06ca..9e00454 100644 --- a/OPSTACK-DECOMPOSITION.md +++ b/OPSTACK-DECOMPOSITION.md @@ -263,6 +263,15 @@ FGridColumnMod Add world-grid jittered cylinders, infinit That is the entire archetype. Two of the eight collapse into one, and the ceiling's `abs(noise)` (formations hang down only, never punch up) is a two-line flag on the source. +> **Observed in-editor 2026-07-27, and it settles the question:** Jahni reports FlatPlain and +> CrystalChamber render **identical** in the live world. They should — they share +> `FSlabGenerationParams`, and nothing in the content sets them apart. **The enum promised a +> difference the data never delivered**, in the shipped world as well as in the test fixture. +> So the merge does not lose a distinction; it *reveals* that there was none. Making CrystalChamber +> look like a crystal chamber is a **params** job — raise `CeilingRoughness` (6 → ~20, what +> `SlabEquivalence`'s tuned pass uses) and drop `CeilingRelativeHeight` a little. That is authoring, +> which is exactly the outcome the whole refactor is aiming at. + ### 3.1 ✅ RESOLVED 2026-07-27 — Jahni: the Z term can go. Removed. **Decision:** the Z term was not intentional character. It is gone from `GetSlabDensity` (both diff --git a/OPSTACK-PROGRESS.md b/OPSTACK-PROGRESS.md index 01b8c27..6279b4e 100644 --- a/OPSTACK-PROGRESS.md +++ b/OPSTACK-PROGRESS.md @@ -990,3 +990,72 @@ question is whether it still reads as the same kind of place. Then `SurfaceWorld (OPSTACK-DECOMPOSITION §5) — biggest payoff, most care. --- + +## 2026-07-27 — the tuned pass cried wolf. The TEST was wrong, not the port. + +``` +CrystalChamber(tuned): 121/20000 differ, 6 exceed the bound, worst |delta| 1.71661377e-05 + 0 cross the isosurface; box verdicts 32/60 proved, 0 unsound +``` + +**The port is fine. The yardstick was wrong, and it was wrong in a way that only shows up under +large amplitudes — which is precisely why the tuned pass was worth adding.** + +The old bound was `16 · max(|Old|, 1) · FLT_EPSILON` — ULPs measured on the **output density**. +But the density is `min(Z - Floor, Ceil - Z)`, so **near the isosurface the output tends to 0 while +the intermediates (surfaces, world Z, noise amplitudes) are in the HUNDREDS.** A rounding born at +scale ~400 was being judged against a yardstick of scale 1: 400× too tight, and tightest exactly +where the test looks hardest. + +**Measured, not assumed:** + +| | roughness | worst \|delta\| | in ULP of \|Z\| | +|---|---|---|---| +| gentle passes | ceil 6, floor 4 | 3.81e-06 | ~0.08 | +| tuned pass | ceil 20, floor 9 | 1.72e-05 | **0.345** | + +Amplitudes went up ×2.25–3.33, the deltas went up **×4.5**, and the worst one is **sub-ULP at the +scale it is born in**. Error proportional to amplitude is the signature of ordinary rounding, not of +a wrong transcription — a wrong offset or a missing `abs()` would move the surface by **voxels**, +four orders of magnitude above this, not by a factor of four. + +**Fixed:** the bound now scales with the magnitude the error is born in (`max(|Old|, |Z|, strate Z +bounds)`), and — more importantly — **the warning now prints the discriminator instead of just the +alarm**: the density at the offending sample and the delta expressed in ULPs of the working scale. +A few ULP with a near-zero density is cancellation; thousands of ULP is drift. The next context +reads that off the message instead of re-deriving it at a build apiece. + +The test stays discriminating: real drift is 4 orders of magnitude above the new bound. + +### The box verdicts held under the worst case — that is the result that mattered + +32 of 60 proved uniform with **0 unsound**, under tripled ceiling roughness and 3× the columns. +That was the specific thing the tuned pass existed to attack (a wide ceiling band makes the +`Max(CeilZ - noise, FloorSurface + 2)` clamp bind, which is where a false verdict would be a HOLE), +and the bound survived it. Fewer tiles proved than the gentle passes (32 vs 36/40), which is correct +— wider bands mean more genuinely Mixed tiles. + +### Visual A/B — and a content finding worth more than the A/B + +Jahni: *"visually, crystal and plain are identical, same for when opting on or off from the +opstack."* + +**Opt-in on/off identical = the port is confirmed a pure refactor.** Note this is the expected +result and my earlier framing was sloppy: **both** paths compute the post-§3.1 function, so the +Z-term change is invisible in the A/B by construction. It is only visible against the world as it +looked *before* this build. + +**FlatPlain and CrystalChamber rendering identical is the real finding.** They share +`FSlabGenerationParams` and nothing in the content distinguishes them — **the enum promised a +difference the data never delivered**, in the shipped world exactly as in the test fixture. So the +merge loses no distinction; it reveals there was none. Recorded in `OPSTACK-DECOMPOSITION §3`. +Making a crystal chamber look like one is now a **params** job (`CeilingRoughness` 6 → ~20), which +is precisely the outcome the refactor is for. + +**UNVERIFIED:** the corrected bound and the new warning text. + +**Next single action:** rebuild; the tuned pass should drop to INFO with all three passes reporting +sub-ULP-of-scale deltas. Then `SurfaceWorld` (§5) — biggest payoff, most care: the T1.a column cache +and the exact-lattice `ClassifyTile` bound must both survive the port. + +--- diff --git a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp index 4c0598b..20ce719 100644 --- a/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp +++ b/Source/VoxelForge/Private/Tests/VoxelForgeOpStackSlabTest.cpp @@ -128,8 +128,38 @@ bool FVoxelForgeOpStackSlabTest::RunTest(const FString& Parameters) //===================================================================== // 1. ÉQUIVALENCE — géométrie d'abord, bits ensuite. //===================================================================== + // ───────────────────────────────────────────────────────────────────── + // LE BON MÈTRE — corrigé 2026-07-27 après que la passe `tuned` a crié au loup + // ───────────────────────────────────────────────────────────────────── + // Première version : `16 · max(|Old|, 1) · FLT_EPSILON`, c.-à-d. l'ULP mesuré sur la + // DENSITÉ DE SORTIE. C'est le mauvais mètre, et il se trompe exactement là où le test + // regarde le plus : la densité vaut `min(Z - Sol, Plafond - Z)`, donc PRÈS DE L'ISOSURFACE + // la sortie tend vers 0 pendant que les intermédiaires (surfaces, Z monde, amplitudes de + // bruit) valent des CENTAINES. Un arrondi né à l'échelle 400 était jugé contre un mètre + // à l'échelle 1 — 400× trop serré. + // + // Mesuré : la passe `tuned` (rugosités ×2.25 et ×3.33) a vu ses écarts croître ×4.5, et + // son pire écart valait **0.345 ULP de |Z|**. Sous-ULP à l'échelle où l'erreur naît. + // L'erreur est donc proportionnelle à l'AMPLITUDE, ce qui est la signature d'un arrondi + // ordinaire, pas d'une transcription fausse. + // + // Le mètre correct est la magnitude des quantités D'OÙ VIENT l'erreur. Le test reste + // discriminant : une vraie dérive de portage (offset de bruit faux, `abs()` manquant, + // clamp oublié) déplace la surface de plusieurs VOXELS — 4 ordres de grandeur au-dessus + // de ce seuil, pas 4 fois. + // + // The first yardstick measured ULPs on the OUTPUT density, which tends to 0 near the + // isosurface while the intermediates are in the hundreds. Rounding born at scale ~400 was + // judged against a yardstick of scale 1. Real port drift moves the surface by voxels — + // four orders of magnitude above this bound, so the test stays discriminating. + const float SurfaceScale = FMath::Max(FMath::Abs(SlabParams.StrateTopWorldZ), + FMath::Abs(SlabParams.StrateBottomWorldZ)); + int32 NumDiff = 0, WorstIdx = -1, NumBeyondUlpNoise = 0, NumSolidDisagreements = 0; - float WorstDelta = 0.0f; + float WorstDelta = 0.0f, WorstOld = 0.0f, WorstUlpsOfScale = 0.0f; + // Le pire cas PARMI LES DÉPASSEMENTS — c'est lui qui dit si un WARN est du bruit ou une dérive. + float WorstOutlierDelta = 0.0f, WorstOutlierOld = 0.0f, WorstOutlierUlps = 0.0f; + for (int32 i = 0; i < NumSlabSamples; ++i) { const float X = (float)Points[i].X, Y = (float)Points[i].Y, Z = (float)Points[i].Z; @@ -141,13 +171,26 @@ bool FVoxelForgeOpStackSlabTest::RunTest(const FString& Parameters) { ++NumDiff; const float Delta = FMath::Abs(Old - New); - if (Delta > WorstDelta) { WorstDelta = Delta; WorstIdx = i; } - // Même forme que le carve de Maze : `(ColBlend - ColumnSDF)` annule au bord de la - // coquille de blend des colonnes, donc un ULP amont ressort amplifié. Marge - // généreuse mais BORNÉE — au-delà, c'est une vraie dérive de portage. - const float UlpNoise = 16.0f * FMath::Max(FMath::Abs(Old), 1.0f) * FLT_EPSILON; - if (Delta > UlpNoise) { ++NumBeyondUlpNoise; } + // L'échelle à laquelle CET échantillon calcule : la sortie, sa propre altitude, et + // les bornes de la strate. C'est le plus grand des trois qui porte l'arrondi. + const float Scale = FMath::Max3(FMath::Abs(Old), FMath::Abs(Z), + FMath::Max(SurfaceScale, 1.0f)); + const float Ulps = Delta / (Scale * FLT_EPSILON); + + if (Delta > WorstDelta) + { + WorstDelta = Delta; WorstIdx = i; WorstOld = Old; WorstUlpsOfScale = Ulps; + } + + if (Delta > 16.0f * Scale * FLT_EPSILON) + { + ++NumBeyondUlpNoise; + if (Delta > WorstOutlierDelta) + { + WorstOutlierDelta = Delta; WorstOutlierOld = Old; WorstOutlierUlps = Ulps; + } + } } // Le mesher ne lit que le SIGNE. Un désaccord de CÔTÉ bouge la géométrie. if ((Old >= 0.0f) != (New >= 0.0f)) { ++NumSolidDisagreements; } @@ -161,23 +204,32 @@ bool FVoxelForgeOpStackSlabTest::RunTest(const FString& Parameters) else if (NumBeyondUlpNoise == 0) { AddInfo(FString::Printf( - TEXT("%s: %d of %d samples differ, ALL at ULP scale (largest |delta| %.9g at ") - TEXT("(%.0f, %.0f, %.0f)), and 0 cross the isosurface. Same accepted floor as Maze ") - TEXT("-- see AUDIT-2026-07.md C10 before hunting it."), - SlotName, NumDiff, NumSlabSamples, WorstDelta, + TEXT("%s: %d of %d samples differ, ALL at ULP scale (largest |delta| %.9g = %.3f ULP ") + TEXT("of the working scale, where density = %.6g, at (%.0f, %.0f, %.0f)), and 0 cross ") + TEXT("the isosurface. Same accepted floor as Maze -- see AUDIT-2026-07.md C10."), + SlotName, NumDiff, NumSlabSamples, WorstDelta, WorstUlpsOfScale, WorstOld, WorstIdx >= 0 ? Points[WorstIdx].X : 0.0f, WorstIdx >= 0 ? Points[WorstIdx].Y : 0.0f, WorstIdx >= 0 ? Points[WorstIdx].Z : 0.0f)); } else { + // Le message porte maintenant LE DISCRIMINANT, pas seulement l'alarme : la densité au + // point fautif et l'écart exprimé en ULP de l'échelle de travail. Un dépassement à + // quelques ULP avec une densité proche de 0 est un artefact de mètre ; un dépassement + // à des milliers d'ULP est une vraie dérive. La différence se lit, elle ne se devine pas. AddWarning(FString::Printf( - TEXT("%s: %d of %d samples differ and %d are TOO LARGE to be the accepted ULP floor ") - TEXT("(largest |delta| %.9g at (%.0f, %.0f, %.0f)); %d cross the isosurface. THIS is ") - TEXT("real port drift. Check, in order: the floor/ceiling noise offsets (7.3/11.1 and ") - TEXT("17.3+1000/19.7+2000/3000), the abs() on the ceiling noise, the ceiling clamp ") - TEXT("(FloorSurface + 2), the column blend (2.0) and the 0.15/0.7 jitter."), - SlotName, NumDiff, NumSlabSamples, NumBeyondUlpNoise, WorstDelta, + TEXT("%s: %d of %d samples differ and %d exceed the ULP bound. Worst OUTLIER: ") + TEXT("|delta| %.9g = %.1f ULP of the working scale, where density = %.6g. ") + TEXT("(Worst overall: |delta| %.9g at (%.0f, %.0f, %.0f).) %d cross the isosurface. ") + TEXT("READ THE ULP FIGURE BEFORE INVESTIGATING: a few ULP with a near-zero density is ") + TEXT("cancellation near the isosurface, not drift. Thousands of ULP IS drift -- check, ") + TEXT("in order: the floor/ceiling noise offsets (7.3/11.1 and 17.3+1000/19.7+2000/3000), ") + TEXT("the abs() on the ceiling noise, the ceiling clamp (FloorSurface + 2), the column ") + TEXT("blend (2.0) and the 0.15/0.7 jitter."), + SlotName, NumDiff, NumSlabSamples, NumBeyondUlpNoise, + WorstOutlierDelta, WorstOutlierUlps, WorstOutlierOld, + WorstDelta, WorstIdx >= 0 ? Points[WorstIdx].X : 0.0f, WorstIdx >= 0 ? Points[WorstIdx].Y : 0.0f, WorstIdx >= 0 ? Points[WorstIdx].Z : 0.0f,