… the geometry protocols
Physics v1 needs Capsule/Segment/Chain shapes, convex decomposition and a
tilemap bridge on top of it. Three things in the current surface would have to
be undone again to get there, so they are settled first.
A `Shape` promised mass. The abstract base declared `area`, `centroidX`,
`centroidY` and `unitInertia`, which only solid geometry has. A segment or a
chain of edges has no interior, and satisfying that base means writing
`area = 0` and a fake centroid that reads exactly like a degenerate solid.
Mass becomes a capability: `massProperties` is a frozen `ShapeMassProperties`
or `null`, the shipped shapes narrow it to non-null, and the body's mass model
reads it through the base contract so boundary geometry cannot feed NaN into
it. The computed mass, centre of mass and inertia of every existing shape are
unchanged.
Four structural types described one thing. Physics published `Aabb` and
`VectorLike` and kept an internal `Mutable2D`, all structurally identical to
core's `AabbLike` and `PointLike` - and `AabbTreeBroadPhase` imported core's
`AabbLike` in the same file that declared `Aabb`. Physics now uses `AabbLike`
and `Readonly<PointLike>` throughout. `PointLike` stays mutable on purpose: it
is a return type in core (`View.screenToWorld` and friends) and the element
type of `PolygonLike.points`, so read-only-ness belongs at the parameter
position, not in the protocol. `AabbLike` moves out of `DynamicAabbTree.ts`
into its own module, matching the rest of the `*Like` family.
`Segment` and `ShapeLike` go. `Segment` modelled the same finite segment as
`Line` with different field names and no collision contract, and had no
consumer anywhere - its claim to be used by the swept-collision utilities was
not true. `ShapeLike` was never accepted by any API, and two of its
implementers return `null` from `collidesWith` unconditionally, so it promised
a response it could not deliver; `Collidable` is the contract that is actually
consumed. The interface stays as an internal conformance check.
Also fixes a defect that would have blocked convex decomposition outright:
neither the broad phase nor the contact graph excluded a pair of colliders on
the same body, so a compound body collided with itself - a real
`collisionStart` (or sensor enter) plus a solver constraint every step, whose
impulses only cancelled by luck. Decomposition attaches many edge-sharing
colliders to one body, which would have made that O(n^2) per body. The broad
phase now never forms the pair, so it never enters the persistent set.
Claude-Session: https://claude.ai/code/session_01R4RHfm7nhMPXAuxFrpgrqj
Physics v1 needs Capsule/Segment/Chain shapes, convex decomposition and a tilemap bridge on top of it. Three things in the current surface would have to be undone again to get there, so they are settled first.
A
Shapepromised massThe abstract base declared
area,centroidX,centroidYandunitInertia, which only solid geometry has. A segment or a chain of edges has no interior, and satisfying that base means writingarea = 0and a fake centroid that reads exactly like a degenerate solid.Mass becomes a capability:
Shape.massPropertiesis a frozenShapeMassPropertiesornull, the shipped shapes narrow it to non-null, and the body's mass model reads it through the base contract so boundary geometry cannot feed NaN into it. The computed mass, centre of mass and inertia of every existing shape are unchanged.Precedent: Rapier attaches mass properties to the collider (
MassPropsMode), and Box2D v3 has no shape base class at all.Four structural types described one thing
@codexo/exojs-physicspublishedAabbandVectorLikeand kept an internalMutable2D, all structurally identical to core'sAabbLikeandPointLike— andAabbTreeBroadPhaseimported core'sAabbLikein the same file that declaredAabb.Physics now uses
AabbLikeandReadonly<PointLike>throughout.PointLikestays mutable on purpose: it is a return type in core (View.screenToWorldand friends) and the element type ofPolygonLike.points, so read-only-ness belongs at the parameter position, not in the protocol.AabbLikemoves out ofDynamicAabbTree.tsinto its own module, matching the rest of the*Likefamily.SegmentandShapeLikegoSegmentmodelled the same finite segment asLinewith different field names and no collision contract, and had no consumer anywhere — its doc claim to be used by the swept-collision utilities was not true.ShapeLikewas never accepted by any API, and two of its implementers (Vector,Line) returnnullfromcollidesWithunconditionally, so it promised a response it could not deliver.Collidableis the contract that is actually consumed. The interface stays as an internal conformance check.Same-body contacts
Neither the broad phase nor the contact graph excluded a pair of colliders on the same body, so a compound body collided with itself — a real
collisionStart(or sensor enter) plus a solver constraint every step, whose impulses only cancelled by luck.Convex decomposition attaches many edge-sharing colliders to one body, which would have made that O(n²) per body. The broad phase now never forms the pair, so it never enters the persistent set.
Breaking changes
Shape.area/.centroidX/.centroidY/.unitInertia→Shape.massPropertiesAabbandVectorLikeno longer exported from@codexo/exojs-physics→AabbLike/Readonly<PointLike>from@codexo/exojsSegmentremoved → useLineShapeLikeno longer exported → useCollidableValidation
pnpm verify:quick16/16 ·pnpm test10 571 passed · API docs regenerated · root-index snapshots updated (removals only, plus the newShapeMassProperties) · new regression tests for same-body pairs, same-body events/sensors, and a body carrying boundary geometry with no mass properties.https://claude.ai/code/session_01R4RHfm7nhMPXAuxFrpgrqj