Phase 1 / Forge / Narrowphase EPA order-dependence hotfix (M1.1.3-HF)#59
Merged
Conversation
E4-STOP corrections (RD-6 + fix 3): - q/-q double-cover pin (epa_robustness_test): the frozen single-config design used DISTINCT A/B positions, so poseAfter canonicalizes on position alone and never reaches the quaternion compare — the pin was vacuous. Split into two legs: Leg 1 (distinct positions) asserts full bit-identical manifold equality; Leg 2 (COINCIDENT positions, a capsule/box deep pair) forces poseAfter into the quaternion compare and asserts count + depth(tol) + normal AXIS (|dot| > 1 - tol, not sign — a coincident-centre bilateral MTV has no order-independent sign), anchored to the satSegBox MTV oracle. - genericConsistent else branch (fast_paths_test): restructured to assert UNCONDITIONALLY that both orders are non-null and depths agree, THEN accept only the axis divergence via SAT MTV tie OR the documented M1.1.4 count-order-dependence. A null-ness or depth divergence now fails regardless of tie. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The RD-4 post-loop deep band returns `.deep` for a terminal within the accumulated-rounding band of the origin, which is NOT necessarily origin-enclosing. The GjkResult type doc and the `simplex` field doc still promised an "origin-enclosing" simplex — corrected both to "origin-enclosing OR the RD-4 rounding-band terminal (a Minkowski witness at noise distance, not necessarily enclosing)". No behaviour change (comment-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Status → ACTIVE → CLOSED (re-opened for the four E4-STOP corrections, all source-founded, no behaviour change). Execution log gains the 2026-07-24 E4-STOP entry; RD-6 records that the frozen E4(c) q/-q pin could not reach poseAfter's quaternion compare (canonicalized on position alone) and is split into two legs. Closing notes updated to flag RD-6 for ratification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Next planned milestone" row still listed the hotfix as next. The hotfix is the CURRENT milestone (Current-milestone row), untagged by policy — so the Next line now points to M1.1.6 (contact solver). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex P2 — the RD-4 deep band had a "stay separated" lower-boundary pin but no in-band false-deep pin. Add `test "rd-4 in-band false-deep is benign"`, the complement: a core gap INSIDE the band must classify `.deep` (a non-enclosing terminal at noise distance) yet stay benign. The gap is BAND-RELATIVE, recomputed at `Real` — `gap = m0/2`, `m0 = conv_k · floatEps(Real) · coord_scale0`, `coord_scale0 = 2 + 2·√3` (two unit boxes, `coreExtent(box) = |(1,1,1)|`). Since `contact_margin` is monotone-increasing in the gap, `gap = m0/2 < contact_margin(gap)` is provably in-band at any scalar — no iteration. An absolute gap would be in-band in f32 but ~1e8× out of band in f64 (the precision trap). Leg 1 (radius-0 boxes, both orders): `.deep`, generic non-null, count >= 1, penetration benign (`<= 8·m0` — the non-enclosing seed clamps EPA depth to ~0, so the false-deep fabricates no spurious depth); normal direction unasserted (noise-dominated at pen ~ 0). Leg 2 (two r-0.5 spheres, centres `gap` apart): penetration ~ r_sum ~ 1.0 both orders, pinning the "former shallow result to within eps" of the RD-4 comment. 127/127 tests green (0 skipped) f32/f64 x Debug/ReleaseSafe (arm64). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex P3 — two doc sites still promised an origin-enclosing terminal on the `.deep` path, contradicting the RD-4 witness band. `gjk()` doc: the classification now states `.deep` iff `dist <= contact_margin` (RD-4 band, terminal not necessarily enclosing), `.separated` iff `dist - r_sum` exceeds it, else `.shallow`. `expandToTetra` doc: origin-enclosing for a genuine deep seed, but for an RD-4 band seed the origin may lie marginally outside (the downstream depth clamp handles it). Comment-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex P3 — three surgical retouches to the M1.1.2 scope-boundary entry so its `.deep` prose no longer contradicts the RD-4 band: ".deep = geometric enclosure" -> "... OR the RD-4 accumulated-rounding witness band (dist <= contact_margin, M1.1.3-HF)"; "NO distance threshold on that path" -> "NO distance threshold on the enclosure path (the RD-4 band is the post-loop classifier, conv_k-scaled)"; "the terminal origin-enclosing simplex" -> "the terminal simplex (origin-enclosing, or the RD-4 band terminal)". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Journal the Codex P2 (in-band false-deep pin) + P3 (RD-4 doc sync) round in the Execution log; set the explicit final count (127 tests, 0 skipped) in the Closing notes Final-measurements line. Re-closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex P3 ×3 (one commit, no behaviour change): 1. In-band false-deep pin — Leg 2 spheres → INFLATED boxes. Point-core spheres cannot reach the RD-4 band (coreExtent 0 ⇒ coord_scale = gap ⇒ the gap is out of its own tiny band, .shallow). Two inflated boxes (unit box core + radius 0.5, r_sum 1.0) at centres 2+gap keep coreExtent √3, so the same gap = m0/2 is in-band: gjk classifies .deep on a genuinely-disjoint pair (non-enclosing terminal), EPA depth clamps ≈ 0, and the manifold recovers r_sum − 0 ≈ 1.0. Asserts .deep + maxPen ≈ r_sum, both orders. 2. r_sum > 0 qualification of the shallow inflated-touch rule, two sites (gjk() doc + CLAUDE.md M1.1.2): for hard cores (r_sum == 0) the shallow band is empty — an exact touch (dist == 0) is the RD-4 deep band. 3. epa.zig non-enclosing-seed docs: the module header and the Face struct doc now state that an RD-4 band seed may expand to a NON-enclosing tetra (origin marginally outside; Face.dist may be marginally negative; the downstream depth clamp returns ≈ 0). Test count unchanged (127; leg modified, not added). Green f32/f64 × debug/ReleaseSafe (arm64). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex P3 doc-only precision (no behaviour/test change). The RD-4 band (`dist <= contact_margin`) is evaluated FIRST, so an exact inflated touch (`dist == r_a + r_b`) is shallow IFF `r_sum > contact_margin`; a sub-noise inflation radius (`0 < r_sum <= contact_margin`) classifies deep — benign either way (EPA clamps depth ~0, manifold penetration ~r_sum in both regimes). For hard cores (`r_sum == 0`) an exact touch (`dist == 0`) is the RD-4 deep band. Corrects the prior round's imprecise "r_sum > 0 → shallow". Sites: the gjk() doc, the post-loop three-band classifier comment, the neighbouring separated-comparison note (all gjk.zig), and the CLAUDE.md M1.1.2 entry. Test count unchanged (127). Co-Authored-By: Claude Opus 4.8 <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.
Closes the tracked Open decision "M1.1.4 — generic EPA deep-rotated frame-dependence (M1.1.3 DEFECT)".
collideOrderedGeneric(GJK → EPA →generateManifold, in the frame of A) was order-dependent for deep, rotated convex pairs, violating theengine-physics-forge.md §3order-independence contract on the residual capsule/box + sphere/capsule generic path (box/box was already neutralised by the M1.1.4 SAT fast path). Scheduled before M1.1.6 (its first real consumer of deep manifolds under sustained contact).Brief:
briefs/m1.1.3-hf-epa-frame-dependence.mdWhat the fix does (gate by gate, RED-first)
epa.zigexpansion robustness (Fix A): coplanar-tolerant visibility, connected visible region (flood-fill), transactional commit-on-successexpandPolytope, skip-face non-convergence semantics, a realizable exhaustion fallback,EpaDiagnostics, and a flat-faceterminalFacereconstruction restricted to the converged plane. S1 green both orders.gjk.zigpost-loop deep bandif (dist <= contact_margin) deep, reusing the existingconv_kaccumulated-rounding bound (no new constant,noise_kuntouched — P1b/P2 calibration preserved), closing a deep/shallow classification stall that mis-classified a genuinely-deep overlap.shallowat dist≈0 (arm64 f32-unit / f64-×0.01, scalar/scale/order-dependent). Pre-committed by the brief's no-deferred-debt clause; design ratified.genericConsistentnarrowed (SAT MTV tie or documented count-only class); q/−q double-cover pin;collidePair±ε deep-pair boundary pin;CLAUDE.mdpatched.Recorded deviations (need ratification)
.shallow, x86-64/f64.deep).gjk.zigdeep-band fix (oneif+ comment,contact_marginreused).collideOrderedcount-order-dependence, not only SAT MTV ties (count 2↔3 with negated normal + agreeing depth).Validation
zig build test-forge-3d— all tests pass (0 skipped) at f32 AND-Dphysics_f64=true, Debug AND ReleaseSafe (arm64/macOS).zig build,zig build test(Debug + ReleaseSafe),zig fmt --check,zig build lint— green (pre-push).gjk.zig/support.zig/fast_paths.zigproduction: touched ONLYgjk.zig, only for RD-4's deep band.Merge + tag (
M1.1.3-HFrow + squash hash) are Guy's, after review.🤖 Generated with Claude Code