Multi-shape bodies report the geometry that actually collides - #1684
Merged
Merged
Conversation
A body built from several shapes came apart under rotation on matter, and swung away from the body on planck. One root cause, two faces, introduced by the `getBodyShapes` rotation fix in matter-adapter 1.2.1 / planck-adapter 1.3.1: it rotated the AUTHORED shapes about the renderable's origin. Both halves of that were wrong. `Polygon#rotate` moves a shape's points without its `pos`, so a shape carrying its offset in `pos` spun about its own origin while its origin stayed put — several such shapes visibly separated. And the renderable's origin is not what either engine rotates about: matter turns a body about its centre of mass, planck about its ORIGIN, which on a compound body is a third point again. So stop deriving the pose and read the engine's own geometry back. There is then no pivot to get wrong, and the report describes what actually collides rather than what was authored — an Ellipse is simulated as a circle of average radius, a concave polygon as convex pieces, a degenerate one as a box, and on planck a shape with `isActive: false` has no fixture at all. The overlay drew none of that truthfully before. Also fixed, all found by review of the same code: - `updateShape` at an unchanged angle reported the previous geometry, and a removed renderable stayed pinned: the cache was keyed by angle and cleared on no path but one. - The cache missed on every frame of a body that was actually turning — float equality on the angle — and each miss allocated a fresh shape set through the object pools without ever releasing it. On the debug overlay's per-body-per-frame path. - matter simulated shapes flagged `isActive: false`, which planck and the builtin both honour, so one definition collided differently per backend. - planck reported its fixtures newest-first, i.e. the reverse of `def.shapes`, so an index into one did not address the same shape. Engine, separate from the adapters and hit by the builtin instead: `Polygon#rotate(angle, pivot)` now turns the whole shape. `Ellipse#rotate` always rotated its `pos`; the two have the same signature and `Body#rotate` calls both over one mixed list, so a body's ellipse parts orbited correctly and its polygon parts did not. Since every `Rect` added to a body becomes a polygon offset by `pos`, the documented feet-and-torso shape was among them. A polygon at the origin — every caller in the engine until now — rotates to exactly the numbers it always did. The builtin's `getBodyShapes` is deliberately unchanged. Its SAT never reads `body.angle`, so an unrotated hitbox around a spinning sprite is the solver reporting what it genuinely collides as; making the overlay follow the visual angle would turn an honest inconsistency into a confident lie. `body.rotate()` does move the shapes, and that case was already reported. One claimed defect did not survive measurement: planck's `getBodyAABB` reading an "enlarged and stale" broadphase proxy. Measured against the exact AABB across steps at 2000 px/s, the difference is a constant 0.001 m (0.03 px) that does not grow with speed, and the larger gap is the polygon skin radius, which is real collision geometry. Left alone. Tests: every fix above has one, each verified to fail with its fix reverted, on all three backends where it applies. Two of my own first attempts were wrong and are recorded as such — a "the centroid does not move" invariant that only holds when the pivot IS the centroid (false on planck), and a rotation check that encoded matter's pivot; both replaced with pivot-agnostic ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
obiot
force-pushed
the
fix/multi-shape-rotation-adapters
branch
from
September 18, 2026 10:46
8d0f9e2 to
a367a05
Compare
matter-adapter 1.3.0 and planck-adapter 1.4.0, dated for publication. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
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.
A body built from several shapes came apart under rotation on matter, and swung away from the body on planck. One root cause, two faces, introduced by the
getBodyShapesrotation fix in matter-adapter 1.2.1 / planck-adapter 1.3.1.What was wrong
It rotated the authored shapes about the renderable's origin. Both halves of that were wrong:
Polygon#rotatemoves a shape's points without itspos, so a shape carrying its offset inposspun about its own origin while the origin stayed put. Several such shapes visibly separated.getLocalCenter()would have been a second wrong answer.)The fix
Stop deriving the pose and read the engine's own geometry back. There is then no pivot to get wrong, and the report describes what actually collides rather than what was authored: an
Ellipseis simulated as a circle of average radius, a concave polygon as convex pieces, a degenerate one as a box, and on planck a shape withisActive: falsehas no fixture at all. The overlay drew none of that truthfully before. Authored shapes are untouched onrenderable.bodyDef.shapes.Also fixed, all from review of the same code
updateShapeat an unchanged angle reported the previous geometry, and a removed renderable stayed pinned: the cache was keyed by angle and cleared on no path but one.isActive: false, which planck and the builtin both honour.def.shapes.Engine, and the builtin
Polygon#rotate(angle, pivot)now turns the whole shape.Ellipse#rotatealways rotated itspos; same signature, andBody#rotatecalls both over one mixed list, so a body's ellipse parts orbited correctly and its polygon parts did not. EveryRectadded to a body becomes a polygon offset bypos, so the documented feet-and-torso shape was among them. A polygon at the origin — every caller in the engine until now — rotates to exactly the numbers it always did.The builtin's
getBodyShapesis deliberately unchanged. Its SAT never readsbody.angle, so an unrotated hitbox around a spinning sprite is the solver reporting what it genuinely collides as; making the overlay follow the visual angle would turn an honest inconsistency into a confident lie. Three tests pin that contract so it is not "fixed" later.One claimed defect that did not survive measurement
planck's
getBodyAABBreading an "enlarged and stale" broadphase proxy. Measured against the exact AABB across steps at 2000 px/s: a constant 0.001 m (0.03 px) that does not grow with speed. The larger gap is the polygon skin radius, which is real collision geometry. Left alone.Verification
platformer,platformer-matter,pool-matter,plinko-planck,isometric-rpg,whac-a-mole,space-invaders,jungle-rabbit) — worth doing becausePolygon#rotateis core, so the blast radius is wider than the adapters.Two of my own first test attempts were wrong and were replaced: a "the centroid does not move" invariant that only holds when the pivot is the centroid (false on planck), and a rotation check that encoded matter's pivot. Both are pivot-agnostic now.
Versions
matter-adapter 1.3.0, planck-adapter 1.4.0 — minor rather than patch, because
getBodyShapesnow returns different object types. Peer stays>=20.0.0: nothing here needs a newer engine, and the readback never callsrotate, so it does not depend on the core fix either.🤖 Generated with Claude Code
https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t