Phase 1 / Forge / Cross-platform determinism of forge_3d - #71
Merged
Conversation
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.
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
marked this pull request as ready for review
August 20, 2026 09:27
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brief
briefs/m1.1.14-determinism.mdSTILL 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.ymltriggers only onpushtomainandpull_requesttomain: without an open PR, Gates A, C and D are structurallyunmeasurable. 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 one851ff0,generated by run 32026918193 on
ubuntu-24.04at-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:
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.
gitshows only four modified.binfiles, a thirdindependent 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.04andwindows-2025on all four(precision, mode)keys — and level 2 point 1 green on a realubuntu-24.04-armcell.Four behavioural changes, where the frozen Scope named two: a deterministic cosine replacing
@cosat themax_slopeconversion; the float environment INSTALLED at every thread start andprocess entry and ASSERTED at the physics entry; float
@reducereplaced by an explicit left fold at18 sites, behind a
no_float_reducelint rule (a Zig backend defect, written up and not filed —filing is Guy's act); and
shm.zigmoved topage_size_minat 8 sites, which is what made AArch64compile at all.
CI matrix
{ubuntu-24.04, windows-2025, ubuntu-24.04-arm} × {Debug, ReleaseSafe} × {f32, f64}= 12cells, every one pinned
-Dcpu=baseline, withzig build lintandzig build forge-determinismonthe 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-witnessexited 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 pipefailthe CI step died exactly in the case it existsfor. Verified in three directions.
P1-3 —
ARCH-031rule 5's site set was measured, not inherited: TEN, not three. Seven wereuncovered, including
determinism_mainitself — the determinism instrument asserting the guaranteewithout 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; theenumeration 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_defaulteverywhere, andinstalling 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:
mobileholds rigid bodies and a virtual character owns none, sothe 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_y0.0063 / 0.9463 / 2.6707 at cosines0.9211 / 0.7074 / 0.3624.
P1-2 — the deterministic cosine is pinned to an oracle that never calls
@cos. Pi to 80 digits inexact 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 —
divergenceFrameanswerednoneon absent data. An empty, truncated or wrong-precisionwindow returned
null, which the instrument prints as "no divergence". The two outcomes are separatedand 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
nulllive.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.
d237f82CONTROL FAILED — expected 1866 collected, zig build test reported 1867, with layer one printingconservation OKin the same log8116831CONSERVATION FAILED — closure gives 1864 expected collected, expectedCollectedOn(windows) declares 1865; 8 non-Windows cells GREEN at 1866Red #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 1864and staying green whateverhappens. And its fault was invisible from the development host — macOS reads the
elsebranch, sozig build dead-testsprintedconservation OK at 1866and thepre-commithook let it through, whereit 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:
1866with
control OK at 1866on the eight non-Windows cells,1864withcontrol OK at 1864on the fourWindows ones, and
forge-determinismpresent on 12 of 12 by enumeration.Measured at close
windows-2025 / ReleaseSafe / f32hit 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-runforge_3dtestsubuntu-24.04/macOS, 1864 onwindows-2025SHA256SUMSverified 8/8, per-file provenance2^31an energy injection would need — rounding, not energyResiduals, named and carried to the plan
Guy's ruling. Until it exists a new thread or
maincan arrive with no installation and nothingsays so.
and checks the predicate; none observes the panic. A sign-inverted mutant would be caught, only the
always-true mutant survives.
GroundStateis constant on the canonical scenario, so the verdict field carries nodiscrimination there — the position does. Not to be read as coverage of the
.on_steep_groundand.in_airarms.bench.yml's cache key missing theCPU axis, and
weldengine/setup-zigpurging a tree that overlaps.zig-cache.@reducereport — written, not filed. Filing is Guy's act.