Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a046dd5
docs(brief): add M1.1.3 frozen brief
guysenpai Jul 19, 2026
4d23ff9
refactor(forge): split narrowphase into a package (M1.1.3)
guysenpai Jul 19, 2026
c7f6f45
feat(forge): add supportingFace and contact manifold types (M1.1.3)
guysenpai Jul 19, 2026
7fd6d76
docs(brief): correct supportingFace direction wording (M1.1.3)
guysenpai Jul 19, 2026
ba2b3c0
feat(forge): add EPA penetration depth and normal (M1.1.3)
guysenpai Jul 19, 2026
8552b34
fix(forge): harden EPA polytope robustness (M1.1.3)
guysenpai Jul 19, 2026
f8874ea
docs(brief): epa signature carries pose for world output (M1.1.3)
guysenpai Jul 19, 2026
ef46f93
feat(forge): add contact manifold generator (M1.1.3)
guysenpai Jul 19, 2026
16ce366
feat(forge): wire contact manifold into forge_3d (M1.1.3)
guysenpai Jul 19, 2026
a51325a
fix(forge): harden contact manifold (M1.1.3)
guysenpai Jul 19, 2026
c0d835f
fix(forge): unique frame-stable manifold feature ids (M1.1.3)
guysenpai Jul 19, 2026
7bf0b13
docs(brief): ratify collide identical-pose order exception (M1.1.3)
guysenpai Jul 19, 2026
5ec2b5d
test(forge): cover reference-corner manifold feature id (M1.1.3)
guysenpai Jul 19, 2026
d2426cc
fix(forge): reference-corner feature id + pose sweep (M1.1.3)
guysenpai Jul 19, 2026
95b13ee
docs(brief): log reference-corner feature id fix + pose sweep (M1.1.3)
guysenpai Jul 19, 2026
b302ccd
fix(forge): encode incident face in reference corner id (M1.1.3)
guysenpai Jul 19, 2026
40ef6c7
docs(brief): log incident-face reference-corner id fix (M1.1.3)
guysenpai Jul 19, 2026
1e57649
fix(forge): class-tag single-contact feature ids (M1.1.3)
guysenpai Jul 19, 2026
7d1e737
docs(brief): log single-contact feature id class-tagging (M1.1.3)
guysenpai Jul 19, 2026
c7901ea
fix(forge): encode point-core sub-feature in single-contact id (M1.1.3)
guysenpai Jul 20, 2026
5f5d1b8
docs(brief): log point-core sub-feature id fix (M1.1.3)
guysenpai Jul 20, 2026
be3ce59
docs(forge): scope feature_id stability + doc hygiene (M1.1.3)
guysenpai Jul 20, 2026
67d8280
docs(brief): close M1.1.3
guysenpai Jul 20, 2026
852bad3
docs: add v0.11.3 M1.1.3 tag row to working memory
guysenpai Jul 20, 2026
08aaafb
docs(claude-md): complete M1.1.3 current-state
guysenpai Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CLAUDE.md

Large diffs are not rendered by default.

242 changes: 242 additions & 0 deletions briefs/M1.1.3-narrowphase-epa-manifold.md

Large diffs are not rendered by default.

47 changes: 46 additions & 1 deletion src/modules/forge/forge_3d/body_manager.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const api = @import("weld_forge");
const config = @import("config.zig");
const shape_mod = @import("shape.zig");
const body_mod = @import("body.zig");
const narrowphase = @import("pipeline/narrowphase.zig");
const narrowphase = @import("pipeline/narrowphase/root.zig");
const IdAllocator = @import("slot_alloc.zig").IdAllocator;

const Real = config.Real;
Expand All @@ -33,6 +33,7 @@ const Shape = shape_mod.Shape;
const Body = body_mod.Body;
const MotionProperties = body_mod.MotionProperties;
const GjkResult = narrowphase.GjkResult(Real);
const ContactManifold = narrowphase.ContactManifold(Real);

const ApiVec3 = @import("foundation").math.Vec3;
const ApiQuat = @import("foundation").math.Quatf;
Expand Down Expand Up @@ -149,6 +150,50 @@ pub const BodyManager = struct {
self.bodies.items(.rotation)[ib],
);
}

/// Full narrowphase (GJK → shallow/deep contact manifold) for the pair
/// `a`/`b`, resolving each body's world pose and support shape (via `store`).
/// Returns null if the pair is separated, or if either handle — or its shape
/// — is stale/invalid. The `BodyId`-level manifold adapter for the
/// broadphase→narrowphase flow (mirror of `gjkPair`): unpack a `computePairs`
/// candidate's `user_data` as a `BodyId` and call this per pair.
///
/// The pipeline is driven in a canonical BODY-ID order (`min(a, b)` first),
/// negating the normal for the `a > b` caller. This makes the whole
/// narrowphase order-independent even for the measure-zero case `collide`'s
/// pose key cannot break — two bodies with bit-identical shape AND pose — and
/// gives a stable, body-id-keyed order for M1.1.6 warm-starting.
pub fn collidePair(self: *const BodyManager, store: *const ShapeStore, a: BodyId, b: BodyId) ?ContactManifold {
if (a > b) {
var m = self.collidePairOrdered(store, b, a) orelse return null;
m.normal = m.normal.neg(); // caller wants a→b = −(b→a)
return m;
}
return self.collidePairOrdered(store, a, b);
}

/// `collidePair` for a fixed (already-canonical body-id) order — validates both
/// handles/shapes then runs the manifold pipeline in THIS order. Calls
/// `collideOrdered` (not `collide`): `collide` would re-canonicalize by pose,
/// so the `feature_id` reference/incident ownership would follow the pose and
/// flip across a lexicographic pose boundary (Codex P1b). Driving by the fixed
/// body-id order instead keeps the feature_id frame-stable; `collidePair`'s
/// normal negation still gives order-independence.
fn collidePairOrdered(self: *const BodyManager, store: *const ShapeStore, a: BodyId, b: BodyId) ?ContactManifold {
const ia = self.alloc.validate(a) orelse return null;
const ib = self.alloc.validate(b) orelse return null;
const shape_a = store.get(self.bodies.items(.shape)[ia]) orelse return null;
const shape_b = store.get(self.bodies.items(.shape)[ib]) orelse return null;
return narrowphase.collideOrdered(
Real,
shape_mod.supportShape(shape_a),
self.bodies.items(.position)[ia],
self.bodies.items(.rotation)[ia],
shape_mod.supportShape(shape_b),
self.bodies.items(.position)[ib],
self.bodies.items(.rotation)[ib],
);
}
};

/// Exact world-space AABB of a shape at pose (`pos`, `rot`).
Expand Down
Loading