test: localised to FSdfCarveOp — now testing inlining context, the right variable

The SDF is bit-identical on 126 of 126 mismatches (0xBFE1C886 both sides), so the
lattice, hashes, edge set and VoxelSDF::Capsule are exactly right. The entire
difference is in FSdfCarveOp, whose expression is character-identical to the
original and whose inputs are bit-identical.

Identical inputs plus identical expression plus different output means the
arithmetic is being EVALUATED differently. SmoothStep01 is x*x*(3.0f - 2.0f*x),
and 3.0f - 2.0f*x is exactly the shape MSVC fuses into an FMA: one rounding
instead of two, ~1 ULP.

Why the three-way missed this, recorded because it is a reasoning error rather
than a coding one: A and C are both straight-line inlined code, while B goes
through a virtual IVoxelDensityOp call, so FSdfCarveOp::Eval is compiled
out-of-line and can get a different contraction decision. The three-way tested
whether the TRANSLATION UNIT boundary changes the result -- it does not -- but the
real variable is the OPTIMISATION CONTEXT. I built a clean experiment for the
wrong variable and then believed its answer. Hypothesis 3 was right about the
mechanism and wrong about the test.

The new experiment isolates exactly that: the same carve expression, same TU,
once FORCEINLINE and once FORCENOINLINE.

  differ    -> contraction confirmed, the port has NO bug, accept the ULP floor
  identical -> contraction is not it, and FSdfCarveOp has a real logic bug that
               has survived four readings

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 14:48:56 +02:00
parent 210602586e
commit 34f8ca7953
2 changed files with 110 additions and 0 deletions
+42
View File
@@ -589,3 +589,45 @@ immediately.
**Next single action:** rebuild, read `FIRST B-vs-C MISMATCH`. It names the file to open.
---
## 2026-07-27 — LOCALISED to the carve. Testing the right variable this time.
**The diagnostic pinned it exactly:**
```
SDF stack -1.76393199 [0xBFE1C886] verbatim -1.76393199 [0xBFE1C886] IDENTICAL
MC stack 7.83939362 [0x40FADC50] verbatim 7.83939266 [0x40FADC4E] 2 ULP apart
across all mismatches: SDF differs 0, SDF identical but density differs 126
```
So the lattice, the hashes, the edge set and `VoxelSDF::Capsule` are all **exactly right** — 126 of
126. The entire difference is in `FSdfCarveOp`, whose expression is character-identical to the
original and whose inputs (`Sdf`, `Blend` 2.0, `BaseDensity` 8) are bit-identical.
**Identical inputs + identical expression + different output ⇒ the arithmetic is being *evaluated*
differently.** And `SmoothStep01` is `x * x * (3.0f - 2.0f * x)``3.0f - 2.0f*x` is exactly the
shape MSVC fuses into an FMA, which is one rounding instead of two: **~1 ULP.**
**Why the three-way missed it — worth recording, because it is a reasoning error, not a coding one.**
`A` (GetMazeDensity) and `C` (the verbatim copy) are both straight-line, inlined code. `B` goes
through a **virtual** `IVoxelDensityOp` call, so `FSdfCarveOp::Eval` is compiled out-of-line and can
get a different contraction decision. The three-way tested *"does the translation-unit boundary
change the result?"* — it does not — but the real variable is *"does the optimisation context change
the result?"*. **I designed a clean experiment for the wrong variable, and then believed its answer.**
Hypothesis 3 was not wrong about the mechanism (`/fp:fast` contraction); it was wrong about the test.
**The experiment now added** isolates exactly that variable: the same carve expression, in the same
translation unit, once `FORCEINLINE` and once `FORCENOINLINE`.
- **inlined != FORCENOINLINE** ⇒ FP contraction confirmed. **The port has no bug** — the operator
stack is arithmetically correct and the residue is unavoidable wherever an op is a virtual call.
Then: correct the docs with the *real* reason, accept the ULP floor, move on to step 3.
- **inlined == FORCENOINLINE** ⇒ contraction is not it, and there is a real logic bug in
`FSdfCarveOp` that has now survived four readings.
**UNVERIFIED:** the experiment.
**Next single action:** rebuild, read `INLINING EXPERIMENT`. Either way the answer is final — the
inputs are proven bit-identical, so only the evaluation can differ.
---
@@ -132,6 +132,43 @@ namespace
return -Density; // convention MC
}
/**
* L'EXPÉRIENCE DÉCISIVE sur le carve — même unité de compilation, même source, SEUL le contexte
* d'inlining change.
*
* Le diagnostic a montré : SDF bit-identique, densité différente de 1 ULP, sur 126/126 des
* écarts. Or `SmoothStep01` est `x * x * (3.0f - 2.0f * x)`, et `3.0f - 2.0f * x` est exactement
* la forme qu'un compilateur fusionne en FMA — un seul arrondi au lieu de deux, soit ~1 ULP.
*
* A (GetMazeDensity) et C (la copie verbatim) sont tous deux du code DROIT, inliné. B passe par
* un appel VIRTUEL sur `IVoxelDensityOp`, donc `FSdfCarveOp::Eval` est compilé hors-ligne, dans
* un contexte d'optimisation différent. Le test à trois voies a donc répondu à « la frontière
* d'unité de compilation change-t-elle le résultat ? » (non) alors que la vraie variable est
* « le contexte d'optimisation change-t-il le résultat ? ».
*
* Ici on isole EXACTEMENT cette variable : deux fois la même expression, dans la même unité,
* l'une inlinable et l'autre FORCENOINLINE. Si elles diffèrent, la cause est établie et le
* portage n'a aucun bug.
*
* Same TU, same source, only the inlining context differs. If these two disagree, the cause is
* established and there is no bug in the port.
*/
FORCENOINLINE float CarveNoInline(float Sdf, float Blend, float Base, float InDensity)
{
if (Sdf >= Blend) { return InDensity; }
float Carve = FMath::Clamp((Blend - Sdf) / (Blend * 2.0f), 0.0f, 1.0f);
Carve = SmoothStep01(Carve);
return InDensity - Carve * Base * 2.0f;
}
FORCEINLINE float CarveInlined(float Sdf, float Blend, float Base, float InDensity)
{
if (Sdf >= Blend) { return InDensity; }
float Carve = FMath::Clamp((Blend - Sdf) / (Blend * 2.0f), 0.0f, 1.0f);
Carve = SmoothStep01(Carve);
return InDensity - Carve * Base * 2.0f;
}
/** Les params Maze de la strate Maze de la fixture, bornes Z de runtime comprises. */
bool ResolveMazeParams(const VoxelForgeTest::FTestWorld& World, FMazeGenerationParams& Out,
int32& OutTopVoxelZ, int32& OutBottomVoxelZ)
@@ -384,6 +421,37 @@ bool FVoxelForgeOpStackMazeTest::RunTest(const FString& Parameters)
BMC, Bits(BMC), C, Bits(C),
VerbEdges, Core.CellSize, Core.CorridorRadius, Core.BaseDensity,
SdfDiffers, SdfSame_DensityDiffers));
// ── L'expérience décisive : inline vs FORCENOINLINE, même unité, même source. ──
int32 InlineVsNoInline = 0, NoInlineMatchesStack = 0, InlineMatchesVerbatim = 0;
for (int32 i = 0; i < N; ++i)
{
const float PX = (float)Points[i].X, PY = (float)Points[i].Y, PZ = (float)Points[i].Z;
const FVoxelOpSample S = CoreStack.EvalSample(PX, PY, PZ);
const float Inl = -CarveInlined(S.Sdf, 2.0f, Core.BaseDensity, Core.BaseDensity);
const float Noi = -CarveNoInline(S.Sdf, 2.0f, Core.BaseDensity, Core.BaseDensity);
const float Ver = MazeCoreVerbatim(PX, PY, PZ, Core, World.Settings->Seed);
const float Stk = -S.Density;
if (!BitEqual(Inl, Noi)) { ++InlineVsNoInline; }
if (BitEqual(Noi, Stk)) { ++NoInlineMatchesStack; }
if (BitEqual(Inl, Ver)) { ++InlineMatchesVerbatim; }
}
AddInfo(FString::Printf(
TEXT("INLINING EXPERIMENT (%d samples, same TU, same source, only inlining differs):\n")
TEXT(" inlined carve != FORCENOINLINE carve : %d\n")
TEXT(" FORCENOINLINE == operator stack : %d / %d\n")
TEXT(" inlined == verbatim : %d / %d\n")
TEXT(" IF the first number is nonzero, the cause is FLOATING-POINT CONTRACTION under\n")
TEXT(" /fp:fast, not a logic error: SmoothStep01 is x*x*(3-2x), and 3.0f - 2.0f*x is\n")
TEXT(" exactly the shape MSVC fuses into an FMA (one rounding instead of two, ~1 ULP).\n")
TEXT(" A and C are straight-line inlined code; the operator stack goes through a\n")
TEXT(" VIRTUAL call, so FSdfCarveOp::Eval is compiled out-of-line and gets a different\n")
TEXT(" contraction decision. The earlier three-way tested the TU boundary, which is the\n")
TEXT(" WRONG VARIABLE -- this tests the right one.\n")
TEXT(" IF the first number is zero, contraction is NOT it and the operator stack has a\n")
TEXT(" real logic bug that survives every reading so far."),
N, InlineVsNoInline, NoInlineMatchesStack, N, InlineMatchesVerbatim, N));
}
MutableGen->OriginSpineRadius = SavedSpine;