Skip to content

Phase 1 / Forge / Narrowphase EPA order-dependence hotfix (M1.1.3-HF)#59

Merged
guysenpai merged 36 commits into
mainfrom
phase-1/forge/epa-frame-dependence-hotfix
Jul 24, 2026
Merged

Phase 1 / Forge / Narrowphase EPA order-dependence hotfix (M1.1.3-HF)#59
guysenpai merged 36 commits into
mainfrom
phase-1/forge/epa-frame-dependence-hotfix

Conversation

@guysenpai

Copy link
Copy Markdown
Contributor

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 the engine-physics-forge.md §3 order-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.md

What the fix does (gate by gate, RED-first)

  • E1 — Independent SAT oracles (zonotope segment⊖box, extracted 15-axis box/box) + a RED reproduction suite: S1 (polytope corruption → depth 0), S3 (1-D Minkowski degenerate normal frame-dependence), and a 390-config × 2-order × 3-scale order-equivalence sweep. Reproduced the recon (EPA depth 0.0 vs SAT 1.9).
  • E2epa.zig expansion robustness (Fix A): coplanar-tolerant visibility, connected visible region (flood-fill), transactional commit-on-success expandPolytope, skip-face non-convergence semantics, a realizable exhaustion fallback, EpaDiagnostics, and a flat-face terminalFace reconstruction restricted to the converged plane. S1 green both orders.
  • E3 — the intrinsic point⊖segment world normal (segment-owner's world rotation + explicit ownership sign → exact bit negation across orders). S3 green.
  • RD-4 — a gjk.zig post-loop deep band if (dist <= contact_margin) deep, reusing the existing conv_k accumulated-rounding bound (no new constant, noise_k untouched — P1b/P2 calibration preserved), closing a deep/shallow classification stall that mis-classified a genuinely-deep overlap .shallow at dist≈0 (arm64 f32-unit / f64-×0.01, scalar/scale/order-dependent). Pre-committed by the brief's no-deferred-debt clause; design ratified.
  • E4 — the sweep is GREEN with mandatory SAT classification + per-config EPA diagnostics (no corrupt path fires); genericConsistent narrowed (SAT MTV tie or documented count-only class); q/−q double-cover pin; collidePair ±ε deep-pair boundary pin; CLAUDE.md patched.

Recorded deviations (need ratification)

  • RD-1 skip-gating the RED pins (green-enforcing pre-push, no bypass).
  • RD-2 E1(a) zero-cross skip at TRUE zero, not a relative threshold (frozen text was faulty).
  • RD-3 pin (iii) retargeted + status-conditional — the frozen (0.1,0.1,0.1) is a cross-platform deep/shallow boundary (arm64-f32 .shallow, x86-64/f64 .deep).
  • RD-4 in-milestone gjk.zig deep-band fix (one if + comment, contact_margin reused).
  • RD-5 E4(b) also accepts the M1.1.4-documented collideOrdered count-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).
  • Cross-platform (x86-64) validation via this PR's CI.
  • §3.6.1 audits: language (no French in the diff), drift (no stale refs to removed/renamed symbols) — clean.
  • gjk.zig/support.zig/fast_paths.zig production: touched ONLY gjk.zig, only for RD-4's deep band.

Merge + tag (M1.1.3-HF row + squash hash) are Guy's, after review.

🤖 Generated with Claude Code

guysenpai and others added 30 commits July 23, 2026 17:05
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>
guysenpai and others added 6 commits July 24, 2026 01:56
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>
@guysenpai
guysenpai merged commit 7e63912 into main Jul 24, 2026
16 of 18 checks passed
@guysenpai
guysenpai deleted the phase-1/forge/epa-frame-dependence-hotfix branch July 24, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant