The pack
collision
function collision(): Pack<{ collide: CollideApi }, CollideTopics, { collide: CollideData }>
Overlap rules by tag and positional separation, with no dynamics. Runs after physics-2d when that is composed.
Constants
SHAPE
const SHAPE: { readonly circle: 0; readonly box: 1; }
Per-object: h.collide.shape (0 circle, 1 box — default box) and h.collide.radius (0 = from size).
Types
CollideApi
interface CollideApi
game.collide. Rules are scene-scoped: removed when the scene that made them exits.
on(tagA: string, tagB: string, cb: HitFn): void— Overlap callback for every (a, b) pair of tags, each fixed step, after simulation.solid(tagA: string, tagB: string, opts?: { push?: 'a' | 'b' | 'both'; cb?: HitFn }): void— Keep A out of B by moving A (push: 'a', default), B, or both. Positional only — no velocity.overlaps(h: Handle, tag?: string): Handle[]— Every live object overlappinghright now, optionally only those taggedtag.raycast(x: number, y: number, dx: number, dy: number, maxDist: number, tag?: string): Hit | null— The nearest object along the ray withinmaxDistunits, or null;taglimits the candidates. Tests bounding boxes (a circle as its square). Emitsdebug.ray.test(a: Handle, b: Handle): boolean— Do these two overlap right now?contact(a: Handle, b: Handle): Contact | null— WHICH SIDE ofaisbon, and how deep — or null when they do not overlap. The side is the axis of minimum penetration, which is what a platformer's "am I standing on it" and a breakout's "which face did the ball hit" both actually ask. It honours wrap, so a pair straddling the seam measures as neighbours.continuous: boolean— CONTINUOUS TESTING for the whole pack, live. Off by default. A discrete test asks "do these overlap NOW", and a thing that moves further in one step than the thing it should have hit is wide simply leaps over it: a bullet at 3000 units/s travels 50 units a step and a 10-unit wall is not there for any of them. With this on, a rule tests the object's whole PATH since the last step — the broadphase covers the sweep, and the pair is sampled along it — so the hit lands where it would have. It costs nothing for an object that did not move far: the sampling only kicks in when the motion is longer than the smaller of the two shapes. Sensible for bullets and never needed for walking speeds.
CollideData
interface CollideData
The collide columns every object carries.
shape: number— 0 circle, 1 box. Default 1.radius: number— Circle radius, world units; 0 (default) means half the larger of the scaled width and height.
CollideTopics
interface CollideTopics
The signals this pack emits.
'collision.hit': { a: string; b: string; rule: string }— One overlapping pair per rule per step (capacity 256): the two ids and the rule's name ("a×b"foron,"a⊥b"forsolid).
Contact
interface Contact
Which side of a the contact is on and how deep it goes. The side is the axis of MINIMUM penetration, so
pushing a out by overlap along it separates the pair — and 'bottom' is what "am I standing on it" means.
side: 'left' | 'right' | 'top' | 'bottom'— The side ofathatbis against.overlap: number— The penetration depth on that axis, world units.
Hit
interface Hit
A raycast result.
handle: Handle— The object hit.x: number— Hit point x, world units (on the object's bounding box).y: number— Hit point y, world units.dist: number— Distance from the ray origin, world units.
HitFn
type HitFn = (a: Handle, b: Handle, contact: Contact) => void
A rule callback: (a, b) in the order the rule named its tags, plus the contact, read before any separation.


