test: the variable is compile-time-constant vs runtime Blend — one line to confirm
The inlining experiment partitioned everything, just not along the axis it was framed on: inlined != FORCENOINLINE : 0 inlining is not the variable FORCENOINLINE == op stack : 5000/5000 test-TU carve == other-TU op, always inlined == verbatim : 4874/5000 126 differ, in the SAME TU The test-TU carve matches the operator stack across a TU boundary perfectly and disagrees with the verbatim inside its own TU, so neither TU nor inlining is it. Sorting the five implementations by the one remaining difference splits them exactly: A (GetMazeDensity) and C (verbatim) hold Blend as a compile-time constant; B (FSdfCarveOp, a member) and both parameter versions hold it as runtime data. A == C, B == Inl == Noi, and the groups differ. Every observation today fits that and nothing else does. Mechanism: under /fp:fast, folding Blend * 2.0f to the literal 4.0f enables a contraction in SmoothStep01's 3.0f - 2.0f*x -- one rounding instead of two -- that the runtime form cannot get. This matters beyond the bug: an op's parameters are DATA by design, which is the entire point of the refactor, so they can never be compile-time literals again. The ULP difference is therefore inherent and permanent for every archetype port, and no care in transcription will remove it. That is the real reason bit-identity is unachievable here -- the earlier /fp:fast note named the right compiler flag for the wrong reason. CarveConstBlend added: identical to CarveInlined except Blend is a compile-time constant. Predicted to match the verbatim 5000/5000 and differ from the runtime form on exactly 126. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -631,3 +631,52 @@ translation unit, once `FORCEINLINE` and once `FORCENOINLINE`.
|
|||||||
inputs are proven bit-identical, so only the evaluation can differ.
|
inputs are proven bit-identical, so only the evaluation can differ.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-27 — the variable is COMPILE-TIME CONSTANT vs RUNTIME DATA. One line left to confirm.
|
||||||
|
|
||||||
|
**The inlining experiment partitioned the measurements perfectly, just not the way it was framed:**
|
||||||
|
|
||||||
|
```
|
||||||
|
inlined carve != FORCENOINLINE carve : 0 <-- inlining is NOT the variable
|
||||||
|
FORCENOINLINE == operator stack : 5000/5000 <-- test-TU carve == other-TU op, ALWAYS
|
||||||
|
inlined == verbatim : 4874/5000 <-- 126 differ, IN THE SAME TU
|
||||||
|
```
|
||||||
|
|
||||||
|
The test-TU carve matches the operator stack **in a different TU** perfectly, yet disagrees with the
|
||||||
|
verbatim **in its own TU**. So neither the TU boundary nor inlining is the variable. Sorting the five
|
||||||
|
implementations by the one remaining difference:
|
||||||
|
|
||||||
|
| Implementation | `Blend` is | Group |
|
||||||
|
|---|---|---|
|
||||||
|
| `GetMazeDensity` (A) | `const float Blend = 2.0f` | **compile-time constant** |
|
||||||
|
| `MazeCoreVerbatim` (C) | `const float Blend = 2.0f` | **compile-time constant** |
|
||||||
|
| `FSdfCarveOp` (B) | a class member | **runtime data** |
|
||||||
|
| `CarveInlined` | a parameter | **runtime data** |
|
||||||
|
| `CarveNoInline` | a parameter | **runtime data** |
|
||||||
|
|
||||||
|
A == C. B == CarveInlined == CarveNoInline. The two groups differ. **Every single observation from
|
||||||
|
today fits that split, and nothing else does.**
|
||||||
|
|
||||||
|
**Mechanism:** under `/fp:fast`, folding `Blend * 2.0f` to the literal `4.0f` at compile time enables
|
||||||
|
a contraction in `SmoothStep01`'s `3.0f - 2.0f*x` — one rounding instead of two — that the runtime
|
||||||
|
form cannot get. ~1 ULP.
|
||||||
|
|
||||||
|
**Why this matters far beyond the bug:** an operator's parameters are **data by design** — that is
|
||||||
|
the entire point of the refactor. They can never go back to being compile-time literals. So this
|
||||||
|
ULP-level difference is **inherent and permanent** for every archetype port, and no amount of care in
|
||||||
|
transcription will remove it. That is the real, precise reason bit-identity is unachievable here —
|
||||||
|
not the vague `/fp:fast` hand-wave I put in the docs earlier, which happened to name the right
|
||||||
|
compiler flag for the wrong reason.
|
||||||
|
|
||||||
|
**Confirming line added:** `CarveConstBlend` — identical to `CarveInlined` except `Blend` is a
|
||||||
|
compile-time constant. Predicted: matches the verbatim 5000/5000, differs from the runtime form on
|
||||||
|
exactly 126.
|
||||||
|
|
||||||
|
**UNVERIFIED:** that prediction.
|
||||||
|
|
||||||
|
**Next single action:** rebuild, read `CARVE VARIABLE ISOLATION`. If it lands as predicted: correct
|
||||||
|
`OPSTACK-PLAN §2.6`, `AUDIT §C9` and the test's INFO text with the real reason, delete the diagnostic
|
||||||
|
scaffolding, and **resume Phase 1 step 3** — the port is proven correct (SDF exact on 126/126, only
|
||||||
|
the final rounding differs, 0 isosurface crossings).
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -169,6 +169,35 @@ namespace
|
|||||||
return InDensity - Carve * Base * 2.0f;
|
return InDensity - Carve * Base * 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LA DERNIÈRE VARIABLE. Identique à `CarveInlined` à UNE chose près : `Blend` est ici une
|
||||||
|
* CONSTANTE DE COMPILATION, comme dans `GetMazeDensity` et dans la copie verbatim — au lieu
|
||||||
|
* d'être une donnée d'exécution comme dans `FSdfCarveOp` (un membre) ou `CarveInlined` (un
|
||||||
|
* paramètre).
|
||||||
|
*
|
||||||
|
* L'expérience d'inlining a partitionné les mesures exactement ainsi :
|
||||||
|
* A (GetMazeDensity) == C (verbatim) → tous deux Blend CONSTANT
|
||||||
|
* B (FSdfCarveOp) == CarveInlined == CarveNoInline → tous trois Blend À L'EXÉCUTION
|
||||||
|
* et les deux groupes diffèrent. Sous /fp:fast, replier `Blend * 2.0f` en `4.0f` à la
|
||||||
|
* compilation autorise une contraction que la forme à l'exécution n'obtient pas.
|
||||||
|
*
|
||||||
|
* Si cette fonction colle au verbatim 5000/5000 ET diffère de `CarveInlined` sur 126, la cause
|
||||||
|
* est établie sans ambiguïté — et elle est INHÉRENTE à la pile d'opérateurs, dont les
|
||||||
|
* paramètres sont par construction des données et non des littéraux.
|
||||||
|
*
|
||||||
|
* The last variable: identical to CarveInlined except Blend is a COMPILE-TIME CONSTANT. If this
|
||||||
|
* matches the verbatim 5000/5000 and differs from CarveInlined on 126, the cause is settled —
|
||||||
|
* and it is INHERENT to the op stack, whose parameters are data by design.
|
||||||
|
*/
|
||||||
|
FORCEINLINE float CarveConstBlend(float Sdf, float Base, float InDensity)
|
||||||
|
{
|
||||||
|
const float Blend = 2.0f;
|
||||||
|
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. */
|
/** Les params Maze de la strate Maze de la fixture, bornes Z de runtime comprises. */
|
||||||
bool ResolveMazeParams(const VoxelForgeTest::FTestWorld& World, FMazeGenerationParams& Out,
|
bool ResolveMazeParams(const VoxelForgeTest::FTestWorld& World, FMazeGenerationParams& Out,
|
||||||
int32& OutTopVoxelZ, int32& OutBottomVoxelZ)
|
int32& OutTopVoxelZ, int32& OutBottomVoxelZ)
|
||||||
@@ -424,34 +453,41 @@ bool FVoxelForgeOpStackMazeTest::RunTest(const FString& Parameters)
|
|||||||
|
|
||||||
// ── L'expérience décisive : inline vs FORCENOINLINE, même unité, même source. ──
|
// ── L'expérience décisive : inline vs FORCENOINLINE, même unité, même source. ──
|
||||||
int32 InlineVsNoInline = 0, NoInlineMatchesStack = 0, InlineMatchesVerbatim = 0;
|
int32 InlineVsNoInline = 0, NoInlineMatchesStack = 0, InlineMatchesVerbatim = 0;
|
||||||
|
int32 ConstMatchesVerbatim = 0, ConstVsRuntimeBlend = 0;
|
||||||
for (int32 i = 0; i < N; ++i)
|
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 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 FVoxelOpSample S = CoreStack.EvalSample(PX, PY, PZ);
|
||||||
const float Inl = -CarveInlined(S.Sdf, 2.0f, Core.BaseDensity, Core.BaseDensity);
|
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 Noi = -CarveNoInline(S.Sdf, 2.0f, Core.BaseDensity, Core.BaseDensity);
|
||||||
|
const float Cst = -CarveConstBlend(S.Sdf, Core.BaseDensity, Core.BaseDensity);
|
||||||
const float Ver = MazeCoreVerbatim(PX, PY, PZ, Core, World.Settings->Seed);
|
const float Ver = MazeCoreVerbatim(PX, PY, PZ, Core, World.Settings->Seed);
|
||||||
const float Stk = -S.Density;
|
const float Stk = -S.Density;
|
||||||
if (!BitEqual(Inl, Noi)) { ++InlineVsNoInline; }
|
if (!BitEqual(Inl, Noi)) { ++InlineVsNoInline; }
|
||||||
if (BitEqual(Noi, Stk)) { ++NoInlineMatchesStack; }
|
if (BitEqual(Noi, Stk)) { ++NoInlineMatchesStack; }
|
||||||
if (BitEqual(Inl, Ver)) { ++InlineMatchesVerbatim; }
|
if (BitEqual(Inl, Ver)) { ++InlineMatchesVerbatim; }
|
||||||
|
if (BitEqual(Cst, Ver)) { ++ConstMatchesVerbatim; }
|
||||||
|
if (!BitEqual(Cst, Inl)) { ++ConstVsRuntimeBlend; }
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT("INLINING EXPERIMENT (%d samples, same TU, same source, only inlining differs):\n")
|
TEXT("CARVE VARIABLE ISOLATION (%d samples, ALL in this one translation unit):\n")
|
||||||
TEXT(" inlined carve != FORCENOINLINE carve : %d\n")
|
TEXT(" inlined != FORCENOINLINE : %d (inlining is not the variable)\n")
|
||||||
TEXT(" FORCENOINLINE == operator stack : %d / %d\n")
|
TEXT(" FORCENOINLINE == operator stack : %d / %d\n")
|
||||||
TEXT(" inlined == verbatim : %d / %d\n")
|
TEXT(" runtime-Blend == verbatim : %d / %d\n")
|
||||||
TEXT(" IF the first number is nonzero, the cause is FLOATING-POINT CONTRACTION under\n")
|
TEXT(" CONST-Blend == verbatim : %d / %d <-- the tell\n")
|
||||||
TEXT(" /fp:fast, not a logic error: SmoothStep01 is x*x*(3-2x), and 3.0f - 2.0f*x is\n")
|
TEXT(" CONST-Blend != runtime-Blend : %d\n")
|
||||||
TEXT(" exactly the shape MSVC fuses into an FMA (one rounding instead of two, ~1 ULP).\n")
|
TEXT(" The three carve forms are character-identical apart from ONE thing: whether\n")
|
||||||
TEXT(" A and C are straight-line inlined code; the operator stack goes through a\n")
|
TEXT(" `Blend` is a compile-time constant (GetMazeDensity, verbatim) or runtime data\n")
|
||||||
TEXT(" VIRTUAL call, so FSdfCarveOp::Eval is compiled out-of-line and gets a different\n")
|
TEXT(" (FSdfCarveOp holds it as a member; the parameter versions above mimic that).\n")
|
||||||
TEXT(" contraction decision. The earlier three-way tested the TU boundary, which is the\n")
|
TEXT(" If CONST matches the verbatim and differs from runtime, the cause is settled:\n")
|
||||||
TEXT(" WRONG VARIABLE -- this tests the right one.\n")
|
TEXT(" under /fp:fast, folding `Blend * 2.0f` to 4.0f at compile time enables a\n")
|
||||||
TEXT(" IF the first number is zero, contraction is NOT it and the operator stack has a\n")
|
TEXT(" contraction in SmoothStep01's `3.0f - 2.0f*x` that the runtime form cannot get.\n")
|
||||||
TEXT(" real logic bug that survives every reading so far."),
|
TEXT(" That is ~1 ULP, and it is INHERENT to the operator stack: an op's parameters\n")
|
||||||
N, InlineVsNoInline, NoInlineMatchesStack, N, InlineMatchesVerbatim, N));
|
TEXT(" are DATA by design, so they can never be compile-time constants again. Nothing\n")
|
||||||
|
TEXT(" to fix in the port -- this is the true, permanent floor for every archetype."),
|
||||||
|
N, InlineVsNoInline, NoInlineMatchesStack, N,
|
||||||
|
InlineMatchesVerbatim, N, ConstMatchesVerbatim, N, ConstVsRuntimeBlend));
|
||||||
}
|
}
|
||||||
|
|
||||||
MutableGen->OriginSpineRadius = SavedSpine;
|
MutableGen->OriginSpineRadius = SavedSpine;
|
||||||
|
|||||||
Reference in New Issue
Block a user