Phase 1 / Forge / Queries: shapecast, overlap, point query, closest point - #64
Merged
Merged
Conversation
Two tests for `engine-physics-forge.md` §1.11.14, committed ALONE against the M1.1.9 code so the failing state is a commit anyone can check out and re-run. The fix follows in the next commit. `an exact distance tie is broken by entity, not by creation order` is RED here, at both precisions: two unit spheres at (20, +/-0.5, 0) against a ray from the origin along +X return the bit-identical distance 19.133974075 at f32, and with the entity indices crossed against creation order the `BodyId`-only tie-break answers a different entity in each of the two build orders. Its three preconditions pass in the same run, which is what makes the failure mean something: the two BodyIds are ordered by creation, the two kernel distances are bit-identical, and both equal the closed form asserted with no tolerance. `two bodies on the same entity fall back on BodyId` is GREEN here, and correctly so: where the entities are equal the two keys coincide. It pins the residual §1.11.14 names, including the non-invariance itself.
`engine-physics-forge.md` §1.11.14. `BodyId` is a slot index, so it encodes creation order; leading the ordering key with it makes a query's answer a function of the order the scene was built in, which is the inverse of the invariance §1.11.6 claims. The key becomes `(distance, entity, BodyId)`, with `BodyId` only as the final tie-break for the residual §1.11.14 names (two bodies of one entity at an exactly equal distance are not invariant, and nothing forces one body per entity). - `BodyManager.entity(id) ?EntityId`: the column has existed since M1.1.0 and was never exposed, because nothing needed it. The ORDER does. - `hitLess` gains the entity term over an `entityKey` helper: `index` major, `generation` minor, deliberately not `@bitCast` (`EntityId` is a packed struct whose `index` sits in the LOW 32 bits, so a bitcast would order by generation first — a layout accident, not a decision). - `ClosestCollector.add` now CALLS `hitLess` instead of re-deriving the comparison inline, so `closest` and an `all` truncated to one slot cannot disagree structurally rather than by two hand-written comparisons happening to agree. - The solver-side `RayHit` carries the entity. The alternative was passing the store into the comparator, which would turn each of `AllCollector`'s replace-worst comparisons into a lookup; the public `RaycastHit` carries it too, so the mirror is not widened. Verified in both senses: the previous commit is RED, and reverting the key to `BodyId`-only with everything else in place fails BOTH ordering tests (258/260) while the same-entity residual test stays green. The delivered M1.1.9 test `equal distance is broken by the smaller BodyId` had its entities aligned with creation order, so it passed under either rule and discriminated nothing; its entities are now attached to the POSITIONS, making its radius-1.5 scene a second independent witness, and it is renamed `equal distance is broken by the owning entity`. 260/260 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
`engine-physics-forge.md` §1.11.10. `Aabb(T).inflate(half_extents)` in `foundation/math` under the first-consumer rule, and `Bvh(T).queryCast(ray, extent, collector)` plus its `Broadphase(T)` aggregate. Additive in the strict sense: same collector contract, same near-first descent, same visited accounting, and `Aabb.rayInterval` untouched — the only change is that `castInterval` hands the slab test `node.aabb.inflate(extent)`. `queryRay` is now one line on both types, `queryCast` at a zero extent. The argument for why that changes nothing is written at the site: `min - 0` is exact and sign-preserving, `max + 0` maps a `-0.0` bound to `+0.0`, and no comparison downstream distinguishes the two zeros — the parallel-slab test uses `<`/`>`, `@min`/`@max` treat them as equal, `enter > exit` and `accepts` are sign-of-zero-blind, the near-first order compares equal values, and the `0 * inf` NaN repair is unconditional. Inflating the node is EXACT, not conservative-with-respect-to-the-boxes: the Minkowski sum of two boxes is a box, so `node.inflate(extent)` is exactly the set of centres at which the swept box overlaps the node. An inline test pins that equivalence over a 13^3 lattice of centres, both verdicts counted. The `extent`/`c0` contract lives on `queryCast`: the ray starts at the CENTRE of the cast shape's initial world AABB, not its position, with the line saying why the two coincide for the three shapes the store builds today and why that is a property of those shapes. The refactor's proof asserts visited counts MEASURED on the pre-refactor traversal (cloud 91/181/115/1/91 all, 77/125/89/1/91 closest; line 399 all, 17 closest — the last two re-read off the running code, not copied from the M1.1.9 comment that records them). Counts and sets alone proved insufficient: delegating at extent 0.001 left both unchanged, so the pin also carries an ulp-sensitive case — one box entered at exactly t == 10, driven through both entries at a bound of 10 and at 10 - 1 ulp. Three counter-factuals run, each isolating its target: dropping the inflation fails the three inflation tests and leaves the zero-extent pin green; delegating at 0.001 fails the pin and the M1.1.9 closed-bound test; neutralising both bound reads fails five tests including the mandatory discrimination guard on `queryCast prunes with the collector bound`. Breaking `inflate` fails both new foundation tests, so their wiring is observed too. 266/266 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
`engine-physics-forge.md` §1.11.11. A cast is a raycast from the configuration-space origin along `-d` against `C = core_A - core_B`, inflated by `r_a + r_b` — so a sphere cast against a box is a ray against a ROUNDED box, exactly what `raycast.zig` rejects, which is why the cast has its own kernel. Covering every bounded convex through its support map, it has no shape to reject and therefore no error channel at all. `gjk.zig` is imported for `Simplex(T)` alone and `gjk()` is never called — the dependency `epa.zig` already declares, for the same reason: a simplex solver is not a GJK descent. The FROZEN scope mandates reusing `Simplex(T)` while listing an import set excluding where it lives; the enumerated item forbids the LOOP, and the repo precedent settles the prose. `CastExit` is the termination table, one variant per row, so no row is unobservable. The zero-direction exit is a distinct eighth variant labelled as belonging to the DOMAIN table, so the seven termination variants stay a faithful mirror. `castShapeBounded` exposes the ceiling because on the three cores the store builds 32 is unreachable — the hardest scene converges in 4 — and a guard never seen to fire is a comment with extra syntax. The "no longer approaching" guard is at TRUE ZERO with the argument written at the branch: the division is never reached there, a denormal denominator overflows the step to infinity hence the parameter past `max_distance` hence a miss by the bound test, and the numerator is strictly positive so no NaN is reachable. The reference's `-1.0e-18f` is not reproduced. DEFECT found by the oracles: implemented literally, "l'unique restart autorisé" as one restart per CALL truncated a sphere/sphere cast whose closed form is 7 at 6.952526 — for a POINT core the Minkowski difference is a single point, so every sample repeats and the march is healthy anyway. The budget is refreshed at each ADVANCE; the scene now converges 6.214835 -> 6.952526 -> 6.999764 -> 7.000001. RD-1: the bound test is STRICT, against §1.11.11's own "atteint ou dépasse" row and in favour of the closed interval the same section declares — the parameter is a lower bound, so reaching the bound proves nothing while exceeding it does, and the advance can land exactly on the answer. KB patch pending. Seven counter-factuals run, all bit: the restart placement (found the real defect), inflation dropped (270/275), inclusive bound (274/275), ceiling returning a miss (274/275), normal left as A's outward (269/275), witness offset dropped (273/275), zero-direction guard removed (271/275 + 1 crash). 275/275 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
`castShapeBody`, `overlapShapeBody`, `containsPointBody`, `closestPointBody` — each resolves a body's pose and support shape through `store` and calls the exact kernel, mirroring `raycastBody` / `gjkPair` / `collidePair`. No new geometry: this is the wiring. `*const BodyManager` on all four, so a query cannot wake anything structurally rather than by convention. Each validates the HANDLE and then the SHAPE and answers null for either — a live handle whose shape has been destroyed is a different failure from a removed body, and checking only the first would read a freed shape. The core + inflation-radius convention is honoured at the wiring point, which is where a second one could slip in unseen: the body's `Shape` becomes a `SupportShape` through the one shared `supportShape`, and nothing on this path adds a margin. `overlapShapeBody` introduces no threshold at all — the predicate is "the GJK regime is not separated", whose margin is GJK's own. The cast and closest-point adapters return WORLD space through two dedicated types, `BodyCastHit` and `BodyClosestPoint`, rather than reusing the kernel's `CastHit` whose doc says "A's frame". Unlike `raycastBody`, whose local frame is the BODY's and differs per candidate, the cast kernel's frame is the QUERY's and is a traversal constant, so returning it would hand the caller a contract in a frame unrelated to the body it asked about. `closestPointBody` follows §1.11.13 in its order: solidity first by the same membership predicate the point query uses, upstream of any classification; outside, GJK against a degenerate point core, then `max(0, core - r_b)` and the surface projection. A hard core reaches no normalisation at all; where one is reached it is guarded at true zero. Four counter-factuals. Breaking the KERNEL fails all three adapter tests (270/280): the adapters do not survive it. Perturbing the ADAPTER by one ulp fails ONLY the bit-for-bit differential (279/280): it detects a reimplementation at the finest grain, and is deliberately blind to a uniformly wrong kernel, which is E3's oracles' job. Skipping the stale-shape check and waking the body each fail exactly their own test. 280/280 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
Step one of E5, alone in its own commit so it reads at `--stat` and `-M` as a move rather than a change. `git mv query.zig query/root.zig`, then the three ray collectors and the per-candidate `evaluate` they share extracted into `query/ray.zig`. The extracted block is BYTE-IDENTICAL to the original, verified by reconstructing it from `HEAD:query.zig` and comparing: the only alteration is the three `pub` markers the split requires so `root.zig` can name the collectors it constructs. Nothing was reworded, renamed or reflowed. `root.zig` keeps every construction site textually identical by aliasing the three collectors back (`const ClosestCollector = ray_mod.ClosestCollector;`), so the entry functions are unchanged line for line. Its own edits are the mechanical consequences of the move and nothing else: the header path, four `../` import prefixes, the dropped `narrowphase` import (it left with `evaluate`), and `hitLess` becoming `pub` because the split puts it and its caller in different files. `forge_3d/root.zig` re-exports the package at the same public path, so `forge_3d.query.raycast` is unchanged for every caller and no test changed other than its import line. 280/280 at f32, -Dphysics_f64=true and ReleaseSafe.
Step two of E5, after the move landed alone. `query/cast.zig` and `query/overlap.zig` join the package; the four solver-side mirror types (`CastQuery`, `CastHit`, `OverlapRequest`, `ClosestPointHit`) and the five bodies replace the five `@panic` stubs. The five take the SOLVER scalar and return `[]BodyId`, per §1.11.8's structural corollary: an entry typed `f32` inside the solver forces the conversion into the solver, so under -Dphysics_f64 a time of impact would be narrowed to f32 before leaving the kernel and widened again at the interface tier. `api/types.zig` and `engine-tier-interfaces.md` §1 are untouched. The three spec sections meeting a compiler for the first time all hold: `bodyAabb` is already the TIGHT world AABB and already takes `store`, which is why `overlapAabb` needs it; `overlapShape`'s predicate is `gjk(...).status != .separated` with no constant of its own; and `closestPoint` decides solidity upstream of any classification, `.shallow` not being an interior. One ordering key, four consumers: `keyLess` is the identity half — the WHOLE key for the three overlaps, which have no distance — and `hitLess`, `castLess`, `closestLess` are it behind an exact distance comparison. Retention on overflow is replace-worst under that key. No deduplication: the solver returns bodies, and projecting onto entities is owed by §13. `unitDirection` is likewise factored so the cast cannot acquire a different degenerate-direction rule from the ray. `worldAabb` becomes pub: a cast needs the initial world AABB of a shape that is not a body, and the swept ray starts at that AABB's CENTRE. The signature pin now carries two distinct claims: the eight entries at `Real`, and the public types pinned FIELD BY FIELD. The second half is the point — those types used to be pinned incidentally by the stubs' signatures, and now that no entry mentions them a rename there would surface only at the freeze. A detector that stops detecting when its incidental references go is not one. One minimal scene per entry, answers written from the geometry. The `overlapAabb` guard is observed ON THE TRAVERSAL: a counting collector proves the broadphase handed the body over through its fat box while the tight box does not overlap, so the rejection is the exact kernel's. 285/285 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
E6 covers § Acceptance criteria item by item. Nothing was inexpressible as written, so nothing was reinterpreted into something adjacent and easier; the coverage audit is in the brief, entry by entry. New here: the normal at the time of impact (the same pair cast from four directions including one on no axis, the sign pinned by the closed form recomputed in the test rather than transcribed); the initial contact; the face-inclusive AABB with one ulp past the face as the discrimination; pointQuery over all three shapes at inside / boundary / just-outside; the four closest-point behaviours; and, for all five entries, the mask at both ends of its domain, the exclusion list, a sleeping body, buffer overflow under the ordering key, invariance under creation-order permutation and bit-identity between runs. `initial contact` is built so the distinction is real: the cast shape is a bar spanning x in [-5, 5] centred at the origin, the hit body a unit box at 4.5 spanning [3.5, 5.5]. The witness IS a point of the hit body, the cast ORIGIN demonstrably is not, and the two are therefore different — without the middle assertion the test cannot tell §1.11.11's rule from the one it refutes. The M1.1.9 invariance test asserted the geometry and not the identity. It now asserts the ENTITY answered, and that `raycastAll` returns the four entities in the same order under every permutation. The `BodyId` is deliberately not asserted: it follows creation order by construction. The shallow-band test places its probe inside a band that is a few ULPs wide by construction — `.shallow` holds exactly on `r < dist <= r + margin` with `margin = conv_k * floatEps * coordScale` — so the probe is placed in those units and not in metres, above the noise_k deep floor and below the 16-ULP contact margin. The outside-the-solid precondition and the regime are both asserted before the claim. Six counter-factuals run, all bit: `overlapAabb` returning the candidate set (2 fail), `.shallow` read as an interior (1), the shallow guard itself with the probe moved out of the band (1), `keyLess` reverted to BodyId alone (4), the object mask ignored (4 — one per family), and the ceiling returning a miss (E3). The iteration ceiling is referenced, not doubled. Two E5 tests removed rather than left beside their successors: a sphere-only point-query test subsumed by the three-shape one that carries the brief's item name, and a closest-point test whose three claims each acquired a dedicated test. 303/303 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
E7. `bench/forge_3d_shapecast.zig` over the raycast bench's own 10 000-body static grid, so the two tables are comparable. MEASURED and REPORTED, no envelope pre-registered: sphere 1300.8 ns/query, box 1344.7, capsule 1244.0, shape overlap 231.5. The last two rows are a pair that answers a question rather than reporting a number — a radius-0 sphere cast sweeps a point, so its traversal is `queryRay` exactly, and the whole difference against the raycast entry is the GJK march replacing the analytic ray kernels: 1.59x on identical rays with an identical 0.89 hit rate, which also cross-checks that the two paths agree on what they hit. The leak check binds, measured both ways. A deliberate 4 KiB alloc-without-free with `safety` forced true reports the address and the bench's own LEAK DETECTED line; the SAME leak with the DEFAULT safety prints "no leaks". The second run is the one that matters: the default is not a weaker check, it reports success unconditionally. The eleven inherited M1.1.5-M1.1.8 envelopes re-measured with one throwaway probe appended identically to both legs — this branch and `main` at dd7fa1f through a worktree — at both precisions: ZERO movement, digit for digit. Probe and worktree removed. One honesty note recorded: resting-box speed at f32 reads 0.000000000 where M1.1.9's table recorded 0.000000001, and it reads the same on `main`, so it is a difference between two probes' ninth decimal and not a movement. Local §3.6.1 audits clean. Language audit: ZERO French prose, TWELVE counted verbatim spec quotations, each tabulated with its site. Recorded first because it nearly produced a false pass — `grep -E "[eee]"` with an accented bracket class silently matches nothing on UTF-8 here, so the first sweep reported a clean tree over files that demonstrably contain accented text. Redone in Python. Closing grep on the ordering key: 0 occurrences of `(distance, BodyId)` as a query key anywhere; 1 live mention of "the smaller BodyId", which is the §1.11.14 residual itself and true as written; the M1.1.9 brief's three lines correct as history. `CLAUDE.md` NOT patched — the content was announced with the GO and not carried, so nothing was written rather than invented. The exact staleness is tabulated in the brief against the repo rather than recalled: four rows of `Current state`, internally inconsistent as they stand, plus the L68 tag row carrying the superseded tie-break formulation. 303/303 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
E8, three review findings, no deferred debt.
P1 — `closestPointBody` returned `{ distance = 0, position = point }` in
`.deep`, an INTERIOR answer for a point the membership test had just placed
outside, and the comment above it claimed the regime was unreachable. Both
wrong: `.deep` fires whenever the core distance falls inside
`conv_k * floatEps * coordScale`, so a point a few ULPs outside a HARD core
lands there while the membership test — the authority on solidity — says
outside.
The fix keeps to what `.deep` specifies. `closest_a` needed no regime split
at all: A's core is a single point, so it IS the queried point everywhere,
and only `closest_b` was ever regime-dependent. In `.deep` that field is the
ZERO VECTOR and `distance` is 0 — using them would have answered the world
origin. What `.deep` does carry is the terminal simplex, whose vertices hold
`support_b`, so `deepCoreWitness` re-solves the closest-origin problem over
the `w`s for the barycentric weights and combines the `support_b`s with them
— the reconstruction `shapecast.zig` already performs from the same data. The
simplex is in the probe's frame, so world is A-frame + `point`. The three
regimes now share one projection, and the `scale == 0` guard's comment is
corrected: it is REACHABLE in the band.
The band MEASURED against coordScale, which for this probe is RELATIVE
geometry (`|body_pos - point| + coreExtent`), so the band does NOT grow with
the world coordinate: 5.211e-6 at f32 at 1 m as at 10 km. What grows is
ulp(coordinate), and that bounds its REACH — 5 representable points inside it
at 1 m, none at 100 m or 10 km, because the nearest float outside the face is
already past it. An absence of reachability, not of defect; and it explains a
probe that steps by a fraction of the coordinate seeing nothing at scale.
RED-first and TWO-SIDED: 16 eps (in the band) and 64 eps (out of it) through
the same assertions, each with two guards — the membership test really says
outside, and the regime really is the one that side exercises. Plus a pin that
`max_distance == 0` answers only for an interior point. Both RED at 303/305
before the fix. Three counter-factuals bite: the old early return (2 fail),
the unspecified `closest_b` (1), the dropped frame mapping (1).
P2 — the domain assertion was one row of five, on five entries. Measured, not
assumed: `shapeCast` and `closestPoint` asserted only the bound, the other
three asserted nothing, and `store.get(...) orelse return` sat ahead of
everything. All five now assert §1.11.11's domain BEFORE the handle
resolution, which is the substance: a stale handle would otherwise
short-circuit the check and let a NaN pose reach the kernel on the next call.
`assertUnitRotation` is not cosmetic — these rotations are inverses BY
CONJUGATION, and M1.1.9 burned on an f32-unit quaternion widened to f64.
`overlapAabb` deliberately does not assert `min <= max`: §1.11.12 states no
such constraint. Verified the guard fires in the right place — a NaN with a
STALE handle aborts inside `assertFiniteVec` and never reaches the
short-circuit.
P3 — the trailing blank line removed; the FROZEN SECTION is byte-identical to
the first commit, compared programmatically. No RD.
`CLAUDE.md` stays unpatched: the content is suspended, and the Tags line as
drafted describes `closestPoint` without this defect or its correction.
305/305 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
E9, one review finding, no deferred debt. `engine-physics-forge.md` §1.11.12 (1590 lines) carries the rule. Reproduced, all three measurements exactly. Against a body whose tight world AABB is [-2, 2]^3: `min=(1,1,1) max=(-1,-1,-1)` returned ONE body, `min=(9,9,9) max=(-9,-9,-9)` returned ZERO, an inversion on two axes only returned ONE. The answer was not "sometimes non-empty" — it was ARBITRARY, following the amplitude and the axes of the malformation, because `Aabb.overlaps` is written for well-formed boxes and on an inverted one reduces to "does the body enclose both bounds". Fixed by an EXPLICIT REJECTION at the entry, before the traversal, so nothing is walked. Not an assertion: it holds in debug only and would leave the answer arbitrary where the engine runs, on an entry returning a `u32` with no error channel. Not the predicate's job: `Aabb.overlaps` serves the broadphase's hot path. Not defensive padding: it is the semantics of the region the caller described, and it belongs where the caller's bounds cross the boundary. The test is strict `>`, so a DEGENERATE box stays a legal region. Two-sided: the three malformed forms, each behind a guard asserting the malformation is real and annotated with what it returned before; then the well-formed equivalent of each, which must still answer the body; then two degenerate boxes. Counter-factuals bite both ways — removing the rejection fails the E9 test (305/306), and making it non-strict fails the degenerate cases AND `overlapAabb is face-inclusive`, an E6 test written for another purpose (304/306). Which other entry takes caller bounds: MEASURED over every box construction in `query/`. `overlapAabb` is the only one. The other four build theirs from `worldAabb` (extents are sums of non-negative terms), from `pointAabb` (degenerate by construction), or from `fromMinMax(p,p).inflate(max_distance)` — that last one well-formed ONLY because `max_distance >= 0` is asserted, an implicit dependency now written at `closestPointCandidates`. The E8 entry's arbitrage is marked WRONG in place rather than edited away: it read that an inverted box is a well-defined query with an empty answer, which was reasoning where a measurement was available. The false claim stays visible with its correction beside it, because what it records is the failure mode. CLAUDE.md remains unapplied; the corrected paragraph is recorded verbatim in the brief so the superseded one cannot be used by mistake. 306/306 at f32 and -Dphysics_f64=true, debug and ReleaseSafe.
Current state moves to M1.1.10 code-complete with M1.1.9 closed at dd7fa1f / v0.11.9-queries-raycast, and the Tags table gains the v0.11.10-queries-shapecast-overlap row. The two raw pipes inside `|pos_b - pos_a|` are escaped as `\|` so the markdown row keeps its four cells, the convention the M1.1.9 row already follows for `|y|` and `|w|`. 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.
M1.1.10 — eleventh core sub-milestone of the M1.1 rigid arc, and the second and last that interrogates the world instead of advancing it. Replaces the five
@panicbodies M1.1.9 left behind.Brief:
briefs/M1.1.10-queries-shapecast-overlap.md— gate-by-gate execution log, three Recorded deviations, closing notes.What it settles beyond the five bodies
rayShaperejects — asserted in the same test that shows the cast answering that geometry, so the design claim is proven rather than stated. The cast gets its own kernel, and because a support map covers every bounded convex it has no shape to reject and no error channel at all.[]BodyIdoutputs. The publicf32surface ofengine-tier-interfaces.md§1 andapi/types.zigdoes not move — verified by an emptygit diffoverapi/.BodyIdcannot order a query result, measured before it was fixed: two unit spheres at(20, ±0.5, 0)return the bit-identical distance19.133974075at f32, and exchanging their creation order exchanged the answer. The key is(distance, entity, BodyId)across the family.Gates
main,BodyManager.entityAabb(T).inflate+ the swept traversal,queryRayre-expressed at zero extentBodyManageradaptersquery/package (move alone, then the five bodies)Numbers, reported and not gated
Casts land at 1.24–1.34 µs/query over 10 000 static bodies, shape overlap at 232 ns. The cast kernel costs 1.59× the analytic ray kernels on identical rays through an identical traversal. The eleven inherited M1.1.5–M1.1.8 envelopes moved by zero, digit for digit, at both precisions.
Recorded deviations
Simplex(T)lives; resolved on theepa.zigprecedent.query/package does not keep its files under 500 lines; ruled kept.Open at merge
CLAUDE.mdis unpatched: itsCurrent statetable has been stale since M1.1.9 merged and its L68 tag row carries the superseded tie-break formulation. Both patches were announced with the E7 GO and not carried, so nothing was written rather than invented; the exact staleness is tabulated in the brief against the repo.Verification
303/303 forge_3d tests in all four legs — f32 and
-Dphysics_f64=true× debug and ReleaseSafe. Fullzig build testexit 0.zig buildclean,zig build lintgreen,zig fmt --checkgreen.🤖 Generated with Claude Code