Commit Graph

61 Commits

Author SHA1 Message Date
Fr0zka a49d440bf6 build: put FPSemantics=Precise on the VoxelForge module — the experiment never ran
FPSemantics is a per-module ModuleRules property. It had been set on the VoxelM
GAME module, while every line of density code lives in VoxelForge, which kept
compiling /fp:fast. So the run that supposedly "reproduced the residue under
precise semantics" ran under fast semantics and proves nothing.

Retracting the previous commit's conclusion: /fp:fast is NOT eliminated as the
cause, and the notes claiming AUDIT C9 and OPSTACK-PLAN 2.6 are falsified are
withdrawn with it. Those documents were fine.

My error, and the third of its kind today: I reasoned a confident conclusion from
an unverified premise, one paragraph after writing that the lesson was to
instrument rather than assume. Checking took one grep and I only ran it after
Jahni suggested it.

The line is marked TEMPORARY with removal instructions and a guide to reading the
result. Combined with the WORST-POINT DUMP already committed, one build now
separates the two possibilities cleanly:
  454 -> 0   : FP model was the cause; keeping precise then needs a profile,
               because it costs the vectorisation T2.a's SIMD work was buying.
  454 -> 454 : real logic difference; read the dump.

Either way the line comes back out afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:34:50 +02:00
Fr0zka cca9e83182 test: /fp:precise reproduced the residue byte for byte — instrument instead of guessing
An /fp:precise build returned the identical 454 samples, identical max delta,
identical coordinate. A different float model producing byte-identical output is
proof that rounding is not the cause, so the /fp:fast explanation is dead. That
is three failed hypotheses on one discrepancy (FVector round-trip, then "check
the roughness window", then /fp:fast), each reasoned from plausibility and each
costing a build.

So: stop reasoning, print. FVoxelOpStack::EvalSample exposes the full sample, and
MazeEquivalence now dumps the worst point in raw hex -- both densities, the
stack's internal SDF, and the carve factor reconstructed from each side. The
recovered carve localises it: identical carve + differing density means the fault
is after the conversion; differing carve means it is in the SDF (lattice edges or
VoxelSDF::Capsule) or in SmoothStep01.

Note for whoever reads the docs next: AUDIT C9 and OPSTACK-PLAN 2.6 currently
assert the /fp:fast story as the explanation for THIS residue. That specific
claim is falsified and needs walking back once the dump identifies the real
cause. C9's other half -- that UBT's FP default differs by toolchain and the MP
model assumes bit-reproducible terrain -- stands independently; it was read out
of VCToolChain.cs and ClangToolChain.cs, not inferred from this test.

Phase 1 step 3 (wiring the stack into GetDensityAt) is paused until this is
understood. Small unexplained numeric differences do not get smaller when you
build on top of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:32:22 +02:00
Fr0zka af5f2103b3 test: the Maze residue is /fp:fast, not port drift — encode the real bar
The bisect settled it. The difference survives every stage removal down to
"corridors + carve ONLY", which is character-for-character transcribed code, so
it is not in anything the decomposition added.

Cause, read out of the engine rather than assumed (VCToolChain.cs):

    case FPSemanticsMode.Default: // Default is imprecise FP semantics.
    case FPSemanticsMode.Imprecise: Arguments.Add("/fp:fast"); break;

with UBT's own doc: "the compiler is allowed to transform math expressions in
ways that might result in differently rounded results". Identical source in two
translation units may reassociate differently, worth ~1 ULP. It shows up on
exactly the ~2% of samples inside the SDF blend shell, where Blend - Sdf
catastrophically cancels; outside it Carve is exactly 0 or 1 and both agree.

So MazeEquivalence now grades what it can actually assert:
  - hard fail  : any isosurface crossing (geometry moves)
  - info       : differences at ULP scale (the unavoidable floor)
  - warn       : anything larger, which IS port drift, and runs the bisect
A test that warns on every port would get ignored by the port that matters.

Recorded in OPSTACK-PLAN 2.6, and as AUDIT C9 for the part that outlives this
refactor: ARCHITECTURE 9.1's "every peer regenerates identically" holds only
between bit-identical binaries under /fp:fast. Fine for one build on one
platform; a real desync source for a Linux server plus Windows clients both
regenerating authoritative geometry. The FPSemantics::Precise knob exists but
must not be turned speculatively -- it blocks the vectorisation T2.a was chasing,
on the hot loop, for an unmeasured cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:15:43 +02:00
Fr0zka 23605d5350 test: bisect the residual Maze difference instead of guessing at it again
All six tests are green as of the 12:02 run, so Phase 0.5's gate is met.

MazeEquivalence still reports 454 differing samples, the same max delta, at the
same coordinate as before -- byte for byte the previous result. So the FVector
float->double->float hypothesis from the last commit is dead: that detour is a
no-op, exactly as /fp:precise says it should be. It stays (harmless, and it
documents the original's shape) but it explains nothing.

Rather than propose a third guess, MazeEquivalence now bisects: it re-runs the
comparison with roughness, then seal, then spine, then passages disabled ON BOTH
SIDES, and reports which stage's removal makes it bit-exact. One run answers what
two hypotheses failed to.

Standing hypothesis for the bisect to confirm or kill: compiler float
contraction across translation units under /fp:fast, worth ~1 ULP. It fits the
~2% hit rate -- only voxels inside the narrow SDF blend shell have an unsaturated
carve factor; everywhere else Carve is exactly 0 or exactly 1 and both paths
agree bit for bit. If confirmed, bit-identity is not achievable in principle for
these ports and the bar for every later archetype is "zero isosurface
crossings", which is what OPSTACK-PLAN 2.6 asked for anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 14:07:14 +02:00
Fr0zka 826a8c99dc fix: first-green-build follow-ups (diff-layer assertion + Maze float rounding)
The build passed and six tests ran; five green. Details in OPSTACK-PROGRESS.md.

1. DiffLayerContention's failure was the test, not the plugin.
   GetTotalModificationCount() sums STORED ENTRIES, not operations -- a stroke is
   filed under every chunk its AABB overlaps, so 400 radius-6 spheres straddling
   chunk corners store 3200 entries. The assertion now compares the stored count
   against the chunk fan-out ApplyModification itself returned, which also checks
   that the re-mesh list handed to the caller describes what was actually written.
   Everything the test exists for had already passed: 7 readers, 28.7M read
   rounds against 760 writes and 6 Clear()s, no crash, monotonic version.

2. MazeEquivalence: 454/20000 samples differed by at most 1.907e-06 -- exactly
   one ULP at magnitude 16 -- with ZERO crossing the isosurface, i.e. not one
   triangle would move. Leading hypothesis: the original routes noise coords
   through an FVector (double in UE5) and back to float, rounding twice, while
   the op passed floats straight through; under /fp:fast those round differently.
   The op now reproduces the detour on purpose, with a comment against
   "simplifying" it. Unverified -- it predicts 0 differences next run. If drift
   remains, next candidate is FMA contraction across translation units.

Also recorded, because it is the perf half of the whole refactor: the Maze op
stack proved 23 of 60 tiles uniform. ClassifyTile proves ZERO for any cave
archetype today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 02:40:51 +02:00
Fr0zka 8a33bcb42a docs: CODEMAP rows + progress entry for the Phase 1 Maze port
CODEMAP gains 3.2c (VoxelDensityPrimitives), 3.2d (the operator stack and its
factories), FVoxelOpSample under 3.2b, and the two new tests under 3.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 02:33:00 +02:00
Fr0zka 3e4ee198fe docs: progress marker before the Phase 1 Maze port
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 02:23:19 +02:00
Fr0zka f8194dbf92 docs: correct an inaccurate claim in an earlier progress entry
The "starting Phase 0.5" entry said the ClassifyTile test self-skips when the
fixture fails to build. It does not -- all four tests fail loudly with a message
identifying it as a fixture failure. Corrected by appending, since the log is
append-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 02:07:49 +02:00
Fr0zka 6267af86a7 docs: tick OPSTACK-PLAN phases + final progress entry for the unattended session
Phase 0.5 and the Phase 1 skeleton marked WRITTEN / NOT COMPILED (not "done" --
the gates are not met until the tests actually run). Section 8 independent fixes
2, 5 and 6 ticked. Section 9 "Resume here" now says BUILD.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 02:07:32 +02:00
Fr0zka a820f140e1 docs: progress log — batch A+B landed, build gate reached
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 01:58:47 +02:00
Fr0zka 3128852d4e chore: track the design docs in git (.gitignore !*.md)
AUDIT P1: every markdown design doc except CODEMAP.md was untracked, so
ARCHITECTURE / AUDIT / OPSTACK-PLAN / fable-idea / REVIEW_FINDINGS lived
only on disk. Replaces the single !CODEMAP.md exception with !*.md.

Also makes OPSTACK-PROGRESS.md commits actually record something, which
the unattended crash-safety discipline depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 01:39:21 +02:00