Skip to content

Phase 1 / Forge / Cross-platform determinism of forge_3d - #71

Merged
guysenpai merged 85 commits into
mainfrom
phase-1/forge/determinism
Aug 20, 2026
Merged

Phase 1 / Forge / Cross-platform determinism of forge_3d#71
guysenpai merged 85 commits into
mainfrom
phase-1/forge/determinism

Conversation

@guysenpai

@guysenpai guysenpai commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Brief

briefs/m1.1.14-determinism.md

STILL A DRAFT. Marking it ready is Guy's call, and so are the merge, the squash message and the
tag annotation. Opened after Gate A rather than at Étape 5 because ci.yml triggers only on push to
main and pull_request to main: without an open PR, Gates A, C and D are structurally
unmeasurable. Recorded as a deviation in the brief.


WITNESS REGENERATION — DECLARED, as the contract requires

The eight committed witnesses were re-baselined in this PR. Witness-regen: trailer on e851ff0,
generated by run 32026918193 on
ubuntu-24.04 at -Dcpu=baseline, zig 0.16.0.

Motive: the canonical scenario was extended with a riser, three mesh ramps forming a closed bowl,
and the kinematic character serialised into the continuous state — the P1-1 correction below. A
scenario change invalidates every chain witness by construction, so this is a legitimate re-baseline
and not a witness bent to make a cell green.

A falsifiable prediction was written into the commit BEFORE the run, and it held exactly:

predicted measured
only the 4 continuous-chain witnesses change 4 of 4 CHANGED
the 2 discrete traces and 2 reference windows are byte-identical 4 of 4 IDENTICAL
the 4 ARM cells stay green through the stale-witness push 4 of 4 success
the 8 x86_64 cells fail on the chain 8 of 8 failure

Nothing rigid moved — the terrain is static and far from every dynamic body, the character is virtual —
and those four artifacts are derived from the rigid simulation alone. Had a discrete witness moved it
would have been a finding, not a re-baseline; saying so in advance is what made the difference
measurable rather than arguable afterwards. git shows only four modified .bin files, a third
independent confirmation. Cross-mode agreement re-measured and holds at both precisions.


What this milestone delivers

C1.1 level 1 green — the 1000-frame hash chain bit-identical across ubuntu-24.04 and
windows-2025 on all four (precision, mode) keys — and level 2 point 1 green on a real
ubuntu-24.04-arm cell.

Four behavioural changes, where the frozen Scope named two: a deterministic cosine replacing
@cos at the max_slope conversion; the float environment INSTALLED at every thread start and
process entry and ASSERTED at the physics entry; float @reduce replaced by an explicit left fold at
18 sites, behind a no_float_reduce lint rule (a Zig backend defect, written up and not filed —
filing is Guy's act); and shm.zig moved to page_size_min at 8 sites, which is what made AArch64
compile at all.

CI matrix {ubuntu-24.04, windows-2025, ubuntu-24.04-arm} × {Debug, ReleaseSafe} × {f32, f64} = 12
cells
, every one pinned -Dcpu=baseline, with zig build lint and zig build forge-determinism on
the cell path — before this milestone the first ran in no workflow and the second in none either.


The review, and the six corrections

An external review found six defects, five of them this milestone's own dominant family: an
artefact rendering a verdict on something other than what it claims to measure, and answering green.
The class therefore survives its own doctrine wherever no mechanical guard covers it — which is
the milestone's real finding. All six are corrected, each with a counter-factual RUN on the object.

P1-4 — regeneration worked only when it was pointless. With correct witnesses --write-witness
exited 0; with one byte altered — the only case where regenerating means anything — it wrote all three
files and then exited 1, so under set -euo pipefail the CI step died exactly in the case it exists
for. Verified in three directions.

P1-3 — ARCH-031 rule 5's site set was measured, not inherited: TEN, not three. Seven were
uncovered, including determinism_main itself — the determinism instrument asserting the guarantee
without installing it. No exception for a program that compares nothing today. Three sites are inside
modules, where install()'s own doc comment declared a fourth-in-a-module to be a defect; the
enumeration is deleted in favour of the predicate, and the class was swept over four texts.

Bit-neutrality of the install is MEASURED, by a deduction stronger than the byte comparison: the
environment assertion — live in Debug and ReleaseSafe, its reader witnessed on both ISAs by per-field
perturbations whose encoding differs between architectures — passed on all 12 cells when nothing
installed on the witness path. So the inherited state equalled engine_default everywhere, and
installing a value into a state already holding it is a bit-level no-op. Corroborated end to end on
the 8 cells where level 1 applies. The window for that measurement closed with P1-1, which is why
P1-4 and P1-3 shipped alone.

P1-1 — the scenario had NEITHER a step NOR a slope while its header claimed both, and the
character reached no artifact: mobile holds rigid bodies and a virtual character owns none, so
the controller ran 1000 frames and every bit was discarded. The guard written for that case could not
catch it — a count pinned alongside the change it must catch catches nothing. Fixed with mesh ramps
(rotated boxes abandoned after three measured failures, one wedging the character in a crevice for 775
frames), a bowl closing an unbounded 2.2 m/cycle drift that would empty the instrument at a longer
replay, and the walk raised 0.03 → 0.06 because the scenario walked below the threshold of its own
step arm
. The cosine bracket bites both ways: max_y 0.0063 / 0.9463 / 2.6707 at cosines
0.9211 / 0.7074 / 0.3624.

P1-2 — the deterministic cosine is pinned to an oracle that never calls @cos. Pi to 80 digits in
exact decimal arithmetic, computed twice by different Machin-like formulas and required to agree to 70.
Two columns separating CORRECTNESS from REPRODUCIBILITY, said plainly because conflating them is the
family above. At f32 the implementation is correctly rounded on all twelve arguments; at f64 nine of
twelve, worst absolute error 3.14 eps. The bound had to be ABSOLUTE, not in ULP: at the f64 nearest
π/2 the error is 1.6e11 ULP of the true value while being 2.0e-21 absolute — the smallest in the table
— so a ULP bound would fail on the most accurate row.

P1-5 — the fourth discrete trace is now an oracle rather than an accumulator. One real removal at
frame 196, the static mesh against the frictionless sphere, asserted on the set and never on its
cardinality
: the size returns to 11 one tick later, so a size-based probe sees the removal only
because the two events land on different ticks.

P2-6 — divergenceFrame answered none on absent data. An empty, truncated or wrong-precision
window returned null, which the instrument prints as "no divergence". The two outcomes are separated
and the in-loop bounds test is removed: with the entry check it cannot fire, and a guard whose only
reachable branch is the one nobody wanted is what let the silent null live.


The conservation is proven to JUDGE, not merely to run — two negative witnesses

A step that prints OK whatever it is given is indistinguishable, on a green run, from a step that
works. That is exactly what P2-7 was, and it is why the reds were taken.

SHA run measured
red #1 — layer two, the CI-passed total d237f82 32044416532 8 non-Windows cells RED: CONTROL FAILED — expected 1866 collected, zig build test reported 1867, with layer one printing conservation OK in the same log
red #2 — layer one, the declared Windows constant 8116831 32063230404 4 Windows cells RED, all four printing BOTH numbers: CONSERVATION FAILED — closure gives 1864 expected collected, expectedCollectedOn(windows) declares 1865; 8 non-Windows cells GREEN at 1866

Red #2 killed a hypothesis red #1 could not reach: that the comparison is tautological on Windows,
both halves drawn from one source, printing 1864 / control OK at 1864 and staying green whatever
happens. And its fault was invisible from the development host — macOS reads the else branch, so
zig build dead-tests printed conservation OK at 1866 and the pre-commit hook let it through, where
it had REFUSED red #1's fault. That is precisely the regime a real per-platform constant defect would
live in.

Coverage, stated exactly rather than rounded up. Together the two reds cover both layers, all twelve
cells, and cross-platform non-contamination in each run — the conservation being per platform, that
non-contamination is the property needed, not twelve reds. Not covered: layer two on Windows, and
layer one on non-Windows; each has a positive witness only.

Both reverted. Restoration verified on the cell logs and not on the diff — a revert that restores
incompletely, both halves moving together, would go green again and the diff could not see it. Read back
on run 32064137356, 12 of 12 green: 1866
with control OK at 1866 on the eight non-Windows cells, 1864 with control OK at 1864 on the four
Windows ones, and forge-determinism present on 12 of 12 by enumeration.


Measured at close

Matrix 11 of 12 cells green; windows-2025 / ReleaseSafe / f32 hit the recorded hang — test runner failed to respond, zero failed assertions in the log, run test 552 pass (552 total) — signature verified before the flake was invoked, job re-run
forge_3d tests 527 → 552, green at f32 and f64, Debug and ReleaseSafe
Suite 1866 on ubuntu-24.04/macOS, 1864 on windows-2025
Witnesses 8 committed, re-baselined, SHA256SUMS verified 8/8, per-file provenance
Slider residual persists, characterised: 4 ULP at f32, 3 at f64, against the 2^31 an energy injection would need — rounding, not energy

Residuals, named and carried to the plan

  • M1.D.11 — a lint rule for rule 5's site set. Mechanical, and deliberately not built here by
    Guy's ruling. Until it exists a new thread or main can arrive with no installation and nothing
    says so.
  • The environment assertion's wrapper is unwitnessed. Every counter-factual perturbs the register
    and checks the predicate; none observes the panic. A sign-inverted mutant would be caught, only the
    always-true mutant survives.
  • GroundState is constant on the canonical scenario, so the verdict field carries no
    discrimination there — the position does. Not to be read as coverage of the .on_steep_ground and
    .in_air arms.
  • M1.D.8 / M1.D.9 / M1.D.10 — the ARM compile-reuse collapse, bench.yml's cache key missing the
    CPU axis, and weldengine/setup-zig purging a tree that overlaps .zig-cache.
  • The upstream Zig @reduce report — written, not filed. Filing is Guy's act.

A libm transcendental on a compared path is divergent engine state: two C
libraries disagree by an ULP. foundation/math/trig.zig carries one function
at a fixed operation order — Cody-Waite reduction plus minimax kernels, no
external symbol, no FMA, integer part by magic-constant add so that neither
@floor nor @round appears.

Domain |x| <= 2^20*pi/2, the exactness limit of the reduction, asserted
rather than clamped: serving past it needs Payne-Hanek, the replacement
libm ARCH-031 rule 4 names as an abuse of its own clause.

Measured: worst absolute error 1.110e-16 (half an ULP of unity) on a 4096
point oblique sweep, 2.123e-15 at the domain edge where the reduction is
weakest, and the f32 result is bit-identical to the correctly rounded f64.
ARCH-031 rule 5: round-to-nearest-even with denormals preserved, on every
engine thread. The default state is not portable — it depends on the OS, the
linked C runtime and on what a graphics driver may have left behind — so it
is installed, never assumed, and the job system being work-stealing means one
worker with denormals flushed would make a result depend on scheduling.

The mechanism lives in foundation/float_env.zig and core/platform/float_env.zig
is a facade over it, holding no second copy of the register layout. Reason: the
platform layer INSTALLS and every compared module ASSERTS, and the module that
must assert is forge_3d, which may not import weld_core (a C1.1 exit metric).
Tier 0 therefore gains a foundation dep — acyclic, foundation imports only std.

Three call sites, the platform layer having no init entry of its own today:
the head of the job system worker, and the two engine binaries main threads.

Also carries tools/asm_inventory and the forge-asm-inventory build step: a Zig
scanner over the emitted assembly of the three shipped targets, named tool
substitution for grep, whose \\b is a GNU extension that silently matches
nothing on BSD. 41416 call sites examined, zero external transcendental.
Counter-factual: reinstating @cos in the controller yields call cosf@PLT,
call cosf and bl cosf, one per target, each named with file and line.
ARCH-031 rule 5 puts a one-word obligation on a module whose output is
compared, and it is easy to get backwards: assert, never install. A module
that re-installed the state would repair its own thread and leave every other
consumer of that thread running on what it silently fixed, with no diagnostic.

checkFloatEnvironment returns the offending state rather than a bool, so the
report can say what is wrong. The entry point is whatever drives a tick: today
the acceptance harness World, tomorrow PhysicsWorld.step at M1.1.15, which
inherits the same call and moves nothing else.

Three cases, and the second is the discrimination guard the other two need:
the check sees a perturbed rounding mode, LEAVES it in place, and flushed
denormals are detected too — an assertion that only ever perturbed the
rounding mode would pass against a check comparing one field of three.
cos(max_slope) is STORED ENGINE STATE: it is computed once at creation and
sits in the compared bits from frame 0 to the last. @cos lowers to an external
cosf on Linux, Windows and AArch64 alike, and the Windows and Linux
implementations need not agree to the last bit — so this one scalar conversion
was, measured at recon, the whole libm exposure of the deterministic path.

The controller algorithm is unchanged; this is a scalar-conversion
substitution. max_slope is already refused outside [0, pi/2] by a typed error
before the call, so the new domain assert of math.cos is unreachable from here
and is a backstop rather than a second gate.
ARCH-031 rule 6, the third pinning axis alongside compiler version and
optimize mode. No cell compiles for its native CPU. The motive is not parity
between two OSes — two IEEE-strict builds agree anyway — it is stability in
time: a runner image that moves to a new processor generation would otherwise
invalidate a committed determinism witness with no code having changed, and
the diagnosis would go looking inside the engine.

ZIG_CPU: baseline at workflow level, on all twelve zig build invocations.
baseline rather than a named model because the compiler defines it per
architecture, so one value serves x86_64 and aarch64 and cannot drift with a
runner refresh. Full suite green at -Dcpu=baseline: 266/266 steps, 1721/1738
tests. The forge-asm-inventory step joins on one cell — it answers a property
of the three targets, not of the host asking — mirroring verify-synth-100.
Found by self-review of the scanner, not by a failing run — which is the
point: a scanner that misses a call reports a clean tree, and nothing else
would have said otherwise.

The operand was read as ONE symbol. That serves call cosf@PLT and silently
misses call qword ptr [rip + cosf@GOTPCREL], the form -fno-plt produces —
exactly the build flag a distribution is most likely to add. The operand is
now tokenised and every token tested; matching stays exact against thirteen
names, so registers, size keywords and address arithmetic cannot
false-positive.

And only one leading underscore was stripped, so a C library internal alias
spelled __sinf would have passed. All leading underscores go now, after the
__imp_ prefix rather than before it.

Three positive and three negative cases added. Re-verified: same 41416 call
sites, still zero, and the real counter-factual still fires with all three
spellings.
GATE A REVIEW ROUND. B1 was a blocker: the placement violated the metric it
invoked. My paraphrase of the C1.1 bound was "not weld_core"; verbatim it is a
WHITELIST OF TWO — "Dependance uniquement de src/foundation/math/ et des
composants ECS publics de src/modules/forge/api/". A file at
src/foundation/float_env.zig is neither, so forge_3d was gaining a THIRD
foundation dependency outside the list.

The single definition moves to src/foundation/math/float_env.zig, one of the
two whitelisted entries. The core/platform/ facade is DELETED rather than
repointed: a file whose only content is a re-export across a tier boundary adds
a name without adding a definition. Tier 0 imports the definition directly at
its three call sites. ARCH-031 own sources line splits the same way, which is
corroboration rather than the argument.

R2 — every correct-rounding claim about cos removed, from doc comments and
tests. A 0.5 ULP ABSOLUTE error cannot establish correct rounding for a
function whose image crosses zero, and the 2.123e-15 measured at the domain
edge contradicts it outright at ~9.6 ULP. The maxima stand as measured maxima,
and the contract is restated where it belongs: binary identity between
platforms, oracle = the committed value table.

R3 — where FMA contraction is actually verified, measured at baseline on a
two-line probe and written in the scanner header. aarch64 turns @mulAdd into
fmadd (base ISA, so the backend could contract and does not); x86_64 turns it
into jmp fma@PLT, FMA3 being Haswell and later so baseline has no fusion
instruction at all. ARCH-031 rule 2 is exercised on ONE target of three and
holds on the other two by absence of hardware. Zero fused instructions measured
across all three listings, with the probe proving the pattern fires.

R4 — five test blocks rendered one verdict over several INDEPENDENT claims.
Split by mechanism; where the cases sample one mechanism the failing case is
now named, which a bare expectEqual in a loop never was. asm_inventory 5 -> 9
blocks, trig 7 -> 8, float_env 5 -> 8, determinism 3 -> 4.

Green at -Dcpu=baseline: 266/266 steps, 1728/1746 tests; forge_3d 531/531 at
all four corners; inventory 41416 call sites, zero found.
The first matrix run went red on ubuntu-24.04 / Debug at `zig build`:
`invalid constraint: '*m'`. The two MXCSR accessors had never compiled on
x86_64 in Debug, and the pre-push check that should have caught it was
VACUOUS: `zig build-obj -target x86_64-linux float_env.zig` returns 0 while
analysing nothing, the file being a root module with no export and no test,
so lazy analysis never reaches the private accessors. The faithful local
check is `zig build -Dtarget=<triple>` over the whole graph.

Three facts, all established by disassembling emitted objects rather than by
reading exit codes:

- Zig 0.16 uses two backends for x86_64 and they do not accept the same
  inline assembly. Debug goes through the self-hosted x86 backend;
  ReleaseSafe and ReleaseFast go through LLVM. `"*m"`, `(%[p])` with an "r"
  pointer, `0(%[p])` and `(%rsp)` are correct under LLVM and rejected by the
  self-hosted backend. `"+m"` crashes LLVM.
- Worse than a compile error: `"m"` as an INPUT is accepted by both and means
  different things. The self-hosted backend loads the VALUE; LLVM
  materialises a POINTER into a slot and hands the instruction that slot, so
  `ldmxcsr` would have configured the FPU from the low half of a stack
  ADDRESS — silently, in every shipped build.
- `-fno-emit-bin` skips inline-assembly validation entirely.

What ships is `"=m"` as an OUTPUT on both instructions, the only pair both
accepted and correct on both backends. For `stmxcsr` that is its true
direction. For `ldmxcsr` it is a deliberate lie whose one consequence — the
optimiser may treat the slot as undefined and drop the store that filled it —
is closed by writing through a `*volatile u32` first, a language-level
guarantee rather than a hope about an optimiser.

Verified on the REAL module, not on a scratch probe: at ReleaseSafe the
install path emits read, mask, volatile store, write back, with the value and
not an address, and the other MXCSR bits preserved.

Whole-graph cross-builds clean at {Debug, ReleaseSafe} for x86_64-linux-gnu
and x86_64-windows-gnu. aarch64-linux-gnu cannot be cross-built from this
machine at all and that is NOT this branch: main at 5259344 fails identically
on std/atomic.zig:21:20, measured through a worktree. That leg is CI-only.
Language rule, ruled at the Gate A review: a French string is admitted as an
IDENTIFIER — file name, section heading, invariant field name — because those
are anchors and translating them breaks search. A French SENTENCE is not: it
is rendered in faithful English with a file + anchor pointer, the original one
search away.

The C1.1 dependency metric was quoted as a sentence and becomes English with
its pointer, keeping the two things that actually protected the fix — the
STRUCTURE (a whitelist of exactly two) and the LOCALISATION (file + block).
Paraphrasing that structure is what put this file at the wrong address once.

Conséquences and Sources de vérité stay: both are identifiers.

Also journals the two enumerations owed at the gate. The eighteen uncounted
tests, differentially against main through a worktree: 17 skipped there
against 18 here, so exactly one new skip, and it is the x86_64 arm of
float_env, guarded on the architecture and observed by name. Sound: Rosetta
neutralises MXCSR on this host and a test that cannot discriminate must not
pretend to. And there is no fourth citation — four lines, three citations.
Gate B named STOP condition, determined: the non-pruning lives in the TEST
SCAFFOLD, not in the engine, so it is fixed here.

World.active in tests/solver_test.zig only ever grew — computePairs appended,
sortDedup deduplicated, nothing removed. The engine holds no retained set at
all: computePairs is moved-driven and returns a delta, so the persistent set
belongs to its caller, the harness today and PhysicsWorld at M1.1.15. No
engine retention exists to change, so the STOP-and-return clause does not
apply. CLAUDE.md has recorded since M1.1.6 that the harness keeps every
emitted pair as a conservative superset: it was BEHIND the normative rule of
1.7 step 2, never ahead of it.

Two read-only accessors added to Broadphase, data and not policy: Bvh.proxyAabb
was already public and the multi-layer aggregate did not forward it, and
unboundedShape is its half-space counterpart. Same class as BodyManager.entity
at M1.1.10. The rule stays in the caller deliberately — pair retention IS the
wake graph, so putting it in the acceleration structure would move an
islands-and-sleep behaviour into the broadphase.

Measured: 531/531 still green with pruning live, so no existing behaviour
moved, including the M1.1.6 small-hop pin — a sub-margin hop does not separate
the FAT boxes, which is what the margin is for. 532/532 with the new probe, at
Debug and ReleaseSafe, f32 and f64.

Counter-factual on the object: the prune neutralised in place fails exactly one
test, the new one, on exactly the assertion carrying the claim, with zero
compilation errors. The other 531 stay green under it.

The probe is the COMPLEMENT of the small-hop pin. Either alone is satisfied by
a degenerate rule — never prune, or always prune — and only the two together
pin the normative one. It carries its own positive witness: the pair is
asserted to exist before it is shown to disappear.
Gate B. The seven frozen elements of the brief, body creation order numbered
in the code because that order fixes BodyId, hence the island rank, hence the
resolution order: reordering it is a different scenario and invalidates every
witness. Sleeping is ON — the transitions are one of the four traces, and
initNoSleep is for convergence measurements and would silence it.

THE DELIVERABLE IS THE SCOPE TEST, not the scene. A witness taken over a scene
where the groups never meet, the sensor never fires or the mesh never yields a
second constraint would be perfectly stable and prove nothing: the trace would
agree with itself because nothing happened. One test drives 400 frames and
asserts each mechanism is OBSERVED — a half-space contact, a sleep transition,
an island count that both rises and falls, two constraints sharing one
pair_key (the third term of the ordering key actually being needed), exactly
one sensor entered and one exited, the slider still above 4.9 m/s, and the
character resolving ground rather than sinking.

It failed twice and both were in the probe or the arithmetic, never the scene.
saw_ground_contact was false because I assumed the ground carries BodyId 0: a
BodyId is a GENERATIONAL handle and pair_key is min<<32|max, so neither the
value nor the side is mine to assume — fixed by storing the two static handles
and testing both halves. The sensor reported two entered instead of one;
hypothesis, the trigger at y=2 spans [0,4] and its lowest face touches the
half-space boundary so it detects the GROUND as a second permanent pair;
tested by lifting it to y=3 and the count went to one. Measured, not argued.
And a comment counted eleven mobile bodies where the constructor makes twelve
— the assertion caught the comment, and the arithmetic is now written out.

535/535 at Debug and ReleaseSafe, f32 and f64.
Gate B. trace.zig carries the three artifact kinds, run.zig the replayable
entry, determinism_main.zig the shell, and zig build forge-determinism runs the
canonical scenario at one worker.

First real output, f32/Debug: self-reproducible OK over 1000 frames, 32000 B
chain, 24000 B discrete, 20160 B poses; divergence frame none within K=60. The
sizes are checkable by hand — 1000 x 32 B of SHA-256, and 12 mobile bodies x 7
scalars x 4 B = 336 B per pose frame x 60. At -Dphysics_f64 the poses double to
40320 B, an independent check that the encoding follows Real.

Four decisions. SHA-256 rather than a fast hash, because a witness must survive
a compiler patch bump and a standardised digest is defined by its
specification. Chained rather than a digest of the concatenation, because a
chain LOCATES the first differing frame and "the outputs differ" is not a
diagnosis. The deviation metric REUSES the sleep criterion of 1.8.3 with the
sleep_radius the engine already computes — two formulas for one geometric fact
is the defect class this repository names. And the entry sits at forge_3d level
because a Zig module import path is rooted at its root file directory, measured:
an executable rooted in tests/determinism cannot reach config.zig at all.

Four liveness guards, none asked for and all of the inventory counter family:
sizes asserted before equality, adjacent chain links asserted distinct, the
discrete window asserted not constant, and the deviation metric asserted to
fire at frame 0 against a reference displaced by one metre.

539/539 at Debug and ReleaseSafe, f32 and f64; full suite 1736/1754, same 18
skips.
The form C1.1 requires was already there — r is sleepRadius(id), read inside
the per-body loop, and the comparison is deviation > 1e-4 * that body r. Read
from source, not from memory. What was missing is that nothing could catch its
loss: the canonical scenario twelve mobile bodies are all of comparable size,
so an ABSOLUTE threshold passes over it. That is the assertion-valid-only-
through-a-tacit-property-of-its-fixture class, and it becomes undetectable once
a witness is committed over the scene that hides it.

The predicate is extracted as bodyExceeds(translation, rotation_chord, r) and
pinned by two tests an absolute form cannot pass. The first takes bodies three
orders of magnitude apart — 1 cm and 10 m — and gives each half then twice ITS
OWN threshold: a per-body form answers false-then-true for both, while any
constant misclassifies one of the two columns, and the test states that
arithmetic explicitly. The second pins the ROTATION weighting: at zero
translation the 2*r numerator and the 1e-4*r denominator cancel, so the chord
criterion must be radius-independent, and dropping r from either side would
leave the translation tests green while moving the rotation criterion by three
orders of magnitude between the two bodies.

Counter-factual on the object: the per-body threshold replaced in place by an
absolute one fails exactly those two tests and nothing else — 539 of 541 still
green, zero compilation errors, so the extraction changed no behaviour.

541/541.
Gate C provenance mechanism. A witness pins a result in time, so producing one
must be a DECLARED ACT with a verifiable provenance and never a side effect of
a red cell. workflow_dispatch only, a REQUIRED written reason, and the run URL
goes into the body of the commit that adds the binaries — provenance stops
being an assertion and becomes a link.

It cannot be generated on the development machine: that is Apple Silicon, and a
continuous-chain-f32-Debug.bin fabricated there for an x86_64 chain is a
witness with no provenance at all.

Linux by construction, and the consequence is intended: Linux becomes the
reference and Windows the verifier. A Windows divergence is then THE
measurement of the milestone. Regenerating on Windows to make such a divergence
pass is named and forbidden in the file — it is the silently re-baselined
witness, the one act that destroys what the witnesses are for.

No aggregator and no actions/download-artifact: collection is by gh run
download, a CLI call and not a workflow action, so the 7.3 whitelist is not
extended. upload-artifact@v6 is already whitelisted and already in use.

The two mode-independent kinds are keyed by precision alone, which ASSERTS that
Debug and ReleaseSafe agree on them. That is a hypothesis, so the workflow
checks it rather than letting the file name assume it: the two copies must be
byte-identical before either is published, and a mismatch fails generation as a
level-1 finding.

Verified locally on the generation path: the three files appear with the right
names and sizes, and on aarch64-macos Debug and ReleaseSafe agree byte for byte
on all three kinds — an indication for the hypothesis, on a fourth platform, so
not the measurement.
workflow_dispatch cannot fire from a feature branch. Measured, not assumed: gh
workflow run --ref phase-1/forge/determinism returns HTTP 404, workflow not
found on the default branch. GitHub requires the file to exist on the default
branch before it can be dispatched at all, whatever --ref says.

The in-repo claim to the contrary is refuted by the same measurement:
nightly-fuzz.yml lines 7-8 state that workflow_dispatch lets it be triggered
manually before its branch is merged. It has been dormant since M0.7 because
nobody tried it from a branch.

The generation path is verified and is NOT the blocker: --write-witness
produces the three files with the frozen names and sizes, and the cross-mode
agreement check is exercised. What is blocked is WHERE the witnesses are
produced, which changes the provenance story and is therefore a design decision
rather than an implementation choice.

Frozen until Claude.ai rules. Nothing committed to the witness directory.
The standalone workflow_dispatch file is DELETED — measured untriggerable from
a feature branch, and an untriggerable workflow left in the tree is itself a
statement measured false. Generation becomes a job in ci.yml, which is already
on the default branch and therefore already runs on the PR head.

Gated on a Witness-regen trailer in the head commit message. That is stronger
provenance than a dispatch input: the declaration is reviewed in the diff,
greppable and permanent, where a form field leaves no trace in the repository.
Absent the trailer the job skips. Verified NOT to be in ci-gate needs — a
conditional job in a required dependency makes the gate skip or fail by
configuration. The trailer gate is exercised in both directions on four bodies:
it fires on a real trailer, and stays silent with no trailer, with the string
mid-line, and with an empty trailer.

WHICH BACKEND SERVES WHICH CORNER, measured on all eight rather than inferred:
x86_64-linux Debug is stage2_x86_64; x86_64-linux ReleaseSafe, x86_64-windows
in both modes, and both aarch64 targets in both modes are stage2_llvm. Exactly
ONE corner of the eight uses the self-hosted backend, and it is the cell the
'*m' failure appeared on and no other. It is also the cell witness generation
runs on, so the cross-mode check has teeth precisely where it is armed.

RECLASSIFIED: the cross-mode check MEASURES and REPORTS, it does not fail the
job. C1.1 level 1 holds at identical BUILD, and Debug and ReleaseSafe are two
builds, so a disagreement does not breach it. What it would breach is the two
frozen keys carried by precision alone — a design question and a STOP, not
something a CI job may decide.

The reference window mode is NAMED, ReleaseSafe, and written into
PROVENANCE.txt with the run URL, the cell, the CPU pinning and the cross-mode
result. Without naming it a later regeneration could switch mode in silence.

nightly-fuzz.yml corrected: its claim that workflow_dispatch could be triggered
from a branch before merge is measured false, dormant since M0.7.

Witness-regen: M1.1.14 Gate C, first generation of the committed witness set
The gate ran, skipped in green, and had never read the commit it claimed to
read. On a pull_request event actions/checkout defaults to refs/pull/N/merge,
a synthetic commit whose message is "Merge <head> into <base>", so
git log -1 --pretty=%B read text GitHub generates and never the text the author
wrote. The gate could not fire at all, and it failed the most expensive way
available: silently, by skipping, on a job reporting success.

The second consequence is worse and would have survived every green run.
github.sha on that event is the SAME merge commit, an ephemeral object outside
the branch history that no later reader can resolve — so PROVENANCE.txt, whose
entire purpose is provenance and which is the whole argument for preferring a
commit trailer to a dispatch input, would have recorded a sha nobody can check
out. The mechanism chosen for traceability was quietly writing an untraceable
reference.

Both are fixed by pinning ref: github.event.pull_request.head.sha on the
checkout and recording that sha. It is also the right tree to generate from: a
witness pins the reviewed commit, not a merge preview of it that ceases to
exist.

The failure MODE is closed too, not just the instance. "No trailer" and "wrong
commit" were the same observable, which is why the skip looked legitimate. The
step now asserts git rev-parse HEAD against the PR head sha and FAILS on
mismatch, so only a genuine absence can skip. Exercised in six directions, two
new: the guard fires on a wrong checkout even with a real trailer present — the
precise case that was skipping — and a synthetic merge message skips.

Witness-regen: M1.1.14 Gate C, first generation of the committed witness set
The assembled witness set collapses the two mode-independent kinds onto their
ReleaseSafe copy, so when the cross-mode measurement reports a DISAGREEMENT the
artifact no longer contains the disagreeing pair — the one thing needed to
characterise it.

Measured the hard way rather than foreseen: the first generation reported
reference-window-f32 differing between Debug and ReleaseSafe and left nothing
to diff. The continuous chains are keyed by mode and survived, which is how the
divergence could be located at all — frame 1 at f32, with f64 bit-identical
across all 1000 frames — but the poses themselves were gone.

A measurement that reports a difference must retain both sides of it. The raw
per-mode outputs now upload whole, alongside the assembled set.

Witness-regen: M1.1.14 Gate C, retain the per-mode outputs to characterise the f32 cross-mode divergence
The cross-mode measurement fired. The cause is an ARCH-031 rule 3 violation at
the most-used float operation in the engine, established statically rather than
inferred from the divergence.

Vec.dot and Vec.lengthSq are @reduce(.Add, ...), whose summation order is
backend-defined — the language does not specify it. Disassembled at baseline,
x86_64-linux, 3-lane f32: LLVM folds (p0+p1)+p2 at BOTH -O levels; the
self-hosted stage2_x86_64 folds p1+(p2+p0). Float addition is not associative
and the two orders disagree on 31.4% of a million random f32 triples, by 1 ULP.
At f64 both backends produce (p0+p1)+p2, which is why f64 matched over all 1000
frames — the asymmetry is accounted for, mechanism included.

Measured: f32 continuous chain diverges at frame 1 and never re-converges;
reference window differs at frame 1 in 38 of 84 scalars and amplifies to
2.53e-3 m by frame 38. Both discrete traces are identical at both precisions,
so the divergence is numeric and never decisional.

This refutes a Gate A finding I signed off, and that is the more important half.
Recon read ONE backend's listing and generalised it to the target, then built
the criterion "the sequence is ordered" on it. Ordered is not the property:
p1+(p2+p0) is ordered, is not a total reduction, and breaks rule 3 just as a
haddps would. The property is the SAME order across backends, which @reduce
does not give. The line is struck through in place with its refutation.

STOP per the Gate C arbitration. Fixing it is a third behavioural change where
Scope names two as the complete list, and it lands in foundation/math on the
hottest path in the engine. The witness set is NOT committed: both
mode-independent kinds are keyed by precision alone, which asserts the
mode-independence the measurement just refuted.
ARCH-031 rule 3 fixes the reduction order of every float reduction on a compared
path, and @reduce does not carry it today. The class is swept, not the list.

THE CLASS. All 44 @reduce sites classified by element type: 26 boolean and 2
integer are exact under any order and untouched; EIGHTEEN were float — twelve in
production, six in tests and a bench. The recon note's "exactly four sites" was
right about .Add and blind to .Min/.Max, which carry the same order question
plus NaN propagation. Sweeping the list would have left fourteen.

New foundation/math/reduce.zig: foldAdd/foldMul/foldMax/foldMin, ascending lane
order, left-associated, float-only by @CompileError. Five sites needed no helper
— they were @reduce(.Max, @abs(v.data)), which IS Vec.maxAbsComponent.

THE .strict FACT INVERTS THE BLOCKER REPORT. B2 asserted the language specifies
no order. Measured FALSE: the langref calls @reduce "a sequential horizontal
reduction" and states that on floats "the operation associativity is preserved,
unless the float mode is set to Optimized". So LLVM honours the specification and
stage2_x86_64 does not — p1+(p2+p0) is neither sequential nor
associativity-preserving, emitted identically under the default float mode and an
explicit .strict, at Debug, ReleaseSafe and ReleaseFast. A Zig compiler defect,
owed upstream; reproducer written and self-verified under briefs/artifacts, NOT
filed. Read at tag 0.15.2 — ziglang/zig has no public 0.16.0 tag.

The fold ships regardless: bit-exactness must not rest on a compiler fix,
including one that arrives.

THE FREE REGRESSION, MEASURED. aarch64-macOS runs LLVM in both modes, so all four
local corners are LLVM corners: a worktree at 4e3551c and the working tree
produced 12 witness files, 12 identical, 0 moved, with a control proving cmp
discriminates them. At assembly level the x86_64 LLVM listing is
instruction-for-instruction unchanged; AArch64 still reaches faddp, a pair
instruction REALISING the source fold; and the self-hosted x86_64 backend now
emits acc=v0, addss v1, addss v2 — the divergent corner fixed, proven statically.

THE GUARD. no_float_reduce fails zig build lint on @reduce with .Add/.Mul/.Min/
.Max. The escape is a per-SITE WELD_INTEGER_LANES marker, not a path allowlist,
which would exempt a whole file including next year's float reduction.

THE GUARD'S OWN TESTS DID NOT RUN. tools/weld_lint had no test target: every test
block under it was dead text. Rooting addTest at main.zig ran nothing; rooting at
a file that pub const-re-exports the rules ran nothing either — "All 0 tests
passed" over nine files, one holding eight tests, which refutes the standing note
that pub const @import collects tests at Zig 0.16. comptime { _ = @import(...) }
works. Each step was settled by appending a failing test and watching for red.

Then one of my own tests failed: its premise was wrong, but it exposed a real
leak — a trailing marker exempted the statement below it. The line above now
exempts only when it is a pure comment line.

Local: lint clean, 268/268 steps and 1754/1772 tests, 541/541 forge at all four
corners.
pub const @import does not collect tests at Zig 0.16. Established on weld_lint,
and nothing said that module was the only one wired that way. Measured across
the tree: 1871 test blocks in source against 1772 collected, a delta of 99.

THE LANGUAGE FACT, settled by a neutral four-case experiment rather than by
either codebase, because two in-repo sources contradicted each other on it —
src/etch/root.zig states the correct rule in one paragraph and its opposite
twenty lines below. A root importing a two-test leaf collects 0 tests via
pub const alone, 0 with a test of its own, 0 unreferenced, and 2 via
comptime { _ = leaf; }. The false note's OBSERVATION was sound — types.zig is
also reached through interp.zig — but it called the wire-in "not load-bearing",
and acting on that would have darkened 152 test blocks in silence.

INVENTORY, each line by counter-factual or per-target reconciliation:
render 45, etch/zig_codegen 37, tools/bindgen 8, math/exact.zig 2,
modules/audio 1, core/ipc/shm_posix.zig 1.

zig_codegen/root.zig already carried the CORRECT guard for its own test files.
What was broken was the link to it: the right mechanism existed one level down
and the level above used the form that never reaches it.

REPAIRED AND RUNNING: +12, 1772 -> 1784, all green. exact.zig's two tests guard
the exact integer arithmetic M1.1.11.1 spent eleven rounds establishing.
Bindgen needed a tests.zig root — the target rooted at main.zig moved the total
by ZERO, the same trap a third time, caught only because the number was checked
instead of the target trusted; its emitter test then failed to compile
(ArrayList.writer removed at 0.16) and was migrated to Io.Writer.Allocating.

TWO AREAS HELD, with their motive in build.zig and src/etch/root.zig, because
what they uncover is bigger than the dead tests:

- zig_codegen: cache.zig does NOT COMPILE under the pinned toolchain.
  std.fs.cwd() was removed at 0.16, so writeHash, readCachedHash and
  root.writeFileAndCache have been dead code since the pin — and root.zig
  already documents cookTree as having no in-tree consumer. Repair is not a
  rename: the 0.16 filesystem API takes an io parameter these functions do not
  have, so it changes the codegen cache's public signatures.

- render: a CONFIRMED USE-AFTER-RETURN, live since M0.4. buildPass returns a
  Pass whose writes slice points at a literal in its own stack frame. Measured:
  writes.ptr is a stack address, depth_attachment reads false immediately after
  the call, and a fresh call at the SAME address reads true. forward.zig fails
  identically.

Also: the upstream issue draft, with the toolchain settled — ziglang/zig has no
0.16.0 tag and its newest release is 0.15.1, so the report goes against master;
the langref citation is levelled onto master, where it is verbatim identical.

The floor is re-stated on the new denominator: 1784 collected, 1766 passing,
18 skipped, with 82 blocks named and held. Any comparison against a
pre-M1.1.14 total compares two different denominators.
Wiring render's 45 never-run tests collects them with ZERO compilation errors
and exactly 2 failures — the two already measured. Measured before branching,
since an uncompiled module can hide anything.

BRANCH A: the storage is bounded in place and no ownership question arises.
Config is ALREADY the pass's ctx, so it had to outlive the Pass by
construction. A fixed [1]ResourceUsage per slice, filled by buildPass, costs one
field and one *const Config -> *Config. The only call sites in the tree are the
three tests themselves — the passes have no production consumer yet — so the
change is local and the defect was latent rather than live.

capture.zig carried the SAME use-after-return with a test that never checked
content, so it passed. Found by reading the other two files rather than
assuming two failures meant two defects.

Pinned structurally, not only by the restored assertions: the tests now assert
that reads.ptr and writes.ptr point INTO the config. Counter-factual run —
restoring the stack literal in depth_prepass turns the suite red again.

Also journals the five blocks the previous summary did not account for. Four
were a unit error in that summary: it published render 45, a COLLECTED count,
while the delta was computed against render 49, a SOURCE count. The gap is
gal/vulkan/conv.zig, platform-conditional on macOS where the GAL selects Null.
The fifth is not located; its bound is recorded, along with two failed
localisation attempts, one of which produced an invalid pairing that was
discarded rather than reported.

Suite: 274/274 steps, 1811/1829 tests, 18 skipped, zero failures.
The dead-test wire-in from the sweep was UNCONDITIONAL. shm_posix.zig opens
with a @CompileError for any OS but Linux and macOS, so it broke every Windows
build — and ubuntu-24.04/Debug with them, because the failing step is
forge-asm-inventory, which cross-compiles to x86_64-windows-gnu from any host.
Three cells red on one line of mine.

zig build test on a POSIX host could not have shown it. The CI cell runs five
steps; the local suite runs one, and two of the other four cross-compile. A
green local suite is a smaller claim than a green cell — the same unit
confusion as comparing a collected count to a source count. All five steps now
run locally before a push, and all five are green.

Fixed by mirroring shm.zig's own comptime dispatch rather than inventing a
second condition. Two slips inside the fix were caught by the tools rather than
by re-reading: the builtin import first landed inside a comptime block instead
of container scope, then between a doc comment and its declaration, which the
doc_comments rule flagged.
Every in-tree file holding a test block must belong to the analysis closure of
some test target, or be a declared exclusion. The analyser builds nothing and
runs nothing: M1.1.14 hunted dead tests three times with three methods and two
failed, one on a ten-minute budget and one on an invalid pairing.

It derives its roots from build.zig instead of duplicating them — a hand-kept
list drifts the first time a target is added, and drift here reads as "no dead
tests". It declares its exclusions with their owning milestone, which is what
separates a known debt from a hidden one. And it reports the SIZE of what it
judged, because it exists precisely because probes rendered verdicts over
objects they had not measured.

Sixteen tests, including the four hostile fixtures: alive at depth three, dead
when bound-but-unreferenced, dead on an inline field access, alive on a comptime
guard. One caught a defect in the guard itself — a both-ends trim had eaten the
trailing space of "_ = ", so the comptime fixture reported its pinned file dead.

NOT WIRED INTO zig build lint, and the header says why. The first run against
the real tree TESTED the criterion and the criterion survived: render/root.zig
binds with bare pub const and its 45 tests are collected, which looked like a
refutation until the file was read — it carries a comptime reference guard, so
the names ARE referenced. What the run refuted is this implementation: it misses
every target built by the test_specs loop, and its inline-field-access rule is
too strong, since pub const Graph = @import("graph.zig").Graph does pull that
file's tests when Graph is later referenced. Both push toward false DEAD, the
direction chosen on purpose. A guard nobody can believe is worse than none.

Also adds --summary all to the CI test step: the per-cell totals were never
printed, so the platform-dependent floor could not be read at all.
guysenpai and others added 26 commits August 17, 2026 13:13
P1-3 left behind a defect of this milestone's own dominant family, and an
adversarial pass over the 12-cell verdict found it.

`install()`'s doc comment read "Called by Tier 0 only, at exactly three
sites, and a fourth one inside a module is a defect rather than an
extension", then listed three. P1-3 brought the set to ten, three of them
inside modules — the shader watcher's thread, the determinism entry, and
the determinism module's own save/restore test. The text was false twice:
the count, and the rule it stated.

The enumeration is DELETED rather than updated, and the predicate stated
in its place: every thread start, every process entry, wherever it lives.
Bumping three to ten would only reset the clock on the same defect — a
count in prose drifts exactly as the corpus's own enumeration of this
rule's sites drifted, which is what P1-3 measured. The set now lives in
the brief's Closing notes WITH ITS DERIVATION RECIPE, because a reader who
can re-derive does not have to trust.

Swept as a class rather than fixed as an instance, per M1.1.11.1's
standing lesson. One grep over src, briefs, CLAUDE.md and .github for
every prose claim about the number or scope of install sites found four:
the `install()` doc, the same file's header ("One definition, three
callers"), `forge_3d/determinism.zig:11`, and two brief entries — one of
them the §4 claim Guy has already ruled must carry no count. Each
corrected in place with its refutation, never replaced silently.

Also journalled: the bit-neutrality verdict. 12 of 12 cells green, and the
deduction behind it is stronger than the bytes. At 9e23dd9 nothing
installed on the witness path, yet `assertFloatEnvironment` — live in
Debug AND ReleaseSafe — passed on all 12, and its reader is witnessed on
both ISAs by per-field perturbation tests that a decode table copied
across architectures would fail. So the inherited state equalled
`engine_default` everywhere, and installing it into a state already equal
to it is a bit-level no-op. The byte comparison corroborates that end to
end on the 8 cells where level 1 applies.

One framing the pass refuted before it reached the brief: bit-neutrality
is NOT derivable from a documented ABI default. The file states `0x1F80`
only for the six exception masks, and no numeric FPCR default exists in
the tree. The evidence is empirical, and a derivation resting on a misread
comment would have been this same family one level up.

No behaviour change: doc comments and the brief only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P1-1 of the review's six findings: two halves of one defect, both in the
file that defines what the milestone measures.

The header promised "a step and a slope" and the scene held neither —
nothing stood near the character but the flat half-space, so `max_slope`
was never approached and the deterministic cosine, this milestone's first
behavioural change, was exercised by no witness. And the character reached
NO artifact: `dumpState` walks `mobile`, which holds rigid bodies, and a
virtual character owns none, so the controller ran a thousand frames and
every bit of its output was discarded.

The guard written for exactly that case could not catch it. The
`mobile.items.len == 12` assertion claimed to fail "when an element is
added without being appended to `mobile`"; the character was, and the test
stayed green because the body total was updated in the same commit. A count
pinned alongside the change it is meant to catch catches nothing.

THE DESIGN WAS MEASURED INTO EXISTENCE, and every round refuted something
already written down. Rotated boxes were abandoned after three failures —
footprint wider than half-extent, Z faces vertical so a lateral approach
never runs the slope test, and one placement that floated a wedge and
wedged the character in the crevice for 775 of 1000 frames. The scenario
was walking BELOW the threshold of its own step arm: swept, 0.03 m/tick
climbs no riser at any height, 0.15 m needs 0.06 and 0.25 m needs 0.10. A
commanded loop is not a closed loop — climbing costs progress, so the
character drifted 2.2 m per cycle, closed by geometry rather than by tuning
leg lengths against a climb. And I concluded a 63.4 degree ramp was
unclimbable-when-permitted; an isolated sweep refuted it, the real cause
being a leg length spent before the ramp.

THE BRACKET BITES BOTH WAYS, measured over 1000 frames at f32: cosine
0.9211 gives max_y 0.0063 and the character never leaves the plane, 0.7074
gives 0.9463, 0.3624 gives 2.6707 and it crests both steep ramps and
escapes the bowl. Surface cosines 0.894 and 0.6247 bracket cos(0.785) by
0.187 and 0.083, each verified against an independent computation. Shipped
as a test; the flattened-ramp counter-factual fails both new tests.

Serialisation pinned by discrimination and not by a length: move only the
character and the stream must change. Removing the character block fails
it; removing only the ground verdict fails it too.

PREDICTION ENTERING THE REGENERATION, falsifiable: only the four
continuous-chain witnesses change. The terrain is static and far from every
dynamic body and the character is virtual, so nothing rigid moves — VERIFIED
locally at all four corners, four trace verdicts OK and no divergence at
both precisions, with a local generation reproducing the four ISA-independent
witnesses byte-identically. The four ARM cells should therefore stay green
through this push, their comparison never touching the chain. A discrete
witness that moves is a finding, not a re-baseline.

Witness-regen: canonical scenario extended with a riser, three mesh ramps forming a closed bowl, and the kinematic character serialised into the continuous state

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four continuous-chain witnesses only. The two discrete traces and both
reference windows are BYTE-IDENTICAL and git shows it: the terrain is
static and far from every dynamic body and the character is virtual, so
nothing rigid moved, and those four artifacts are derived from the rigid
simulation alone.

THE PREDICTION WRITTEN INTO e851ff0 HELD CELL BY CELL AND FILE BY FILE.
Predicted: the four ARM cells stay green through the stale-witness push,
the eight x86_64 cells fail on the chain, the four chain witnesses change,
the other four do not. Measured on run 32026918193: 4 of 4 ARM success,
8 of 8 x86_64 failure, 4 of 4 chain CHANGED, 4 of 4 others IDENTICAL. Had a
discrete witness moved it would have been a finding and not a re-baseline —
saying so in advance is what made the difference visible rather than
arguable afterwards.

First real use of the `Witness-regen:` mechanism, and it worked in the case
it exists for. That is what P1-4 had to fix first: before it, generation
exited 1 whenever the committed witnesses differed, which is always, when
regenerating. Cross-mode agreement re-measured and holds at both precisions
in the new set. PROVENANCE carries the motive, the generating cell, the CPU
pinning, the reported zig version, the per-file mode, the head sha and the
run URL; SHA256SUMS verified against the committed bytes, 8 of 8 OK.

Local four corners green against the new set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P1-2. The pre-existing accuracy test compares against `@cos` and its own
comment concedes the limit: it proves the function is STABLE, it cannot
prove the function is a COSINE. Two implementations of one wrong idea
agree. So this milestone's first behavioural change — replacing `@cos` at
the `max_slope` conversion — had no pin on the value it produces.

THE ORACLE IS EXTERNAL AND ARBITRARY-PRECISION: pi to 80 digits by an
alternating arctan series in exact decimal arithmetic, no floating point
anywhere on the value path, computed TWICE by different Machin-like
formulas and required to agree to 70 digits. Each argument is taken as its
exact binary value — the f32 row is the cosine of the f32-ROUNDING of the
literal, not of the literal — and each result is rounded by comparing both
neighbours explicitly, a single conversion inheriting its own rounding and
a Decimal to f64 to f32 path being able to double-round. The tool is not in
the repository and the recipe is, the same arbitrage as the
float-environment site list and for the same reason.

TWO COLUMNS, TWO CLAIMS, said plainly because conflating them is this
milestone's own defect family. `oracle` is the correctly-rounded true
cosine and carries CORRECTNESS. `engine` is what the implementation emits
and carries REPRODUCIBILITY across the twelve cells; it is self-generated
and validates nothing about accuracy.

Measured, and one result is stronger than expected: at f32 the
implementation is correctly rounded on all twelve arguments, bit for bit,
so the oracle column doubles as the reproducibility column there. At f64
nine of twelve agree exactly and the worst absolute error is 3.14 eps, at
the largest integer under `max_argument`.

AND THE BOUND IS ABSOLUTE, NOT IN ULP, which is a measurement and not a
preference. At the f64 nearest pi/2 the true cosine is 6.12e-17 — near-total
cancellation — and the error there is 1.6e11 ULP of that value while being
2.0e-21 in absolute terms, the smallest absolute error in the table. A ULP
bound would fail spectacularly on the most accurate row. Cosine is bounded
by one, so an absolute bound is its natural accuracy statement. Budget 4
eps against a measured 3.14: 78% used.

Anti-vacuity asserted, not assumed, and written as a property of the table
rather than a count so a new row cannot silently make it vacuous. Four
counter-factuals run; the fourth was mis-designed at first and the lesson
is recorded — changing the `engine` column to create vacuity trips
REPRODUCIBILITY first and never reaches the clause under test. A
counter-factual must perturb what the targeted guard judges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P1-5 and P2-6, the last two of the review's six.

P1-5. `trace.zig` states the hazard on itself: over a set that can only
GROW, a determinism trace agrees with itself by accumulation and proves
nothing. The harness was pruned earlier in this milestone so that stops
being true, and `solver_test.zig` has a generic departure test — but the
pruning being IMPLEMENTED and the canonical scenario EXERCISING it are two
claims, and only the first was established.

Measured: exactly one removal in 1000 frames, at frame 196, and it is the
static MeshShape against the frictionless sphere crossing it. The sphere
carries a fixed 3 m/s, leaves the mesh at x = 65 around frame 180, and its
fat AABB separates around 196 — permanently. The event is a consequence of
element 5's design and cannot quietly stop happening while that element
still does what it is for.

THE ASSERTION IS ON THE SET AND NEVER ON ITS CARDINALITY, which is measured
rather than stylistic. The removal is followed by an addition as the same
sphere reaches the ground plane, so the size returns to what it was. My
first probe was size-based and saw the dip ONLY because the two events land
on different ticks; had they coincided it would have reported nothing while
the removal happened. The test also names the pair — built from the live
handles, a BodyId being generational and not a slot number — because
otherwise a removal caused by anything at all would satisfy it.

P2-6. `divergenceFrame` carried `if (off + stride > reference.len) return
null;`, so an empty, truncated or wrong-precision reference returned null —
which the entry point prints as "divergence frame : none within K=60".
Absent data read as a perfect match, in the function that produces this
milestone's level-2-point-2 number.

Two outcomes now separated: null means measured and no divergence, an error
means the input was not a window and no measurement was taken. The length
is required exactly, which also catches a reference of the other precision
since the f64 window is twice the f32 one and neither length divides the
other's; a zero stride is a distinct error because 0 * window_frames == 0
would let an empty reference pass the length test as a match. The in-loop
bounds test is REMOVED rather than kept: with the entry check it cannot
fire, and a guard whose only reachable branch is the one nobody wanted is
what let the silent null live.

Both counter-factuals run. Neutralising `pairStillOverlaps` fails the
generic test and the new one; restoring the silent early-return fails the
window test. Real path re-verified at four corners, all four still
measuring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gate F, replayed in full after the review. Documentation only.

CLAUDE.md. The milestone row said "code-complete" and now says what is
true: PR open, NOT closed, six defects found by review and corrected, the
witnesses re-baselined. The tag row is rewritten on what was MEASURED
rather than on what was planned — the six corrections, the bit-neutrality
deduction, the scenario that had neither a step nor a slope, the cosine
oracle, and the absolute-not-ULP bound. State rows corrected to 552 forge
blocks and 1866/1864 per platform.

FOUR PLAN ENTRIES, each with its owner or its explicit absence.

M1.D.11, a lint rule for ARCH-031 rule 5's site set: mechanical, and
deliberately NOT built here by Guy's ruling. Until it exists a new thread
or a new `main` can arrive with no installation and nothing says so.

The environment assertion's WRAPPER is unwitnessed: every counter-factual
perturbs the register and checks the predicate, none observes the panic. A
sign-inverted mutant would be caught; only the always-true mutant survives.
Found by the adversarial pass over the bit-neutrality verdict.

GroundState is constant on the canonical scenario, so the verdict field
carries no discrimination there and the position does. Recorded so nobody
reads its presence as coverage of the `.on_steep_ground` and `.in_air` arms.

Six lines of French prose in ci.yml, PRE-EXISTING: established by git blame
per line plus an is-ancestor check, not inferred from the code's apparent
age. Outside the closed correction list, so recorded rather than fixed.

CLOSING LANGUAGE AUDIT, and the first attempt had the wrong SCOPE. Run over
the whole tree it returned 390 accented lines, most of them closed brief
records that are not retro-patched. Re-scoped to the 71 files changed
against main: clean. Every accented line is a verbatim spec citation, a
proper name, an English loanword, or `Étape`. Run with a Python regex and
never a grep bracket class, the byte-wise class reporting a clean tree over
files that demonstrably contain French.

The PR body is rewritten on the closing state and now DECLARES the witness
regeneration with its motive — the contract requirement the Gate A body did
not carry. The PR stays a draft; marking it ready, the merge, the squash
message and the tag annotation are not mine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documentation only, and it corrects two things I had asserted.

A DOCS-ONLY COMMIT DOES NOT SKIP THE HEAVY CELLS, and the reason is
structural rather than a bug. On a `pull_request` event the `changes` job
compares the PR BASE — main — against the head, not the previous push, so
the diff it classifies is the whole PR. A branch that touches code runs the
full matrix on every push whatever the individual commit changed. That is
correct for a merge gate, since the merge lands the whole diff, and the
consequence is welcome: the head is verified rather than assumed. My
prediction that the cells would skip was a misreading of the mechanism.

THE ONE FAILURE ON THE HEAD WAS A THIRD DISTINCT INFRASTRUCTURE CAUSE,
neither the recorded Windows hang nor a cancellation. `ubuntu-24.04 / Debug
/ f32` died in `Set up job` on 429 Too Many Requests fetching the
weldengine/setup-zig action archive: a 36-line log, zero assertions, before
a single line of Weld compiled. Partly self-inflicted — five pushes and two
job re-runs inside an hour, times a 12-wide matrix each fetching that
archive. Re-run, and the head is now 12 of 12 on cells as well.

The discriminator is the same for all three and is stated once in the
brief: grep for TestExpected / assertion failed / panic: and read the Build
Summary counts. Zero failed assertions plus a reconciling count means the
code did not answer wrongly — something stopped it, or never started it.
Three different things stopped it today, and each would read as a code
failure to anyone judging on the exit code alone.

The verdict of record for the corrected milestone stays `d627b89`: 12 of 12,
run conclusion success, with the previously-hung cell verified to have
actually compared the chain rather than merely passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P2-7, and it is a P1. The conservation `live_tests = collected total` is
the gate that authorises the dead-test guard at all — not its twenty-one
fixtures, the conservation. IT WAS NEVER EXECUTED. `weld_lint` computed it
only when handed `--expect-collected=N`, and neither build.zig nor the CI
ever passed it: the loop is guarded on `argv_extra`. The tool printed
"expected collected" and then printed "clean", having compared its
expectation to nothing.

And the number was right BY ACCIDENT. `closure 1864 - 5 = 1859` is
arithmetic on the closure and the declared gap; it never touched the suite.
1859 was indeed the collected total and nothing compared them. A verdict was
read where there was only a display, three times.

Fourth instance of "a control that exists and a path bypasses" — after the
lint step in no workflow, the witnesses with no reader, and the cache save
outside its own size guard — this one inside the tool built against that
family.

TWO LAYERS, AND ONLY THE SECOND IS OPTIONAL, because an optional check is
the defect itself.

One, unconditional, no flag to forget: `expectedCollectedOn(os)` declares
the per-platform total beside `uncollected`, and the tool confronts
`live_tests - gap` against it on every invocation. Its failure message
forbids the obvious wrong repair — bumping the declared number to match a
drifted closure is what turns the control into arithmetic on itself.

Two, suite-derived, from CI: the `zig build test` step captures the
collected total from the summary of the invocation that ACTUALLY ran the
tests, by `tee` rather than a re-run, and a new step passes it as
`-Dexpect-collected`. That is what stops the declared number from being
aligned to a drifted closure. Absent or unparsable is a FAILURE and not a
skip, and the exit code is read through PIPESTATUS since a pipeline's status
is the last command's and this repository has pushed a red build under a
green self-report exactly that way.

build.zig forwards the option when given, and it is optional there for a
reason stated at the site: `zig build lint` cannot run the suite to learn
the figure, and an option silently defaulting to the closure's own
arithmetic would be the defect in a new costume.

Measured: closure 1871 - 5 = 1866 on macOS against a suite reporting
1847/1866. The conservation was true all along; nobody was checking it.
Both layers counter-factualled locally — a declared 1867 gives CONSERVATION
FAILED, `-Dexpect-collected=1865` gives CONTROL FAILED. The cell-level
counter-factual is a separate deliberately red push.

Also in this commit: P1-1's scope probe now asserts that the character
REACHES each obstacle, not that the obstacle exists. One clause of three was
satisfiable by absence — `max_x < -60` holds for a character that never came
near the steep ramp — and the position bands are replaced by the contact
itself, `moveCharacter`'s `ground.body`, whose result the scenario had been
discarding. Three counter-factuals, one per clause, each firing on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first wiring put the step on ubuntu-24.04/Debug, copying the arbitration
written for `zig build lint` — one cell, because the lint RULE pass answers a
property of the SOURCE. The conservation is not that kind of quantity.

The collected total is PER PLATFORM: 1864 on Windows against 1866 elsewhere,
the two `only_on = .windows` entries of `uncollected`. On one cell the branch
`expectedCollectedOn(.windows)` was therefore a declared number that NOTHING
confronted — the exact shape P2-7 exists to remove, reproduced inside P2-7's
own fix. Found by reading the cell log's `1866` and asking who checks 1864.

The step is a source scan plus one small build, so paying it twelve times is
cheap against a per-platform number left unchecked on two platforms of three.

Positive witness recorded before any deliberate red: on the cell, `suite
reported 1866 collected tests`, then `conservation OK` inside the lint step,
then `control OK — closure and suite agree at 1866` in the new step. Both
layers execute on a real cell, and the CI parser works on a real Linux
summary rather than only on a fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REVERTED BY THE VERY NEXT COMMIT. Do not merge, do not build on. This commit
exists to make the twelve-cell matrix FAIL, and its failure is the
deliverable.

WHY IT IS NOT OPTIONAL. Without it the evidence reads "the conservation step
executes on the cell and prints OK", and a step that prints OK whatever it is
given is indistinguishable, on a green run, from a step that works. That is
exactly what P2-7 was: a control that existed, displayed, and confronted
nothing. `ci.yml` already records that the local counter-factual which
authorised the lint step proved only that the LOCAL invocation reddens. This
does not replay that authorisation on the tool built against it.

WHY THE FAULT IS ON THE CI-PASSED NUMBER AND NOT ON THE DECLARATION. Faulting
`expectedCollectedOn` was the first attempt and the `pre-commit` hook REFUSED
the commit — `dead-tests` runs there, so layer one is already witnessed on the
hook as well as locally, and the fault could not be committed without
`--no-verify`, which is never taken. Moving the fault to the value CI passes
is better on its own terms: layer two is the half that was missing, sitting
behind a flag neither build.zig nor the CI ever passed, so the path under test
is exactly the one P2-7 exists to wire.

The fault is `+ 1` on `COLLECTED_TESTS`, one line, nothing else.

PREDICTION, written before the push:
  - all twelve cells go RED in the conservation step, with `CONTROL FAILED —
    expected N collected, zig build test reported N+1`;
  - the numbers DIFFER BY PLATFORM from one faulted expression: 1866 against
    1867 on ubuntu-24.04 and ubuntu-24.04-arm, 1864 against 1865 on
    windows-2025. That is what shows the tool compares against its OWN
    per-platform expectation rather than echoing what it was handed.
  - layer one still prints `conservation OK`, the tree being untouched, so the
    two layers are visibly independent in the same log.
If a cell stays green, the passed value does not reach the tool. If every cell
reports the same pair of numbers, the per-platform branch is not read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverts d237f82, whose only content was `+ 1` on the collected total the CI
hands to the dead-test conservation. The red it produced is evidence P2-7
could not get any other way: a step that prints OK whatever it is given is
indistinguishable, on a green run, from a step that works.

MEASURED, from the cell logs and not from intent.

Green run 32041901826, honest total: 8 cells reached the conservation and 8
printed `control OK`; `windows-2025 / Debug / f64` printed `suite reported
1864` then `control OK at 1864`, which is the positive witness for the
per-platform branch the earlier one-cell wiring left unconfronted.

Red run 32044416532: 12 of 12 cells FAILED. On the 8 non-Windows cells the
prediction held at the character, and the two layers are visibly independent
in one log — `suite reported 1866`, then `conservation OK … agree at 1866`
with the tree untouched, then `CONTROL FAILED — expected 1866 collected, zig
build test reported 1867`.

THE WINDOWS HALF OF THE PREDICTION WAS WRONG, and the cause is my
counter-factual, not the shipped wiring. I predicted 1864 against 1865. The
step carried no `shell:`, so on windows-2025 it ran under pwsh, where
`$(( 1864 + 1 ))` becomes `$ 1865` — two tokens — and zig build answered
`Expected -Dexpect-collected to be an integer of type usize`. The three
Windows cells that reached the step failed on ARGUMENT PARSING and not on the
conservation. The negative witness on Windows is NOT established; the positive
one is.

So the fault did find something about the shipped step: it was shell-dependent
by omission. Harmless for a bare substitution, a trap for whoever next adds
shell syntax there. `shell: bash` is declared here, uniform across the twelve,
which also makes a Windows negative witness obtainable if one is wanted.

VERIFIED ON THE CELL LOG AND NOT ON THE DIFF, which is the check a revert
actually needs — a revert that restores incompletely is the obvious failure
class, and the diff cannot see it if the declared number and the passed number
move together. The pair of NUMBERS is what this run's own logs must read back:
1866 with `control OK at 1866` on ubuntu-24.04 and ubuntu-24.04-arm, 1864 with
`control OK at 1864` on windows-2025.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The closing measurements, none of them inferred.

FINAL HEAD a4f6e82, run 32048243472, 12 of 12 cells success, and the
`forge-determinism` step present on 12 of 12 by ENUMERATION over each job's own
step list rather than inferred from the matrix.

THE REVERT IS VERIFIED ON THE CELL LOG AND NOT ON THE DIFF, which is the check
this shape of revert needs: one restoring incompletely — the declared number
and the passed number moving together — would go green again and the diff could
not see it. What is read back is the pair of NUMBERS, per cell, and it differs
by platform: 1866 with `control OK at 1866` on the eight non-Windows cells,
1864 with `control OK at 1864` on the four Windows ones. That settles both open
points at once — the revert is complete, and `expectedCollectedOn(.windows)` is
confronted on all four Windows cells, a branch nothing confronted until the
hole inside P2-7's own fix was found.

The complete P2-7 evidence is tabulated in the brief in the order obtained,
because no single witness suffices: local layer one, local layer two, the
pre-commit hook refusing the first fault, the green cells, and the red run
32044416532 with `CONTROL FAILED` in eight cell logs while layer one printed OK
in the same log. The red is the one that cannot be substituted, and its absence
is exactly what P2-7 was.

Bit-neutrality moves to the Closing notes on its three identifiers rather than
as a characterisation: head 2c29c7c, run 32019448000, and not one of the eight
witness files present in the diff 9e23dd9..2c29c7c. The window closed at
e851ff0. This brief had once named d627b89 for it, which was the wrong SHA —
it carries P1-1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REVERTED BY THE VERY NEXT COMMIT. Do not merge, do not build on. The failure
is the deliverable.

THE HYPOTHESIS BEING KILLED is not "the step runs" — the positive witness
settled that. It is "the comparison is TAUTOLOGICAL on Windows": both halves
drawn from one source, printing exactly `1864` / `control OK at 1864` and
staying green whatever happens. That is the P2-7 class itself, and a green run
cannot distinguish it from a working control.

THE FAULT IS ON THE DECLARED WINDOWS CONSTANT, AT ITS SITE, AND NOTHING ELSE.
`expectedCollectedOn(.windows)` 1864 -> 1865. Not the passed argument, not the
shell, not a shared value. Exact complement of d237f82, which reddened the
eight non-Windows cells.

IT IS INVISIBLE FROM macOS, which is the point and also why it went
unconfronted for so long: `zig build dead-tests` on this host reads the `else`
branch and prints `conservation OK … agree at 1866`, so the pre-commit hook —
which does run dead-tests, and which REFUSED the first fault attempt — lets
this one through.

EXPECTED, written before the push:
  - the 4 windows-2025 cells go RED in the conservation step, printing BOTH
    numbers: `CONSERVATION FAILED — closure gives 1864 expected collected,
    expectedCollectedOn(windows) declares 1865`;
  - the 8 non-Windows cells stay GREEN with 1866 on both halves.
Any other pattern is a result, not a miss: 12 red means the constant is not
per-platform; 12 green means the guard judges nothing on Windows and the P2-7
fix must be reopened.

ONE CORRECTION TO THE EXPECTED MESSAGE NAME. The layer that fires is layer ONE
— unconditional, closure against declared constant — which short-circuits
before layer two, so the line reads `CONSERVATION FAILED` and not `CONTROL
FAILED`. Both numbers are in it, which is what the check needs.

WHAT THE UNION OF THE TWO NEGATIVE WITNESSES COVERS, stated exactly rather
than rounded up: d237f82 killed the tautology of layer TWO on the eight
non-Windows cells; this one kills the tautology of layer ONE on the four
Windows cells. Together: both layers, all twelve cells, and no cross-platform
contamination in either run. NOT covered: layer two on Windows and layer one
on non-Windows, each of which has a positive witness only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverts 8116831, whose only content was `expectedCollectedOn(.windows)`
1864 -> 1865. Its red is the second negative witness, and the exact
complement of d237f82.

WHAT IT KILLED. Not "the step runs on Windows" — the positive witness had
that. The hypothesis was that the comparison is TAUTOLOGICAL there: both
halves from one source, printing `1864` / `control OK at 1864` and staying
green whatever happens, which is the P2-7 class itself and which a green run
cannot distinguish from a working control.

MEASURED on red run 32063230404, the expected pattern exactly:
  - the 4 windows-2025 cells RED, every one on the step named `dead-test
    conservation against the suite's own total`, every one printing BOTH
    numbers: `CONSERVATION FAILED — closure gives 1864 expected collected,
    `expectedCollectedOn(windows)` declares 1865`;
  - the 8 non-Windows cells GREEN, `conservation OK … agree at 1866` and
    `control OK … agree at 1866`.
Not infrastructure — the failing step is named on all four, and the
discriminator was applied before reading anything into it.

The layer that fired is layer ONE, unconditional, which short-circuits before
layer two, so the line reads `CONSERVATION FAILED` and not `CONTROL FAILED`.
Both numbers are in it, which is what the check needed.

COVERAGE OF THE TWO NEGATIVE WITNESSES, stated exactly rather than rounded up.
d237f82 killed the tautology of layer TWO on the eight non-Windows cells; this
one killed the tautology of layer ONE on the four Windows cells. Together:
both layers, all twelve cells, and no cross-platform contamination in either
run — the conservation being per platform, that non-contamination is the
property needed, not twelve reds. NOT covered: layer two on Windows and layer
one on non-Windows, each having a positive witness only.

VERIFIED ON THE CELL LOG AND NOT ON THE DIFF, which is the check a revert of
this shape needs — one restoring incompletely, both halves moving together,
would go green again and the diff could not see it. The pair of NUMBERS is what
this commit's own run must read back, per cell: 1864 with `control OK at 1864`
on the four windows-2025 cells, 1866 with `control OK at 1866` on the other
eight. Locally the constant is read back as `.windows => 1864` from the file
content and `zig build dead-tests` prints `conservation OK … agree at 1866`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closing-notes self-audit, Guy's finding, documentation only.

Announcing the first cell counter-factual I wrote that layer one,
unconditional, `CONSERVATION FAILED`, should fall on the twelve. What was
observed was `CONTROL FAILED` in EIGHT logs. Two different messages, two
different counts, and the gap went uncommented.

Two errors in that one sentence, the second being the instructive one. "On
the twelve" was already false as written: the fault then intended was on the
`else` branch, which does not cover Windows, so the correct expectation was
eight and never twelve. Then the fault CHANGED LAYER — the pre-commit hook
refused it, so it moved onto the CI-passed number — and the observed message
became `CONTROL FAILED`. I reported the change of layer and did NOT withdraw
the prediction it replaced. That is the motif this repository sweeps: a
correction applied without retracting what it supersedes, which is how the
superseded text goes on being quoted.

Layer one did not end up without a negative witness, but red #1 is not what
supplied it — red #2 did, two rounds later, on the four Windows cells. The
corrected accounting is tabulated: `CONTROL FAILED` on 8 non-Windows cells in
run 32044416532 with the CI-passed number faulted, layer two;
`CONSERVATION FAILED` on 4 Windows cells in run 32063230404 with the declared
Windows constant faulted, layer one.

Also recorded: one instruction was internally inconsistent, and the right move
was to refuse rather than to choose. It asked for `CONTROL FAILED` in a Windows
cell log while faulting the Windows branch alone; faulting the declared
constant fires layer one, which returns before layer two is reached, so no
fault on that branch can produce that message. The incompatibility was stated
and the choice handed back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three findings, in the order imposed, and the order was the point:
regeneration had to work BEFORE the scenario change made it necessary.

P1-2 — REGENERATION STILL FAILED WHERE IT SERVES. The earlier fix covered
only the case where the FORMAT is stable. The real case is structural: one
more mobile, a different `pose_stride`, and the old reference window makes
`divergenceFrame` raise `ReferenceWindowLengthMismatch` — an error, not the
`failed` boolean being neutralised. Reproduced before fixing, with one extra
scalar per body in the pose dump: rc=1, three files already on disk, dead
before the upload under `set -euo pipefail`. The generation path now writes
and RETURNS, reading no committed witness at all. Verified on the same
structural change: rc=0.

P2-3 — THE GATE CAME AFTER THE ACT IT PREVENTS. Files were written, then
`NotSelfReproducible` returned, so a non-reproducible run left a complete and
usable witness set on disk. The gate now precedes every side effect. Verified
with a genuine non-reproducibility rather than a forced boolean: rc=1, zero
files written.

P1-1 — THREE OF THE FOUR DISCRETE TRACES WERE CONSTANT IN THE COMPARED
WINDOW. The old probe searched 400 frames; the witnesses hold 60. Inside the
window actually compared, island partition, sleep state and the retained pair
set each took exactly ONE value — they agreed between x86_64 and AArch64
because they did not move.

My first measurement of that was also the wrong instrument: it counted
cardinalities and called the manifold trace constant at 11, while the
serialised segment takes 7 distinct values over the same frames. Redone on
bytes, per trace, per frame.

Scene tuned, a trace's variation being a property of the scenario and not of
the physics: groups from x=30/42 to 35/38, mesh sphere from 3 to 12 m/s, and
a lone box at x=15 that settles at once and sleeps early. First movement went
from 71/70/196 to 30/29/49, and the four traces now take 2/2/12/2 distinct
values inside the window.

Shipped as a test on `run.window_frames`, asserted per trace and not as an
aggregate — an aggregate is satisfied by one trace moving while three stand
still. Counter-factuals run; one of mine was vacuous and is reported as such
in the brief.

The conservation wired yesterday stopped this commit on its first real use:
the closure went to 1867 against a declared 1866. Reconciled against `zig
build test --summary all`, which reports 1867, and not against the closure's
own arithmetic, which is the repair its message forbids.

Witness-regen: canonical scenario tuned so all four discrete traces vary inside the 60-frame window the witnesses cover, plus a lone early sleeper

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NOT AUTHORITATIVE. Read `PROVENANCE.txt`: these eight files were generated on
aarch64-macOS and two of the three kinds are WRONG FOR THEIR PURPOSE — the
chain is intra-ISA and belongs to x86_64, and the reference window is the
baseline the ARM cell measures its divergence AGAINST, so an aarch64 window
makes that metric meaningless. The `witness-generation` job replaces all eight.

WHY THIS COMMIT EXISTS AT ALL. The scenario change makes the two witness
comparison tests genuinely red, so `zig build test` fails, so the `pre-push`
hook refuses the push — and the authoritative regeneration only runs in CI,
which needs the push. `--no-verify` is never taken. The way out is an
intermediate state that is green locally and honest about being provisional.

The previous regeneration did not hit this: at that point the scenario change
left the discrete traces byte-identical, so the tests stayed green. This one
changes them deliberately, which is the whole point of P1-1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `pre-push` hook runs `zig build test`. A scenario change that MOVES the
discrete traces makes the two witness-comparison tests genuinely red, so the
hook refuses the push — while the authoritative regeneration runs only in CI,
which needs that push. `--no-verify` is never taken.

The previous regeneration slipped past this by accident: its scenario change
left the discrete traces byte-identical, so the tests stayed green. This one
moves them deliberately, so the deadlock is structural and recurs on every
trace-moving change.

And the trailer had to be re-carried, which is a second edge of the same
mechanism: `witness-generation` reads `Witness-regen:` from the HEAD commit
message, and the intermediate-witness commit displaced the one that carried
it. A trailer on an ancestor is not read.

Witness-regen: canonical scenario tuned so all four discrete traces vary inside the 60-frame window the witnesses cover, plus a lone early sleeper

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things, and none of them is a new promise.

THE AUTHORITATIVE WITNESS SET, from run 32078978850 on ubuntu-24.04. The
intermediate set two commits back carried the SAME BYTES — compared file by
file — so what was wrong was the PROVENANCE, not the content, and a
`PROVENANCE.txt` the measurement contradicts is not left in place.

INTER-ISA BIT-IDENTITY IS MEASURED. All eight files are identical between
ubuntu-24.04 (x86_64) and aarch64-macOS, the four 1000-frame chains included.
Both alternative readings are refuted by measurements already in hand: "the
control does not judge" by P1-4's counter-factual, where a stale witness gives
rc=1; "trivial agreement on exactly-representable values" by the slider
residual, where 5.000002 at f32 is four ULP of ACCUMULATED rounding that an
exact trajectory could not produce.

The mechanism predicts it. IEEE-754 fixes the correctly-rounded result of
`+ - * /`, sqrt and comparisons, so two conforming implementations on the same
inputs in the same ORDER give the same bits. The inter-ISA divergence sources
are reassociation, contraction, transcendentals, denormals, rounding mode and
extended precision — and this milestone removed every one.

LEVEL 3 IS NOT PROMISED AND C1.1 DOES NOT MOVE. One measurement, one scenario,
one machine pair of which one is not in the matrix; and the property holds by
the ABSENCE of a single transcendental on the path, so a future shape could end
it. A contract disabled at its first failure is not a contract.

But the skip had to go: it discarded the strongest signal available. The chain
is now compared on every host and REPORTED where level 1 does not apply,
exactly like the divergence frame — its regression is the signal, never its
value. No pass/fail added, therefore no promise added, and the day the
agreement ends it is learned rather than discovered.

Also in this commit: the residual I recorded yesterday — hook and remote
regeneration "mutually blocking", three pushes — is RETRACTED. They are not.
P1-2 made local generation work on a structural change, so the correct flow is
to regenerate locally and commit the new baseline in the same commit as the
scene change; one push suffices. My three pushes were a self-inflicted detour,
and claiming a residual for it would have left a false obstacle in the record.
What remains is smaller: the trailer is read from the HEAD commit message, so
an interposed commit puts it out of reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A reason for presence is not an oracle. The frozen Scope listed each element
with why it was there, which is satisfiable BY PRESENCE. The two questions that
had to be asked instead, per element: does its effect occur BEFORE FRAME 60,
and is that effect in a COMPARED ARTIFACT. Three of nine failed them; the
sensor failed both. The full table is in the brief's Closing notes, because it
is what M1.1.25 and M1.A will reread to know what the scenario guarantees.

SENSOR — a level-1 coverage hole, not a level-2 one. A trigger resolves no
impulse, so `current`, `entered` and `exited` can each diverge between two
machines without displacing a body, leaving the chain, the four discrete traces
and the reference window all identical. All three sets are now serialised into
the chain, identity written index AND generation. Measured before: over the 60
compared frames the visitor never reached the trigger — zero deltas, `current`
never non-empty — so the element was absent from the window as well. Now x = 76
at 12 m/s, both deltas inside the window.

GROUPS — `max_islands > min_islands` passed for 1000 frames without the
announced collision ever happening: measured, the two groups never met inside
the window, the gap closing to 2.272 m and reopening, and the single island
variation came from the sleeper disappearing. Frictionless with restitution 0.5
now, gap closing to 0.914 m. A targeted test names the group_a to group_b
constraint and requires the island count to FALL and RISE, since a merge-only
scene passes a weaker test. The old criterion is KEPT beside it: weak-but-true
is insufficient, not wrong.

STALE CONTRACTS, both superseded by their own fixes: `scenario.zig` announced
eight elements and twelve mobiles, and `witness.zig` still described the chain
as skipped on ARM64 while it is measured there. Both patched, and each element
entry now states WHERE its effect is observed.

TWO METHOD POINTS. A counter-factual evaluated at the wrong instant is green
for a TRUE reason: the sensor lockstep first compared at the last frame, where
the visitor has already left and both worlds legitimately agree — the temporal
variant of the family, one rewrite. And my first collision counter-factual was
not discriminating, 4 m/s arriving even with friction restored; it was discarded
rather than kept for being green, and replaced by the measured cause.

The dead-test conservation stopped this commit too, closure 1869 against 1867
declared, and the number was re-derived from `zig build test` reporting 1869 —
second commit in a row it has done its job on a real test addition.

REGENERATION, generated locally and one push rather than three: run
32078978850 measured all eight witnesses bit-identical between ubuntu-24.04 and
this host, chains included, so local generation produces the cell's bytes and
the twelve cells VERIFY rather than produce. Reference window and discrete in
ReleaseSafe, cross-mode agreement re-measured true at both precisions, zig
0.16.0, -Dcpu=baseline. Full provenance in PROVENANCE.txt.

Witness-regen: nine-element scenario — sensor state serialised into the chain, the two groups now actually collide inside the compared window, and a lone early sleeper

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P1-1 — the sensor test was GATED ON `current`, so it could not see what it
protected. The comparison ran only where `current` was non-empty: at entry
that masks a missing `entered`, and the frame where only `exited` is
populated is never observed. Removing both deltas left it green. "A
counter-factual has a correct INSTANT" was the lesson I had just written for
the lockstep evaluated at the last frame, and the fix applied it to `current`
and to neither of the others.

Three independent discriminations now, one per set, each evaluated at a frame
where THAT set is non-empty. The suppression is written as an EMPTY SET and
never as an omission: omitting drops the length prefix too, so the bytes would
move even where the set is legitimately empty. Three counter-factuals, three
separate removals, three reds, and the message names the counts — `current` on
25 frames, `entered` on 1, `exited` on 1. A single-frame delta is exactly what
the gated version could not see.

P1-2 — two true witnesses side by side with nothing joining them.
`saw_cross_pair` on one hand, fall/rise on the GLOBAL island count on the
other; the sleeper already supplies a fall and nothing tied either to the
groups, so the test passed even if they stayed merged. Island MEMBERSHIP of the
two groups is followed directly now, and the sequence SEPARATE, ONE ISLAND,
SEPARATE is required on those bodies. Measured: apart from frame 0, merged at
15, apart again at 21.

My first counter-factual for it was NOT discriminating — restitution 0 reaches
phase 3 anyway, friction being zero — and is reported rather than kept.
Replaced by a window truncated to 20, before the split: it fires and names the
phase reached, 2 of 3, the merge-only scene the clause refuses.

P2-4 — the numbering was unstable, 1 to 8 with a `6 bis` while the text cited
a nonexistent element 9 and the main test said eight. Nine entries, 1 to 9, no
bis, three other sites aligned, and CLAUDE.md's floor moved to 1869/1867.

P2-3 — the provenance said `sha=pending` and local ARM origin while run
32096367333 had accepted exactly those eight files on twelve cells. Rewritten
with cell=ubuntu-24.04 as the authoritative origin, reference window and
discrete in ReleaseSafe, zig 0.16.0, -Dcpu=baseline, the verifying run named, and
the dependency kept explicit: local generation produces the cell's bytes only
while inter-ISA bit-identity holds, which rests on the absence of a
transcendental on the deterministic path.

Witness-regen: nine-element scenario, second review round — three independent sensor-set discriminations and the group collision followed by island membership

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P1 — one question, three formulations, one element. The sensor reached no
artifact; then the test was gated on `current`; now the previous fix tested
`dumpSensorSets` AGAINST ITSELF. `full` was produced in the loop and entered no
comparison, and the single integration check ran after the 60 frames where all
three sets are empty, so it compared emptiness. Making `dumpState` emit only
`current` left the whole Forge suite green.

The assertion is now on the SUFFIX of what `dumpState` produces, at a frame
where the set in question is non-empty. And the correction had a trap of its
own, avoided by construction: the reference uses a LITERAL mask, not
`all_sensor_sets`. With the reference reading the same constant production
reads, a counter-factual on production moves both sides and fires for the wrong
reason — which is exactly how the previous round's three counter-factuals
passed while never touching `dumpState`. Three counter-factuals on production
now, each reporting `in dumpState=false` while `contributes=true`.

The same question applied to the nine, and the answer is not uniform: only the
sensor and the character reach an artifact through a serialisation function, and
those are the only two where a test could confront a helper instead of the path.
Both are now asserted on `dumpState` output. The other seven are read directly
off world state, where no intermediate exists to be mistaken for the path.

P2 — three stale live contracts, each superseded by its own fix. The tuning
comment still described the original scene, 4 m/s closing over a 12 m gap and
contact near frame 180, after a retune that moved every figure; it now carries
the current numbers and the measured phases 0/15/21. The renumbering was swept
rather than patched at the reported instances — `trace.zig` still said "element
7 of 7" and "twelve mobile bodies". And the provenance was hand-written with
`sha=pending` and a local ARM origin while the run's own artifact carried the
canonical one; it is TAKEN from the artifact now, with the local-generation
dependency appended as an explicitly conditional annex.

The eight committed files are identical to run 32132088988's artifact, 8 of 8 —
a third independent confirmation that local generation produces the cell bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment only, no behaviour change.

P2-1 — the coverage test's comment attributed `max_islands > min_islands` to
element 3 and claimed BOTH directions. The predicate carries neither: `max >
min` is satisfied by ONE variation in either sense, and the lone sleeper
leaving the partition supplies exactly that by itself. The targeted membership
test is correct and does not make the sentence true — a correct assertion
elsewhere does not retroactively justify a false claim here.

Attribution removed. The predicate stays as an unattributed coverage probe, the
partition moves at all, and the two-direction property with its attribution to
the two groups lives only in the test that follows island membership.

P2-2 — the renumbering, third round. Three active comments still called the
terrain element 7 while the numbered blocks made it 8. Swept as a class rather
than at the three reported lines, and verified in both directions afterwards:
the nine numbered blocks read 1 to 9 in order, and the only textual indices left
are 5 and 8, both correct. Three rounds on one renumbering is the measurement —
a numbering changed in one pass and referenced in prose across files is not
swept by fixing what a reviewer happens to cite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The coverage test named for nine elements asserted seven. Element 3 lost its
assertion when the coarse island-count predicate was un-attributed; element 7
never had one, `saw_sleep` being satisfied by the five-box stack — Codex measured
it, forbidding `lone_sleeper` to sleep left the test green.

Both observations added: the sleeper by IDENTITY, the two groups by island
MEMBERSHIP. Each counter-factual attributes to its line, :789 and :788. The first
probe reached for on element 3 was NOT discriminating — moving the group centres
apart leaves them closing at 4 m/s — and was replaced by removing the cause.

Element 3's summary no longer claims the dedicated test requires the global
island count to fall and rise; it requires membership on those two groups.
An observation has an instant, not only an object. `lone_slept` accumulated over
750 frames while element 7 exists to make the sleep-state trace non-constant
inside the 60 the witness compares — Codex measured it: awake for the window and
asleep afterwards left the test green. Identity was fixed one round earlier and
the instant was not.

The criterion is which artifact carries the effect and over how many frames it is
compared: the four discrete traces over 60, the continuous chain over 1000. Five
discrete-carried observations now assert a FRAME through `firstFireInside`, which
is strictly stronger than the boolean. The chain-carried ones stay unbounded — the
riser is stood on at frame 422 and a 60-bound there would be false. Measured
first-fire frames: 0, 15, 20, 29, 15 inside; 422 outside and chain-verified.

The sweep found a second instance: the line labelled (2) fires at 29, the same
frame as element 7, and with element 7 forbidden to sleep the scene's first sleep
is frame 100 — outside the window. It is chain-carried, so verified at 100 of
1000, and is labelled for that rather than bounded.

And element 3's summary, reported as aligned last round, was NOT in the code. One
pattern used twice: the replacement said "the island count" where the code says
"the count", so it matched nothing, and the verifying grep carried the same two
words and could only confirm itself. Applied, verified by reading the object back.
Third instance of one class on this test. `first_any_sleep` read
`slept_last_tick > 0`, a counter, while the assertion claimed the stack's
transition at frame 100 — the value it actually read was element 7's body at 29.
The previous round found this on this very line and moved it one notch instead of
closing it. Codex measured the consequence: `can_sleep = false` on all five stack
bodies left the whole suite green.

The question asked of the nine — does each line naming an element read an identity
of that element, or an aggregate another element can satisfy? Three were
aggregates: the stack's sleep, the mesh's multi-constraint pair, and the sensor
deltas. All three now read identities. Whether each fix changes an answer today
was measured: 0 non-mesh multi-constraint pairs, 0 non-target sensor events. So
elements 5 and 6 were correct by accident and element 2 was not.

Counter-factuals attribute per line: stack :881, mesh pair :874, sensor pair :896.
`EntityId` has no `eql`, so `samePair` compares both fields rather than bitcasting
the packed u64.

And the loop traversed 750 while the assertions claimed the chain's 1000, so a
second sensor crossing in 750..999 escaped `exactly one`. It now runs
`chain_frames`, and nothing had to be relaxed: entered 1, exited 1, max_x
-60.1875, max_y 0.946324, slider v 5.0.
@guysenpai
guysenpai marked this pull request as ready for review August 20, 2026 09:27
@guysenpai
guysenpai merged commit e02d27b into main Aug 20, 2026
19 checks passed
@guysenpai
guysenpai deleted the phase-1/forge/determinism branch August 20, 2026 09:32
guysenpai added a commit that referenced this pull request Aug 23, 2026
The state table was stale on three lines: PR #71 is merged and
v0.11.14-determinism is posted, so the last released tag, the active branch
and the current milestone all move. The test floor is re-derived from the
suite at 1906 / 1904 and says so, since re-deriving it from the closure is
what the dead-tests guard refused once during this milestone.

Two open decisions are added as preconditions of the M1.1.26 freeze --
ModuleContext, declared nowhere while the interface types init with it, and
the pose setters that cannot stay void now that bodies carry proxies -- and
the M1.1.9 precision entry is re-pointed onto the rewritten section 1.11.8.

Closing audits: zero French function words and zero accented tokens on the
full branch diff and on the brief; the drift sweep found three orphaned
references to deleted symbols and patched them in place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant