@flare-engine/dynamics 0.4.8 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/body.ts","../src/manifold.ts","../src/pool.ts","../src/world.ts"],"sourcesContent":["/** A rigid body: an oriented box (OBB) with linear + angular state.\n *\n * Boxes only. Mass properties are derived from the box extents; a body with\n * `invMass === 0` is static (floor / walls). */\n\nimport type { Vec2 } from '@flare-engine/math';\n\nexport interface Body {\n /** Center of mass, world space. */\n position: Vec2;\n /** Linear velocity. */\n velocity: Vec2;\n /** Orientation in radians. */\n angle: number;\n /** Angular velocity in radians/second. */\n angularVelocity: number;\n /** Half-extents (half width, half height) in the body's local frame. */\n half: Vec2;\n /** 1 / mass. `0` marks a static (immovable, infinite-mass) body. */\n invMass: number;\n /** 1 / rotational inertia. `0` locks rotation (all static bodies). */\n invInertia: number;\n /** Bounciness in [0, 1]. */\n restitution: number;\n /** Coulomb friction coefficient (≈0 slippery … ≈1 grippy). */\n friction: number;\n}\n\nexport interface BodyOptions {\n position: Vec2;\n half: Vec2;\n /** Uniform density; drives both mass and rotational inertia. Ignored when `static`. */\n density?: number;\n velocity?: Vec2;\n angle?: number;\n angularVelocity?: number;\n restitution?: number;\n friction?: number;\n /** Static bodies have infinite mass and never move. */\n static?: boolean;\n}\n\nexport function createBody(opts: BodyOptions): Body {\n const half = opts.half;\n const density = opts.density ?? 1;\n\n let invMass = 0;\n let invInertia = 0;\n if (!opts.static) {\n const w = half.x * 2;\n const h = half.y * 2;\n const mass = density * w * h;\n // Solid-rectangle inertia about its center: m·(w² + h²) / 12.\n const inertia = (mass * (w * w + h * h)) / 12;\n invMass = mass > 0 ? 1 / mass : 0;\n invInertia = inertia > 0 ? 1 / inertia : 0;\n }\n\n return {\n position: { x: opts.position.x, y: opts.position.y },\n velocity: opts.velocity ? { x: opts.velocity.x, y: opts.velocity.y } : { x: 0, y: 0 },\n angle: opts.angle ?? 0,\n angularVelocity: opts.angularVelocity ?? 0,\n half: { x: half.x, y: half.y },\n invMass,\n invInertia,\n restitution: opts.restitution ?? 0.2,\n friction: opts.friction ?? 0.4,\n };\n}\n\nexport function isStatic(b: Body): boolean {\n return b.invMass === 0;\n}\n\n/** The four world-space corners of `b`, written as x,y pairs at `offset`. */\nexport function writeBoxVertices(b: Body, out: Float64Array, offset: number): void {\n const c = Math.cos(b.angle);\n const s = Math.sin(b.angle);\n const hx = b.half.x;\n const hy = b.half.y;\n const px = b.position.x;\n const py = b.position.y;\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const lx = i === 0 || i === 3 ? -hx : hx;\n const ly = i === 0 || i === 1 ? -hy : hy;\n out[o] = px + (lx * c - ly * s);\n out[o + 1] = py + (lx * s + ly * c);\n o += 2;\n }\n}\n\n/** Outward face normals, one per edge `i → i+1`, guaranteed to point away\n * from `(cx, cy)` regardless of winding or the frame's y-direction. Reads\n * four already-written corners from `verts` at `vOffset`. */\nexport function writeBoxNormals(\n verts: Float64Array,\n vOffset: number,\n cx: number,\n cy: number,\n out: Float64Array,\n offset: number,\n): void {\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const j = (i + 1) % 4;\n const ax = verts[vOffset + i * 2]!;\n const ay = verts[vOffset + i * 2 + 1]!;\n const bx = verts[vOffset + j * 2]!;\n const by = verts[vOffset + j * 2 + 1]!;\n const ex = bx - ax;\n const ey = by - ay;\n const l = Math.sqrt(ex * ex + ey * ey) || 1;\n let nx = ey / l;\n let ny = -ex / l;\n const midX = (ax + bx) / 2 - cx;\n const midY = (ay + by) / 2 - cy;\n if (nx * midX + ny * midY < 0) {\n nx = -nx;\n ny = -ny;\n }\n out[o] = nx;\n out[o + 1] = ny;\n o += 2;\n }\n}\n\n/** World-space axis-aligned bounds of four already-written corners, written\n * as `minX, minY, maxX, maxY` at `offset`. Does not recompute vertices. */\nexport function writeAabb(\n verts: Float64Array,\n vOffset: number,\n out: Float64Array,\n offset: number,\n): void {\n const x0 = verts[vOffset]!;\n const y0 = verts[vOffset + 1]!;\n let minX = x0;\n let minY = y0;\n let maxX = x0;\n let maxY = y0;\n for (let i = 1; i < 4; i++) {\n const x = verts[vOffset + i * 2]!;\n const y = verts[vOffset + i * 2 + 1]!;\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n out[offset] = minX;\n out[offset + 1] = minY;\n out[offset + 2] = maxX;\n out[offset + 3] = maxY;\n}\n","/** OBB-OBB collision: separating-axis test + reference/incident face clipping,\n * building a 1-2 point contact manifold with per-point penetration depth.\n *\n * Ported from a prototype implementation onto flat `Float64Array` body geometry\n * and a pooled `Manifold`/`ManifoldPoint` — no per-call allocation.\n * The algorithm is the standard 2D clipping manifold (reference face → incident\n * face → Sutherland-Hodgman side-plane clip), stable for stacked / tumbling boxes. */\n\nimport { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport type { Manifold } from './pool.js';\n\n// Module-level scratch — collideInto is called every step (broad-phase pairs\n// × narrow-phase), so nothing here may allocate.\nconst faceScratch = new Int32Array(1);\nconst clipScratch = new Float64Array(4);\nconst normalScratch: Vec2 = { x: 0, y: 0 };\nconst tangentScratch: Vec2 = { x: 0, y: 0 };\n\n/** Index (0-3) of the vertex of the box at `offset` with greatest support\n * along direction `(dx, dy)`. */\nfunction supportIndex(verts: Float64Array, offset: number, dx: number, dy: number): number {\n let bestDot = Number.NEGATIVE_INFINITY;\n let best = 0;\n for (let i = 0; i < 4; i++) {\n const x = verts[offset + i * 2]!;\n const y = verts[offset + i * 2 + 1]!;\n const d = x * dx + y * dy;\n if (d > bestDot) {\n bestDot = d;\n best = i;\n }\n }\n return best;\n}\n\n/** Max separation of the box at `otherOffset` from the faces of the box at\n * `selfOffset`. Positive → a gap exists on some axis (no overlap); negative\n * → the least-penetrating axis. Writes the winning face index into\n * `faceScratch[0]`. */\nfunction leastPenetration(\n verts: Float64Array,\n normals: Float64Array,\n selfOffset: number,\n otherOffset: number,\n): number {\n let best = Number.NEGATIVE_INFINITY;\n let bestFace = 0;\n for (let i = 0; i < 4; i++) {\n const nx = normals[selfOffset + i * 2]!;\n const ny = normals[selfOffset + i * 2 + 1]!;\n const vx = verts[selfOffset + i * 2]!;\n const vy = verts[selfOffset + i * 2 + 1]!;\n const si = supportIndex(verts, otherOffset, -nx, -ny);\n const sx = verts[otherOffset + si * 2]!;\n const sy = verts[otherOffset + si * 2 + 1]!;\n const sep = nx * (sx - vx) + ny * (sy - vy);\n if (sep > best) {\n best = sep;\n bestFace = i;\n }\n }\n faceScratch[0] = bestFace;\n return best;\n}\n\n/** Bias toward keeping the current reference face for solver coherence. */\nfunction biasGreaterThan(a: number, b: number): boolean {\n return a >= b * 0.95 + a * 0.01;\n}\n\n/** Index (0-3) of the incident box's face whose normal is most anti-parallel\n * to the reference normal. */\nfunction incidentFaceIndex(\n refNx: number,\n refNy: number,\n incNormals: Float64Array,\n incOffset: number,\n): number {\n let minDot = Number.POSITIVE_INFINITY;\n let index = 0;\n for (let i = 0; i < 4; i++) {\n const nx = incNormals[incOffset + i * 2]!;\n const ny = incNormals[incOffset + i * 2 + 1]!;\n const d = refNx * nx + refNy * ny;\n if (d < minDot) {\n minDot = d;\n index = i;\n }\n }\n return index;\n}\n\n/** Clips segment `(ax,ay)-(bx,by)` to the halfspace `dot(n, p) <= c`. Writes\n * 0-2 surviving points into `clipScratch` and returns the count. */\nfunction clip(\n nx: number,\n ny: number,\n c: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n): number {\n let count = 0;\n const d1 = nx * ax + ny * ay - c;\n const d2 = nx * bx + ny * by - c;\n if (d1 <= 0) {\n clipScratch[count * 2] = ax;\n clipScratch[count * 2 + 1] = ay;\n count++;\n }\n if (d2 <= 0) {\n clipScratch[count * 2] = bx;\n clipScratch[count * 2 + 1] = by;\n count++;\n }\n if (d1 * d2 < 0) {\n const t = d1 / (d1 - d2);\n clipScratch[count * 2] = ax + (bx - ax) * t;\n clipScratch[count * 2 + 1] = ay + (by - ay) * t;\n count++;\n }\n return count;\n}\n\n/** Builds a contact manifold for boxes `a` (index `ai`) and `b` (index `bi`)\n * into `out`, reading pre-written geometry from `verts`/`normals` (8 doubles\n * per body). Returns `false` — leaving `out` untouched — when the boxes do\n * not overlap. */\nexport function collideInto(\n a: Body,\n b: Body,\n verts: Float64Array,\n normals: Float64Array,\n ai: number,\n bi: number,\n out: Manifold,\n): boolean {\n const aOff = ai * 8;\n const bOff = bi * 8;\n\n const sepA = leastPenetration(verts, normals, aOff, bOff);\n if (sepA >= 0) return false;\n const faceA = faceScratch[0]!;\n\n const sepB = leastPenetration(verts, normals, bOff, aOff);\n if (sepB >= 0) return false;\n const faceB = faceScratch[0]!;\n\n const refIsA = biasGreaterThan(sepA, sepB);\n const refOffset = refIsA ? aOff : bOff;\n const incOffset = refIsA ? bOff : aOff;\n const refIndex = refIsA ? faceA : faceB;\n\n const refNx = normals[refOffset + refIndex * 2]!;\n const refNy = normals[refOffset + refIndex * 2 + 1]!;\n const refJ = (refIndex + 1) % 4;\n const v1x = verts[refOffset + refIndex * 2]!;\n const v1y = verts[refOffset + refIndex * 2 + 1]!;\n const v2x = verts[refOffset + refJ * 2]!;\n const v2y = verts[refOffset + refJ * 2 + 1]!;\n\n const incIndex = incidentFaceIndex(refNx, refNy, normals, incOffset);\n const incJ = (incIndex + 1) % 4;\n const iax = verts[incOffset + incIndex * 2]!;\n const iay = verts[incOffset + incIndex * 2 + 1]!;\n const ibx = verts[incOffset + incJ * 2]!;\n const iby = verts[incOffset + incJ * 2 + 1]!;\n\n // Tangent along the reference face; its two end-planes form the clip slab.\n // `|| 1` guards a degenerate (zero- or near-zero-extent) reference edge the\n // same way `writeBoxNormals` already does for the identical computation —\n // without it, a squared-length underflow makes this a division by zero.\n let sideX = v2x - v1x;\n let sideY = v2y - v1y;\n const sideInv = 1 / (Math.sqrt(sideX * sideX + sideY * sideY) || 1);\n sideX *= sideInv;\n sideY *= sideInv;\n const negSide = -(sideX * v1x + sideY * v1y);\n const posSide = sideX * v2x + sideY * v2y;\n\n let count = clip(-sideX, -sideY, negSide, iax, iay, ibx, iby);\n if (count < 2) return false;\n const c0x = clipScratch[0]!;\n const c0y = clipScratch[1]!;\n const c1x = clipScratch[2]!;\n const c1y = clipScratch[3]!;\n\n count = clip(sideX, sideY, posSide, c0x, c0y, c1x, c1y);\n if (count < 2) return false;\n const p0x = clipScratch[0]!;\n const p0y = clipScratch[1]!;\n const p1x = clipScratch[2]!;\n const p1y = clipScratch[3]!;\n\n const refC = refNx * v1x + refNy * v1y;\n\n let written = 0;\n const sep0 = refNx * p0x + refNy * p0y - refC;\n if (sep0 <= 0) {\n const point = out.points[written]!;\n point.x = p0x;\n point.y = p0y;\n point.penetration = -sep0;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n const sep1 = refNx * p1x + refNy * p1y - refC;\n if (sep1 <= 0) {\n const point = out.points[written]!;\n point.x = p1x;\n point.y = p1y;\n point.penetration = -sep1;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n if (written === 0) return false;\n\n // Manifold normal must point a → b; refNormal points off the reference\n // box, which is b when we flipped, so negate in that case.\n const normalX = refIsA ? refNx : -refNx;\n const normalY = refIsA ? refNy : -refNy;\n\n out.a = a;\n out.b = b;\n out.normalX = normalX;\n out.normalY = normalY;\n normalScratch.x = normalX;\n normalScratch.y = normalY;\n Vec2.perp(normalScratch, tangentScratch);\n out.tangentX = tangentScratch.x;\n out.tangentY = tangentScratch.y;\n out.friction = Math.sqrt(a.friction * b.friction);\n out.pointCount = written;\n return true;\n}\n","/** Per-world scratch: contact manifolds and the flat vertex/normal/AABB\n * buffers `world.ts` writes body geometry into each step. Growth doubles\n * capacity and rebuilds arrays from scratch — pool contents are fully\n * rewritten every step, so nothing needs to be copied forward. */\n\nimport type { Body } from './body.js';\n\nexport interface ManifoldPoint {\n x: number;\n y: number;\n penetration: number;\n normalImpulse: number;\n tangentImpulse: number;\n raX: number;\n raY: number;\n rbX: number;\n rbY: number;\n massNormal: number;\n massTangent: number;\n bias: number;\n}\n\nexport interface Manifold {\n a: Body;\n b: Body;\n normalX: number;\n normalY: number;\n tangentX: number;\n tangentY: number;\n friction: number;\n pointCount: number;\n points: ManifoldPoint[];\n}\n\nexport interface SolverPool {\n manifolds: Manifold[];\n manifoldCount: number;\n verts: Float64Array;\n normals: Float64Array;\n aabbs: Float64Array;\n capacityBodies: number;\n}\n\nfunction createManifoldPoint(): ManifoldPoint {\n return {\n x: 0,\n y: 0,\n penetration: 0,\n normalImpulse: 0,\n tangentImpulse: 0,\n raX: 0,\n raY: 0,\n rbX: 0,\n rbY: 0,\n massNormal: 0,\n massTangent: 0,\n bias: 0,\n };\n}\n\nfunction createManifold(): Manifold {\n return {\n a: undefined as unknown as Body,\n b: undefined as unknown as Body,\n normalX: 0,\n normalY: 0,\n tangentX: 0,\n tangentY: 0,\n friction: 0,\n pointCount: 0,\n points: [createManifoldPoint(), createManifoldPoint()],\n };\n}\n\nexport function createPool(): SolverPool {\n return {\n manifolds: [],\n manifoldCount: 0,\n verts: new Float64Array(0),\n normals: new Float64Array(0),\n aabbs: new Float64Array(0),\n capacityBodies: 0,\n };\n}\n\n/** Grows the per-body geometry buffers to hold at least `count` bodies (8\n * doubles per body for verts/normals, 4 for the AABB). No-op if already\n * large enough. */\nexport function ensureBodyCapacity(pool: SolverPool, count: number): void {\n if (count <= pool.capacityBodies) return;\n let cap = pool.capacityBodies === 0 ? 8 : pool.capacityBodies;\n while (cap < count) cap *= 2;\n pool.verts = new Float64Array(cap * 8);\n pool.normals = new Float64Array(cap * 8);\n pool.aabbs = new Float64Array(cap * 4);\n pool.capacityBodies = cap;\n}\n\n/** Grows `pool.manifolds` to hold at least `count` entries, appending\n * fully-formed `Manifold` objects (each with two pre-created points).\n * Never truncates — manifolds past `manifoldCount` simply go unused, which\n * is what keeps the steady state allocation-free. */\nexport function ensureManifoldCapacity(pool: SolverPool, count: number): void {\n while (pool.manifolds.length < count) {\n pool.manifolds.push(createManifold());\n }\n}\n","/** The rigid-body world: gravity integration + a sequential-impulse contact\n * solver (Box2D-lite structure) with Coulomb friction and Baumgarte\n * de-penetration, plus a restitution term for bounce.\n *\n * Modeled as a plain object + free functions (no class) to match the house\n * style. The pool lives in a module-level `WeakMap` so `World` keeps its\n * frozen three-field public shape — one lookup per `step`, not per body. */\n\nimport type { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport { isStatic, writeAabb, writeBoxNormals, writeBoxVertices } from './body.js';\nimport { collideInto } from './manifold.js';\nimport { createPool, ensureBodyCapacity, ensureManifoldCapacity, type SolverPool } from './pool.js';\n\nexport interface WorldOptions {\n /** Acceleration applied to every dynamic body each step (px/s²). */\n gravity?: Vec2;\n /** Sequential-impulse iterations per step. More = stiffer stacks. */\n iterations?: number;\n}\n\nexport interface World {\n bodies: Body[];\n gravity: Vec2;\n iterations: number;\n}\n\nconst BAUMGARTE = 0.2;\nconst PENETRATION_SLOP = 0.5;\n\nconst pools = new WeakMap<World, SolverPool>();\n\nexport function createWorld(opts: WorldOptions = {}): World {\n const world: World = {\n bodies: [],\n gravity: opts.gravity ? { x: opts.gravity.x, y: opts.gravity.y } : { x: 0, y: 1800 },\n iterations: opts.iterations ?? 10,\n };\n pools.set(world, createPool());\n return world;\n}\n\nexport function addBody(world: World, body: Body): Body {\n world.bodies.push(body);\n return body;\n}\n\n/** Relative velocity at a contact projected onto (dx, dy). The vector never\n * escapes — its only use is a dot product, so it collapses to two scalars. */\nfunction relativeVelocityAlong(\n a: Body,\n b: Body,\n raX: number,\n raY: number,\n rbX: number,\n rbY: number,\n dx: number,\n dy: number,\n): number {\n const vax = a.velocity.x - a.angularVelocity * raY;\n const vay = a.velocity.y + a.angularVelocity * raX;\n const vbx = b.velocity.x - b.angularVelocity * rbY;\n const vby = b.velocity.y + b.angularVelocity * rbX;\n return (vbx - vax) * dx + (vby - vay) * dy;\n}\n\nfunction applyImpulse(body: Body, ix: number, iy: number, rx: number, ry: number): void {\n if (body.invMass === 0) return;\n body.velocity.x += ix * body.invMass;\n body.velocity.y += iy * body.invMass;\n body.angularVelocity += body.invInertia * (rx * iy - ry * ix);\n}\n\n/** Writes every body's world-space vertices, outward normals, and AABB into\n * `pool`, once per step, so the broadphase pair loop never recomputes them. */\nfunction writeGeometry(pool: SolverPool, bodies: Body[]): void {\n ensureBodyCapacity(pool, bodies.length);\n for (let i = 0; i < bodies.length; i++) {\n const b = bodies[i]!;\n const vOffset = i * 8;\n const aOffset = i * 4;\n writeBoxVertices(b, pool.verts, vOffset);\n writeBoxNormals(pool.verts, vOffset, b.position.x, b.position.y, pool.normals, vOffset);\n writeAabb(pool.verts, vOffset, pool.aabbs, aOffset);\n }\n}\n\n/** Broadphase (precomputed-AABB reject) + narrowphase (OBB manifold),\n * writing surviving contacts into `pool.manifolds[0..manifoldCount)`. */\nfunction collectManifolds(pool: SolverPool, bodies: Body[]): void {\n pool.manifoldCount = 0;\n const aabbs = pool.aabbs;\n for (let i = 0; i < bodies.length; i++) {\n const a = bodies[i]!;\n const aOff = i * 4;\n const aMinX = aabbs[aOff]!;\n const aMinY = aabbs[aOff + 1]!;\n const aMaxX = aabbs[aOff + 2]!;\n const aMaxY = aabbs[aOff + 3]!;\n for (let j = i + 1; j < bodies.length; j++) {\n const b = bodies[j]!;\n if (isStatic(a) && isStatic(b)) continue;\n const bOff = j * 4;\n const bMinX = aabbs[bOff]!;\n const bMinY = aabbs[bOff + 1]!;\n const bMaxX = aabbs[bOff + 2]!;\n const bMaxY = aabbs[bOff + 3]!;\n if (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) continue;\n ensureManifoldCapacity(pool, pool.manifoldCount + 1);\n const m = pool.manifolds[pool.manifoldCount]!;\n if (collideInto(a, b, pool.verts, pool.normals, i, j, m)) pool.manifoldCount++;\n }\n }\n}\n\n/** Derives per-point solver mass terms and the velocity/position bias for\n * every live manifold, writing them onto the manifold's own contact points. */\nfunction prepare(pool: SolverPool, gravity: Vec2, dt: number): void {\n const restThreshold = Math.hypot(gravity.x, gravity.y) * dt;\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b } = m;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n const restitution = Math.min(a.restitution, b.restitution);\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.x - a.position.x;\n const raY = cp.y - a.position.y;\n const rbX = cp.x - b.position.x;\n const rbY = cp.y - b.position.y;\n\n const rnA = raX * normalY - raY * normalX;\n const rnB = rbX * normalY - rbY * normalX;\n const kNormal = a.invMass + b.invMass + a.invInertia * rnA * rnA + b.invInertia * rnB * rnB;\n\n const rtA = raX * tangentY - raY * tangentX;\n const rtB = rbX * tangentY - rbY * tangentX;\n const kTangent = a.invMass + b.invMass + a.invInertia * rtA * rtA + b.invInertia * rtB * rtB;\n\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n const restitutionBias = vn < -restThreshold ? -restitution * vn : 0;\n const baumgarte = (BAUMGARTE / dt) * Math.max(0, cp.penetration - PENETRATION_SLOP);\n\n cp.raX = raX;\n cp.raY = raY;\n cp.rbX = rbX;\n cp.rbY = rbY;\n cp.massNormal = kNormal > 0 ? 1 / kNormal : 0;\n cp.massTangent = kTangent > 0 ? 1 / kTangent : 0;\n cp.bias = restitutionBias + baumgarte;\n }\n }\n}\n\n/** One sequential-impulse sweep over every live manifold's contact points. */\nfunction solve(pool: SolverPool): void {\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b, friction } = m;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.raX;\n const raY = cp.raY;\n const rbX = cp.rbX;\n const rbY = cp.rbY;\n\n // Normal impulse (clamped >= 0 so contacts push, never pull).\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n let dPn = cp.massNormal * (-vn + cp.bias);\n const newNormal = Math.max(cp.normalImpulse + dPn, 0);\n dPn = newNormal - cp.normalImpulse;\n cp.normalImpulse = newNormal;\n const pnX = normalX * dPn;\n const pnY = normalY * dPn;\n applyImpulse(a, -pnX, -pnY, raX, raY);\n applyImpulse(b, pnX, pnY, rbX, rbY);\n\n // Friction impulse (clamped to the Coulomb cone mu*Pn).\n const vt = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, tangentX, tangentY);\n let dPt = cp.massTangent * -vt;\n const maxPt = friction * cp.normalImpulse;\n const newTangent = Math.max(-maxPt, Math.min(maxPt, cp.tangentImpulse + dPt));\n dPt = newTangent - cp.tangentImpulse;\n cp.tangentImpulse = newTangent;\n const ptX = tangentX * dPt;\n const ptY = tangentY * dPt;\n applyImpulse(a, -ptX, -ptY, raX, raY);\n applyImpulse(b, ptX, ptY, rbX, rbY);\n }\n }\n}\n\n/** Advance the world by `dt` seconds (fixed steps recommended).\n *\n * Resting contacts settle to a stable *position* but retain a small, bounded\n * velocity oscillation at the penetration-slop equilibrium — a sequential-\n * impulse solver with Baumgarte bias and no sleeping does not converge\n * velocity to exactly zero at rest, only the position does. This is\n * inherent to the algorithm, not a bug; sleeping and damping are\n * deliberately out of scope for v1. A consumer needing pixel-exact\n * stillness should clamp small resting velocities at its own layer. */\nexport function step(world: World, dt: number): void {\n if (dt <= 0) return;\n // `World` is a public interface with plain fields, so a consumer can hand\n // back a structurally valid value that never went through `createWorld`\n // (spread copy, `structuredClone`, state round-tripping) and is therefore\n // absent from `pools`. Lazily adopt it instead of crashing on a missing\n // entry; steady-state callers (worlds from `createWorld`) never pay this\n // branch since their pool is already registered.\n let pool = pools.get(world);\n if (!pool) {\n pool = createPool();\n pools.set(world, pool);\n }\n const { bodies } = world;\n\n // 1. Integrate velocities (apply gravity to dynamic bodies).\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body)) continue;\n body.velocity.x += world.gravity.x * dt;\n body.velocity.y += world.gravity.y * dt;\n }\n\n // 2. Broadphase (precomputed AABB reject) + narrowphase (OBB manifold).\n writeGeometry(pool, bodies);\n collectManifolds(pool, bodies);\n\n // 3. Solve velocity constraints.\n prepare(pool, world.gravity, dt);\n for (let iter = 0; iter < world.iterations; iter++) {\n solve(pool);\n }\n\n // 4. Integrate positions.\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body)) continue;\n body.position.x += body.velocity.x * dt;\n body.position.y += body.velocity.y * dt;\n body.angle += body.angularVelocity * dt;\n }\n}\n"],"mappings":";;;AA0CA,SAAgB,WAAW,MAAyB;CAClD,MAAM,OAAO,KAAK;CAClB,MAAM,UAAU,KAAK,WAAW;CAEhC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,CAAC,KAAK,QAAQ;EAChB,MAAM,IAAI,KAAK,IAAI;EACnB,MAAM,IAAI,KAAK,IAAI;EACnB,MAAM,OAAO,UAAU,IAAI;EAE3B,MAAM,UAAW,QAAQ,IAAI,IAAI,IAAI,KAAM;EAC3C,UAAU,OAAO,IAAI,IAAI,OAAO;EAChC,aAAa,UAAU,IAAI,IAAI,UAAU;CAC3C;CAEA,OAAO;EACL,UAAU;GAAE,GAAG,KAAK,SAAS;GAAG,GAAG,KAAK,SAAS;EAAE;EACnD,UAAU,KAAK,WAAW;GAAE,GAAG,KAAK,SAAS;GAAG,GAAG,KAAK,SAAS;EAAE,IAAI;GAAE,GAAG;GAAG,GAAG;EAAE;EACpF,OAAO,KAAK,SAAS;EACrB,iBAAiB,KAAK,mBAAmB;EACzC,MAAM;GAAE,GAAG,KAAK;GAAG,GAAG,KAAK;EAAE;EAC7B;EACA;EACA,aAAa,KAAK,eAAe;EACjC,UAAU,KAAK,YAAY;CAC7B;AACF;AAEA,SAAgB,SAAS,GAAkB;CACzC,OAAO,EAAE,YAAY;AACvB;;AAGA,SAAgB,iBAAiB,GAAS,KAAmB,QAAsB;CACjF,MAAM,IAAI,KAAK,IAAI,EAAE,KAAK;CAC1B,MAAM,IAAI,KAAK,IAAI,EAAE,KAAK;CAC1B,MAAM,KAAK,EAAE,KAAK;CAClB,MAAM,KAAK,EAAE,KAAK;CAClB,MAAM,KAAK,EAAE,SAAS;CACtB,MAAM,KAAK,EAAE,SAAS;CACtB,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK;EACtC,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK;EACtC,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK;EAC7B,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK;EACjC,KAAK;CACP;AACF;;;;AAKA,SAAgB,gBACd,OACA,SACA,IACA,IACA,KACA,QACM;CACN,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,KAAK,IAAI,KAAK;EACpB,MAAM,KAAK,MAAM,UAAU,IAAI;EAC/B,MAAM,KAAK,MAAM,UAAU,IAAI,IAAI;EACnC,MAAM,KAAK,MAAM,UAAU,IAAI;EAC/B,MAAM,KAAK,MAAM,UAAU,IAAI,IAAI;EACnC,MAAM,KAAK,KAAK;EAChB,MAAM,KAAK,KAAK;EAChB,MAAM,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE,KAAK;EAC1C,IAAI,KAAK,KAAK;EACd,IAAI,KAAK,CAAC,KAAK;EACf,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,IAAI,KAAK,OAAO,KAAK,OAAO,GAAG;GAC7B,KAAK,CAAC;GACN,KAAK,CAAC;EACR;EACA,IAAI,KAAK;EACT,IAAI,IAAI,KAAK;EACb,KAAK;CACP;AACF;;;AAIA,SAAgB,UACd,OACA,SACA,KACA,QACM;CACN,MAAM,KAAK,MAAM;CACjB,MAAM,KAAK,MAAM,UAAU;CAC3B,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,IAAI,MAAM,UAAU,IAAI;EAC9B,MAAM,IAAI,MAAM,UAAU,IAAI,IAAI;EAClC,IAAI,IAAI,MAAM,OAAO;EACrB,IAAI,IAAI,MAAM,OAAO;EACrB,IAAI,IAAI,MAAM,OAAO;EACrB,IAAI,IAAI,MAAM,OAAO;CACvB;CACA,IAAI,UAAU;CACd,IAAI,SAAS,KAAK;CAClB,IAAI,SAAS,KAAK;CAClB,IAAI,SAAS,KAAK;AACpB;;;;;;;;;AC5IA,MAAM,8BAAc,IAAI,WAAW,CAAC;AACpC,MAAM,8BAAc,IAAI,aAAa,CAAC;AACtC,MAAM,gBAAsB;CAAE,GAAG;CAAG,GAAG;AAAE;AACzC,MAAM,iBAAuB;CAAE,GAAG;CAAG,GAAG;AAAE;;;AAI1C,SAAS,aAAa,OAAqB,QAAgB,IAAY,IAAoB;CACzF,IAAI,UAAU,OAAO;CACrB,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,IAAI,MAAM,SAAS,IAAI;EAC7B,MAAM,IAAI,MAAM,SAAS,IAAI,IAAI;EACjC,MAAM,IAAI,IAAI,KAAK,IAAI;EACvB,IAAI,IAAI,SAAS;GACf,UAAU;GACV,OAAO;EACT;CACF;CACA,OAAO;AACT;;;;;AAMA,SAAS,iBACP,OACA,SACA,YACA,aACQ;CACR,IAAI,OAAO,OAAO;CAClB,IAAI,WAAW;CACf,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,KAAK,QAAQ,aAAa,IAAI;EACpC,MAAM,KAAK,QAAQ,aAAa,IAAI,IAAI;EACxC,MAAM,KAAK,MAAM,aAAa,IAAI;EAClC,MAAM,KAAK,MAAM,aAAa,IAAI,IAAI;EACtC,MAAM,KAAK,aAAa,OAAO,aAAa,CAAC,IAAI,CAAC,EAAE;EACpD,MAAM,KAAK,MAAM,cAAc,KAAK;EACpC,MAAM,KAAK,MAAM,cAAc,KAAK,IAAI;EACxC,MAAM,MAAM,MAAM,KAAK,MAAM,MAAM,KAAK;EACxC,IAAI,MAAM,MAAM;GACd,OAAO;GACP,WAAW;EACb;CACF;CACA,YAAY,KAAK;CACjB,OAAO;AACT;;AAGA,SAAS,gBAAgB,GAAW,GAAoB;CACtD,OAAO,KAAK,IAAI,MAAO,IAAI;AAC7B;;;AAIA,SAAS,kBACP,OACA,OACA,YACA,WACQ;CACR,IAAI,SAAS,OAAO;CACpB,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,KAAK,WAAW,YAAY,IAAI;EACtC,MAAM,KAAK,WAAW,YAAY,IAAI,IAAI;EAC1C,MAAM,IAAI,QAAQ,KAAK,QAAQ;EAC/B,IAAI,IAAI,QAAQ;GACd,SAAS;GACT,QAAQ;EACV;CACF;CACA,OAAO;AACT;;;AAIA,SAAS,KACP,IACA,IACA,GACA,IACA,IACA,IACA,IACQ;CACR,IAAI,QAAQ;CACZ,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK;CAC/B,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK;CAC/B,IAAI,MAAM,GAAG;EACX,YAAY,QAAQ,KAAK;EACzB,YAAY,QAAQ,IAAI,KAAK;EAC7B;CACF;CACA,IAAI,MAAM,GAAG;EACX,YAAY,QAAQ,KAAK;EACzB,YAAY,QAAQ,IAAI,KAAK;EAC7B;CACF;CACA,IAAI,KAAK,KAAK,GAAG;EACf,MAAM,IAAI,MAAM,KAAK;EACrB,YAAY,QAAQ,KAAK,MAAM,KAAK,MAAM;EAC1C,YAAY,QAAQ,IAAI,KAAK,MAAM,KAAK,MAAM;EAC9C;CACF;CACA,OAAO;AACT;;;;;AAMA,SAAgB,YACd,GACA,GACA,OACA,SACA,IACA,IACA,KACS;CACT,MAAM,OAAO,KAAK;CAClB,MAAM,OAAO,KAAK;CAElB,MAAM,OAAO,iBAAiB,OAAO,SAAS,MAAM,IAAI;CACxD,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,QAAQ,YAAY;CAE1B,MAAM,OAAO,iBAAiB,OAAO,SAAS,MAAM,IAAI;CACxD,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,QAAQ,YAAY;CAE1B,MAAM,SAAS,gBAAgB,MAAM,IAAI;CACzC,MAAM,YAAY,SAAS,OAAO;CAClC,MAAM,YAAY,SAAS,OAAO;CAClC,MAAM,WAAW,SAAS,QAAQ;CAElC,MAAM,QAAQ,QAAQ,YAAY,WAAW;CAC7C,MAAM,QAAQ,QAAQ,YAAY,WAAW,IAAI;CACjD,MAAM,QAAQ,WAAW,KAAK;CAC9B,MAAM,MAAM,MAAM,YAAY,WAAW;CACzC,MAAM,MAAM,MAAM,YAAY,WAAW,IAAI;CAC7C,MAAM,MAAM,MAAM,YAAY,OAAO;CACrC,MAAM,MAAM,MAAM,YAAY,OAAO,IAAI;CAEzC,MAAM,WAAW,kBAAkB,OAAO,OAAO,SAAS,SAAS;CACnE,MAAM,QAAQ,WAAW,KAAK;CAC9B,MAAM,MAAM,MAAM,YAAY,WAAW;CACzC,MAAM,MAAM,MAAM,YAAY,WAAW,IAAI;CAC7C,MAAM,MAAM,MAAM,YAAY,OAAO;CACrC,MAAM,MAAM,MAAM,YAAY,OAAO,IAAI;CAMzC,IAAI,QAAQ,MAAM;CAClB,IAAI,QAAQ,MAAM;CAClB,MAAM,UAAU,KAAK,KAAK,KAAK,QAAQ,QAAQ,QAAQ,KAAK,KAAK;CACjE,SAAS;CACT,SAAS;CACT,MAAM,UAAU,EAAE,QAAQ,MAAM,QAAQ;CACxC,MAAM,UAAU,QAAQ,MAAM,QAAQ;CAEtC,IAAI,QAAQ,KAAK,CAAC,OAAO,CAAC,OAAO,SAAS,KAAK,KAAK,KAAK,GAAG;CAC5D,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CAExB,QAAQ,KAAK,OAAO,OAAO,SAAS,KAAK,KAAK,KAAK,GAAG;CACtD,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CACxB,MAAM,MAAM,YAAY;CAExB,MAAM,OAAO,QAAQ,MAAM,QAAQ;CAEnC,IAAI,UAAU;CACd,MAAM,OAAO,QAAQ,MAAM,QAAQ,MAAM;CACzC,IAAI,QAAQ,GAAG;EACb,MAAM,QAAQ,IAAI,OAAO;EACzB,MAAM,IAAI;EACV,MAAM,IAAI;EACV,MAAM,cAAc,CAAC;EACrB,MAAM,gBAAgB;EACtB,MAAM,iBAAiB;EACvB;CACF;CACA,MAAM,OAAO,QAAQ,MAAM,QAAQ,MAAM;CACzC,IAAI,QAAQ,GAAG;EACb,MAAM,QAAQ,IAAI,OAAO;EACzB,MAAM,IAAI;EACV,MAAM,IAAI;EACV,MAAM,cAAc,CAAC;EACrB,MAAM,gBAAgB;EACtB,MAAM,iBAAiB;EACvB;CACF;CACA,IAAI,YAAY,GAAG,OAAO;CAI1B,MAAM,UAAU,SAAS,QAAQ,CAAC;CAClC,MAAM,UAAU,SAAS,QAAQ,CAAC;CAElC,IAAI,IAAI;CACR,IAAI,IAAI;CACR,IAAI,UAAU;CACd,IAAI,UAAU;CACd,cAAc,IAAI;CAClB,cAAc,IAAI;CAClB,mBAAA,KAAK,KAAK,eAAe,cAAc;CACvC,IAAI,WAAW,eAAe;CAC9B,IAAI,WAAW,eAAe;CAC9B,IAAI,WAAW,KAAK,KAAK,EAAE,WAAW,EAAE,QAAQ;CAChD,IAAI,aAAa;CACjB,OAAO;AACT;;ACnMA,SAAS,sBAAqC;CAC5C,OAAO;EACL,GAAG;EACH,GAAG;EACH,aAAa;EACb,eAAe;EACf,gBAAgB;EAChB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,YAAY;EACZ,aAAa;EACb,MAAM;CACR;AACF;AAEA,SAAS,iBAA2B;CAClC,OAAO;EACL,GAAG,KAAA;EACH,GAAG,KAAA;EACH,SAAS;EACT,SAAS;EACT,UAAU;EACV,UAAU;EACV,UAAU;EACV,YAAY;EACZ,QAAQ,CAAC,oBAAoB,GAAG,oBAAoB,CAAC;CACvD;AACF;AAEA,SAAgB,aAAyB;CACvC,OAAO;EACL,WAAW,CAAC;EACZ,eAAe;EACf,uBAAO,IAAI,aAAa,CAAC;EACzB,yBAAS,IAAI,aAAa,CAAC;EAC3B,uBAAO,IAAI,aAAa,CAAC;EACzB,gBAAgB;CAClB;AACF;;;;AAKA,SAAgB,mBAAmB,MAAkB,OAAqB;CACxE,IAAI,SAAS,KAAK,gBAAgB;CAClC,IAAI,MAAM,KAAK,mBAAmB,IAAI,IAAI,KAAK;CAC/C,OAAO,MAAM,OAAO,OAAO;CAC3B,KAAK,QAAQ,IAAI,aAAa,MAAM,CAAC;CACrC,KAAK,UAAU,IAAI,aAAa,MAAM,CAAC;CACvC,KAAK,QAAQ,IAAI,aAAa,MAAM,CAAC;CACrC,KAAK,iBAAiB;AACxB;;;;;AAMA,SAAgB,uBAAuB,MAAkB,OAAqB;CAC5E,OAAO,KAAK,UAAU,SAAS,OAC7B,KAAK,UAAU,KAAK,eAAe,CAAC;AAExC;;AC/EA,MAAM,YAAY;AAClB,MAAM,mBAAmB;AAEzB,MAAM,wBAAQ,IAAI,QAA2B;AAE7C,SAAgB,YAAY,OAAqB,CAAC,GAAU;CAC1D,MAAM,QAAe;EACnB,QAAQ,CAAC;EACT,SAAS,KAAK,UAAU;GAAE,GAAG,KAAK,QAAQ;GAAG,GAAG,KAAK,QAAQ;EAAE,IAAI;GAAE,GAAG;GAAG,GAAG;EAAK;EACnF,YAAY,KAAK,cAAc;CACjC;CACA,MAAM,IAAI,OAAO,WAAW,CAAC;CAC7B,OAAO;AACT;AAEA,SAAgB,QAAQ,OAAc,MAAkB;CACtD,MAAM,OAAO,KAAK,IAAI;CACtB,OAAO;AACT;;;AAIA,SAAS,sBACP,GACA,GACA,KACA,KACA,KACA,KACA,IACA,IACQ;CACR,MAAM,MAAM,EAAE,SAAS,IAAI,EAAE,kBAAkB;CAC/C,MAAM,MAAM,EAAE,SAAS,IAAI,EAAE,kBAAkB;CAC/C,MAAM,MAAM,EAAE,SAAS,IAAI,EAAE,kBAAkB;CAC/C,MAAM,MAAM,EAAE,SAAS,IAAI,EAAE,kBAAkB;CAC/C,QAAQ,MAAM,OAAO,MAAM,MAAM,OAAO;AAC1C;AAEA,SAAS,aAAa,MAAY,IAAY,IAAY,IAAY,IAAkB;CACtF,IAAI,KAAK,YAAY,GAAG;CACxB,KAAK,SAAS,KAAK,KAAK,KAAK;CAC7B,KAAK,SAAS,KAAK,KAAK,KAAK;CAC7B,KAAK,mBAAmB,KAAK,cAAc,KAAK,KAAK,KAAK;AAC5D;;;AAIA,SAAS,cAAc,MAAkB,QAAsB;CAC7D,mBAAmB,MAAM,OAAO,MAAM;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,IAAI,OAAO;EACjB,MAAM,UAAU,IAAI;EACpB,MAAM,UAAU,IAAI;EACpB,iBAAiB,GAAG,KAAK,OAAO,OAAO;EACvC,gBAAgB,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,EAAE,SAAS,GAAG,KAAK,SAAS,OAAO;EACtF,UAAU,KAAK,OAAO,SAAS,KAAK,OAAO,OAAO;CACpD;AACF;;;AAIA,SAAS,iBAAiB,MAAkB,QAAsB;CAChE,KAAK,gBAAgB;CACrB,MAAM,QAAQ,KAAK;CACnB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,IAAI,OAAO;EACjB,MAAM,OAAO,IAAI;EACjB,MAAM,QAAQ,MAAM;EACpB,MAAM,QAAQ,MAAM,OAAO;EAC3B,MAAM,QAAQ,MAAM,OAAO;EAC3B,MAAM,QAAQ,MAAM,OAAO;EAC3B,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GAC1C,MAAM,IAAI,OAAO;GACjB,IAAI,SAAS,CAAC,KAAK,SAAS,CAAC,GAAG;GAChC,MAAM,OAAO,IAAI;GACjB,MAAM,QAAQ,MAAM;GACpB,MAAM,QAAQ,MAAM,OAAO;GAC3B,MAAM,QAAQ,MAAM,OAAO;GAC3B,MAAM,QAAQ,MAAM,OAAO;GAC3B,IAAI,QAAQ,SAAS,QAAQ,SAAS,QAAQ,SAAS,QAAQ,OAAO;GACtE,uBAAuB,MAAM,KAAK,gBAAgB,CAAC;GACnD,MAAM,IAAI,KAAK,UAAU,KAAK;GAC9B,IAAI,YAAY,GAAG,GAAG,KAAK,OAAO,KAAK,SAAS,GAAG,GAAG,CAAC,GAAG,KAAK;EACjE;CACF;AACF;;;AAIA,SAAS,QAAQ,MAAkB,SAAe,IAAkB;CAClE,MAAM,gBAAgB,KAAK,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI;CACzD,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,eAAe,MAAM;EAC9C,MAAM,IAAI,KAAK,UAAU;EACzB,MAAM,EAAE,GAAG,MAAM;EACjB,MAAM,UAAU,EAAE;EAClB,MAAM,UAAU,EAAE;EAClB,MAAM,WAAW,EAAE;EACnB,MAAM,WAAW,EAAE;EACnB,MAAM,cAAc,KAAK,IAAI,EAAE,aAAa,EAAE,WAAW;EAEzD,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,YAAY,MAAM;GACxC,MAAM,KAAK,EAAE,OAAO;GACpB,MAAM,MAAM,GAAG,IAAI,EAAE,SAAS;GAC9B,MAAM,MAAM,GAAG,IAAI,EAAE,SAAS;GAC9B,MAAM,MAAM,GAAG,IAAI,EAAE,SAAS;GAC9B,MAAM,MAAM,GAAG,IAAI,EAAE,SAAS;GAE9B,MAAM,MAAM,MAAM,UAAU,MAAM;GAClC,MAAM,MAAM,MAAM,UAAU,MAAM;GAClC,MAAM,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,MAAM,MAAM,EAAE,aAAa,MAAM;GAExF,MAAM,MAAM,MAAM,WAAW,MAAM;GACnC,MAAM,MAAM,MAAM,WAAW,MAAM;GACnC,MAAM,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,MAAM,MAAM,EAAE,aAAa,MAAM;GAEzF,MAAM,KAAK,sBAAsB,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,SAAS,OAAO;GAC3E,MAAM,kBAAkB,KAAK,CAAC,gBAAgB,CAAC,cAAc,KAAK;GAClE,MAAM,YAAa,YAAY,KAAM,KAAK,IAAI,GAAG,GAAG,cAAc,gBAAgB;GAElF,GAAG,MAAM;GACT,GAAG,MAAM;GACT,GAAG,MAAM;GACT,GAAG,MAAM;GACT,GAAG,aAAa,UAAU,IAAI,IAAI,UAAU;GAC5C,GAAG,cAAc,WAAW,IAAI,IAAI,WAAW;GAC/C,GAAG,OAAO,kBAAkB;EAC9B;CACF;AACF;;AAGA,SAAS,MAAM,MAAwB;CACrC,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,eAAe,MAAM;EAC9C,MAAM,IAAI,KAAK,UAAU;EACzB,MAAM,EAAE,GAAG,GAAG,aAAa;EAC3B,MAAM,UAAU,EAAE;EAClB,MAAM,UAAU,EAAE;EAClB,MAAM,WAAW,EAAE;EACnB,MAAM,WAAW,EAAE;EAEnB,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,YAAY,MAAM;GACxC,MAAM,KAAK,EAAE,OAAO;GACpB,MAAM,MAAM,GAAG;GACf,MAAM,MAAM,GAAG;GACf,MAAM,MAAM,GAAG;GACf,MAAM,MAAM,GAAG;GAGf,MAAM,KAAK,sBAAsB,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,SAAS,OAAO;GAC3E,IAAI,MAAM,GAAG,cAAc,CAAC,KAAK,GAAG;GACpC,MAAM,YAAY,KAAK,IAAI,GAAG,gBAAgB,KAAK,CAAC;GACpD,MAAM,YAAY,GAAG;GACrB,GAAG,gBAAgB;GACnB,MAAM,MAAM,UAAU;GACtB,MAAM,MAAM,UAAU;GACtB,aAAa,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG;GACpC,aAAa,GAAG,KAAK,KAAK,KAAK,GAAG;GAGlC,MAAM,KAAK,sBAAsB,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,UAAU,QAAQ;GAC7E,IAAI,MAAM,GAAG,cAAc,CAAC;GAC5B,MAAM,QAAQ,WAAW,GAAG;GAC5B,MAAM,aAAa,KAAK,IAAI,CAAC,OAAO,KAAK,IAAI,OAAO,GAAG,iBAAiB,GAAG,CAAC;GAC5E,MAAM,aAAa,GAAG;GACtB,GAAG,iBAAiB;GACpB,MAAM,MAAM,WAAW;GACvB,MAAM,MAAM,WAAW;GACvB,aAAa,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG;GACpC,aAAa,GAAG,KAAK,KAAK,KAAK,GAAG;EACpC;CACF;AACF;;;;;;;;;;AAWA,SAAgB,KAAK,OAAc,IAAkB;CACnD,IAAI,MAAM,GAAG;CAOb,IAAI,OAAO,MAAM,IAAI,KAAK;CAC1B,IAAI,CAAC,MAAM;EACT,OAAO,WAAW;EAClB,MAAM,IAAI,OAAO,IAAI;CACvB;CACA,MAAM,EAAE,WAAW;CAGnB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,IAAI,GAAG;EACpB,KAAK,SAAS,KAAK,MAAM,QAAQ,IAAI;EACrC,KAAK,SAAS,KAAK,MAAM,QAAQ,IAAI;CACvC;CAGA,cAAc,MAAM,MAAM;CAC1B,iBAAiB,MAAM,MAAM;CAG7B,QAAQ,MAAM,MAAM,SAAS,EAAE;CAC/B,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,YAAY,QAC1C,MAAM,IAAI;CAIZ,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,OAAO,OAAO;EACpB,IAAI,SAAS,IAAI,GAAG;EACpB,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI;EACrC,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI;EACrC,KAAK,SAAS,KAAK,kBAAkB;CACvC;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/body.ts","../src/timeline.ts","../src/manifold.ts","../src/pool.ts","../src/world.ts"],"sourcesContent":["/** A rigid body: an oriented box (OBB) with linear + angular state.\n *\n * Boxes only. Mass properties are derived from the box extents; a body with\n * `invMass === 0` is static (floor / walls). */\n\nimport type { Vec2 } from '@flare-engine/math';\n\nexport interface Body {\n /** Center of mass, world space. */\n position: Vec2;\n /** Linear velocity. */\n velocity: Vec2;\n /** Orientation in radians. */\n angle: number;\n /** Angular velocity in radians/second. */\n angularVelocity: number;\n /** Half-extents (half width, half height) in the body's local frame. */\n half: Vec2;\n /** 1 / mass. `0` marks a static (immovable, infinite-mass) body. */\n invMass: number;\n /** 1 / rotational inertia. `0` locks rotation (all static bodies). */\n invInertia: number;\n /** Bounciness in [0, 1]. */\n restitution: number;\n /** Coulomb friction coefficient (≈0 slippery … ≈1 grippy). */\n friction: number;\n}\n\nexport interface BodyOptions {\n position: Vec2;\n half: Vec2;\n /** Uniform density; drives both mass and rotational inertia. Ignored when `static`. */\n density?: number;\n velocity?: Vec2;\n angle?: number;\n angularVelocity?: number;\n restitution?: number;\n friction?: number;\n /** Static bodies have infinite mass and never move. */\n static?: boolean;\n}\n\nexport function createBody(opts: BodyOptions): Body {\n const half = opts.half;\n const density = opts.density ?? 1;\n\n let invMass = 0;\n let invInertia = 0;\n if (!opts.static) {\n const w = half.x * 2;\n const h = half.y * 2;\n const mass = density * w * h;\n // Solid-rectangle inertia about its center: m·(w² + h²) / 12.\n const inertia = (mass * (w * w + h * h)) / 12;\n invMass = mass > 0 ? 1 / mass : 0;\n invInertia = inertia > 0 ? 1 / inertia : 0;\n }\n\n return {\n position: { x: opts.position.x, y: opts.position.y },\n velocity: opts.velocity ? { x: opts.velocity.x, y: opts.velocity.y } : { x: 0, y: 0 },\n angle: opts.angle ?? 0,\n angularVelocity: opts.angularVelocity ?? 0,\n half: { x: half.x, y: half.y },\n invMass,\n invInertia,\n restitution: opts.restitution ?? 0.2,\n friction: opts.friction ?? 0.4,\n };\n}\n\nexport function isStatic(b: Body): boolean {\n return b.invMass === 0;\n}\n\n/** The four world-space corners of `b`, written as x,y pairs at `offset`. */\nexport function writeBoxVertices(b: Body, out: Float64Array, offset: number): void {\n const c = Math.cos(b.angle);\n const s = Math.sin(b.angle);\n const hx = b.half.x;\n const hy = b.half.y;\n const px = b.position.x;\n const py = b.position.y;\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const lx = i === 0 || i === 3 ? -hx : hx;\n const ly = i === 0 || i === 1 ? -hy : hy;\n out[o] = px + (lx * c - ly * s);\n out[o + 1] = py + (lx * s + ly * c);\n o += 2;\n }\n}\n\n/** Outward face normals, one per edge `i → i+1`, guaranteed to point away\n * from `(cx, cy)` regardless of winding or the frame's y-direction. Reads\n * four already-written corners from `verts` at `vOffset`. */\nexport function writeBoxNormals(\n verts: Float64Array,\n vOffset: number,\n cx: number,\n cy: number,\n out: Float64Array,\n offset: number,\n): void {\n let o = offset;\n for (let i = 0; i < 4; i++) {\n const j = (i + 1) % 4;\n const ax = verts[vOffset + i * 2]!;\n const ay = verts[vOffset + i * 2 + 1]!;\n const bx = verts[vOffset + j * 2]!;\n const by = verts[vOffset + j * 2 + 1]!;\n const ex = bx - ax;\n const ey = by - ay;\n const l = Math.sqrt(ex * ex + ey * ey) || 1;\n let nx = ey / l;\n let ny = -ex / l;\n const midX = (ax + bx) / 2 - cx;\n const midY = (ay + by) / 2 - cy;\n if (nx * midX + ny * midY < 0) {\n nx = -nx;\n ny = -ny;\n }\n out[o] = nx;\n out[o + 1] = ny;\n o += 2;\n }\n}\n\n/** World-space axis-aligned bounds of four already-written corners, written\n * as `minX, minY, maxX, maxY` at `offset`. Does not recompute vertices. */\nexport function writeAabb(\n verts: Float64Array,\n vOffset: number,\n out: Float64Array,\n offset: number,\n): void {\n const x0 = verts[vOffset]!;\n const y0 = verts[vOffset + 1]!;\n let minX = x0;\n let minY = y0;\n let maxX = x0;\n let maxY = y0;\n for (let i = 1; i < 4; i++) {\n const x = verts[vOffset + i * 2]!;\n const y = verts[vOffset + i * 2 + 1]!;\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n out[offset] = minX;\n out[offset + 1] = minY;\n out[offset + 2] = maxX;\n out[offset + 3] = maxY;\n}\n","/** Bake-once / scrub-often recorder for a fixed-step simulation. Friction +\n * restitution Euler is not time-reversible, so a fall is recorded once into a\n * pre-allocated `Float32Array` and replayed by sampling — reverse, scrub and\n * speed all read back through `sampleInto` instead of re-integrating.\n *\n * Angles are stored exactly as written. `Body.angle` is cumulative and never\n * wrapped, so a plain lerp between adjacent frames is correct across\n * multi-turn spins; a shortest-path angle lerp would unravel them.\n *\n * Float32 storage is deliberate: this is playback precision, not simulation\n * precision, at half the bytes of f64. */\n\nexport interface TimelineOptions {\n /** Floats per element per frame. Default 3: `dx`, `dy`, `angle`. */\n stride?: number;\n /** Frame cap; the whole buffer is allocated at creation. Default 900. */\n maxFrames?: number;\n}\n\n/** Fills one frame of `buf`: element `i` lives at `frameBase + i * stride`. */\nexport type TimelineWriter = (buf: Float32Array, frameBase: number) => void;\n\nexport interface Timeline {\n readonly elementCount: number;\n /** Frames recorded so far. */\n readonly frameCount: number;\n /** Recorded length in seconds: `(frameCount - 1) * dt`, 0 below two frames. */\n readonly duration: number;\n /** Writes frames until `until()` returns true or the buffer is full; without\n * `until`, exactly one frame. `record(write)` once per tick suits a chunked,\n * time-budgeted bake; `record(stepAndWrite, isSettled)` bakes in one call\n * when the writer also advances the simulation. No-op once full. */\n record(writer: TimelineWriter, until?: () => boolean): void;\n /** Lerps the two frames bracketing `progress` (clamped to [0, 1], NaN reads\n * as 0) into `out`, which must hold at least `elementCount * stride`\n * floats. Zero-alloc. All zeros before the first frame. */\n sampleInto(progress: number, out: Float32Array): void;\n}\n\nconst DEFAULT_STRIDE = 3;\nconst DEFAULT_MAX_FRAMES = 900;\n\nfunction assertCount(name: string, value: number, min: number): void {\n if (!Number.isInteger(value) || value < min) {\n throw new RangeError(`createTimeline: ${name} must be an integer >= ${min}, got ${value}`);\n }\n}\n\nexport function createTimeline(\n elementCount: number,\n dt: number,\n opts: TimelineOptions = {},\n): Timeline {\n assertCount('elementCount', elementCount, 0);\n if (!Number.isFinite(dt) || dt <= 0) {\n throw new RangeError(`createTimeline: dt must be a finite number > 0, got ${dt}`);\n }\n const stride = opts.stride ?? DEFAULT_STRIDE;\n const maxFrames = opts.maxFrames ?? DEFAULT_MAX_FRAMES;\n assertCount('stride', stride, 1);\n assertCount('maxFrames', maxFrames, 1);\n\n const frameStride = elementCount * stride;\n const buf = new Float32Array(Math.max(1, frameStride) * maxFrames);\n let frameCount = 0;\n\n return {\n elementCount,\n get frameCount() {\n return frameCount;\n },\n get duration() {\n return frameCount > 1 ? (frameCount - 1) * dt : 0;\n },\n record(writer, until) {\n if (elementCount === 0) return;\n while (frameCount < maxFrames) {\n writer(buf, frameCount * frameStride);\n frameCount++;\n if (until === undefined || until()) return;\n }\n },\n sampleInto(progress, out) {\n const n = frameStride;\n if (frameCount === 0) {\n out.fill(0, 0, n);\n return;\n }\n // NaN fails both comparisons and lands on 0.\n const p = progress > 1 ? 1 : progress >= 0 ? progress : 0;\n const f = p * (frameCount - 1);\n const i0 = Math.floor(f);\n const a = f - i0;\n const i1 = i0 + 1 < frameCount ? i0 + 1 : i0;\n const b0 = i0 * n;\n const b1 = i1 * n;\n for (let o = 0; o < n; o++) {\n const lo = buf[b0 + o]!;\n const hi = buf[b1 + o]!;\n out[o] = lo + (hi - lo) * a;\n }\n },\n };\n}\n","/** OBB-OBB collision: separating-axis test + reference/incident face clipping,\n * building a 1-2 point contact manifold with per-point penetration depth.\n *\n * Ported from a prototype implementation onto flat `Float64Array` body geometry\n * and a pooled `Manifold`/`ManifoldPoint` — no per-call allocation.\n * The algorithm is the standard 2D clipping manifold (reference face → incident\n * face → Sutherland-Hodgman side-plane clip), stable for stacked / tumbling boxes. */\n\nimport { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport type { Manifold } from './pool.js';\n\n// Module-level scratch — collideInto is called every step (broad-phase pairs\n// × narrow-phase), so nothing here may allocate.\nconst faceScratch = new Int32Array(1);\nconst clipScratch = new Float64Array(4);\nconst normalScratch: Vec2 = { x: 0, y: 0 };\nconst tangentScratch: Vec2 = { x: 0, y: 0 };\n\n/** Index (0-3) of the vertex of the box at `offset` with greatest support\n * along direction `(dx, dy)`. */\nfunction supportIndex(verts: Float64Array, offset: number, dx: number, dy: number): number {\n let bestDot = Number.NEGATIVE_INFINITY;\n let best = 0;\n for (let i = 0; i < 4; i++) {\n const x = verts[offset + i * 2]!;\n const y = verts[offset + i * 2 + 1]!;\n const d = x * dx + y * dy;\n if (d > bestDot) {\n bestDot = d;\n best = i;\n }\n }\n return best;\n}\n\n/** Max separation of the box at `otherOffset` from the faces of the box at\n * `selfOffset`. Positive → a gap exists on some axis (no overlap); negative\n * → the least-penetrating axis. Writes the winning face index into\n * `faceScratch[0]`. */\nfunction leastPenetration(\n verts: Float64Array,\n normals: Float64Array,\n selfOffset: number,\n otherOffset: number,\n): number {\n let best = Number.NEGATIVE_INFINITY;\n let bestFace = 0;\n for (let i = 0; i < 4; i++) {\n const nx = normals[selfOffset + i * 2]!;\n const ny = normals[selfOffset + i * 2 + 1]!;\n const vx = verts[selfOffset + i * 2]!;\n const vy = verts[selfOffset + i * 2 + 1]!;\n const si = supportIndex(verts, otherOffset, -nx, -ny);\n const sx = verts[otherOffset + si * 2]!;\n const sy = verts[otherOffset + si * 2 + 1]!;\n const sep = nx * (sx - vx) + ny * (sy - vy);\n if (sep > best) {\n best = sep;\n bestFace = i;\n }\n }\n faceScratch[0] = bestFace;\n return best;\n}\n\n/** Bias toward keeping the current reference face for solver coherence. */\nfunction biasGreaterThan(a: number, b: number): boolean {\n return a >= b * 0.95 + a * 0.01;\n}\n\n/** Index (0-3) of the incident box's face whose normal is most anti-parallel\n * to the reference normal. */\nfunction incidentFaceIndex(\n refNx: number,\n refNy: number,\n incNormals: Float64Array,\n incOffset: number,\n): number {\n let minDot = Number.POSITIVE_INFINITY;\n let index = 0;\n for (let i = 0; i < 4; i++) {\n const nx = incNormals[incOffset + i * 2]!;\n const ny = incNormals[incOffset + i * 2 + 1]!;\n const d = refNx * nx + refNy * ny;\n if (d < minDot) {\n minDot = d;\n index = i;\n }\n }\n return index;\n}\n\n/** Clips segment `(ax,ay)-(bx,by)` to the halfspace `dot(n, p) <= c`. Writes\n * 0-2 surviving points into `clipScratch` and returns the count. */\nfunction clip(\n nx: number,\n ny: number,\n c: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n): number {\n let count = 0;\n const d1 = nx * ax + ny * ay - c;\n const d2 = nx * bx + ny * by - c;\n if (d1 <= 0) {\n clipScratch[count * 2] = ax;\n clipScratch[count * 2 + 1] = ay;\n count++;\n }\n if (d2 <= 0) {\n clipScratch[count * 2] = bx;\n clipScratch[count * 2 + 1] = by;\n count++;\n }\n if (d1 * d2 < 0) {\n const t = d1 / (d1 - d2);\n clipScratch[count * 2] = ax + (bx - ax) * t;\n clipScratch[count * 2 + 1] = ay + (by - ay) * t;\n count++;\n }\n return count;\n}\n\n/** Builds a contact manifold for boxes `a` (index `ai`) and `b` (index `bi`)\n * into `out`, reading pre-written geometry from `verts`/`normals` (8 doubles\n * per body). Returns `false` — leaving `out` untouched — when the boxes do\n * not overlap. */\nexport function collideInto(\n a: Body,\n b: Body,\n verts: Float64Array,\n normals: Float64Array,\n ai: number,\n bi: number,\n out: Manifold,\n): boolean {\n const aOff = ai * 8;\n const bOff = bi * 8;\n\n const sepA = leastPenetration(verts, normals, aOff, bOff);\n if (sepA >= 0) return false;\n const faceA = faceScratch[0]!;\n\n const sepB = leastPenetration(verts, normals, bOff, aOff);\n if (sepB >= 0) return false;\n const faceB = faceScratch[0]!;\n\n const refIsA = biasGreaterThan(sepA, sepB);\n const refOffset = refIsA ? aOff : bOff;\n const incOffset = refIsA ? bOff : aOff;\n const refIndex = refIsA ? faceA : faceB;\n\n const refNx = normals[refOffset + refIndex * 2]!;\n const refNy = normals[refOffset + refIndex * 2 + 1]!;\n const refJ = (refIndex + 1) % 4;\n const v1x = verts[refOffset + refIndex * 2]!;\n const v1y = verts[refOffset + refIndex * 2 + 1]!;\n const v2x = verts[refOffset + refJ * 2]!;\n const v2y = verts[refOffset + refJ * 2 + 1]!;\n\n const incIndex = incidentFaceIndex(refNx, refNy, normals, incOffset);\n const incJ = (incIndex + 1) % 4;\n const iax = verts[incOffset + incIndex * 2]!;\n const iay = verts[incOffset + incIndex * 2 + 1]!;\n const ibx = verts[incOffset + incJ * 2]!;\n const iby = verts[incOffset + incJ * 2 + 1]!;\n\n // Tangent along the reference face; its two end-planes form the clip slab.\n // `|| 1` guards a degenerate (zero- or near-zero-extent) reference edge the\n // same way `writeBoxNormals` already does for the identical computation —\n // without it, a squared-length underflow makes this a division by zero.\n let sideX = v2x - v1x;\n let sideY = v2y - v1y;\n const sideInv = 1 / (Math.sqrt(sideX * sideX + sideY * sideY) || 1);\n sideX *= sideInv;\n sideY *= sideInv;\n const negSide = -(sideX * v1x + sideY * v1y);\n const posSide = sideX * v2x + sideY * v2y;\n\n let count = clip(-sideX, -sideY, negSide, iax, iay, ibx, iby);\n if (count < 2) return false;\n const c0x = clipScratch[0]!;\n const c0y = clipScratch[1]!;\n const c1x = clipScratch[2]!;\n const c1y = clipScratch[3]!;\n\n count = clip(sideX, sideY, posSide, c0x, c0y, c1x, c1y);\n if (count < 2) return false;\n const p0x = clipScratch[0]!;\n const p0y = clipScratch[1]!;\n const p1x = clipScratch[2]!;\n const p1y = clipScratch[3]!;\n\n const refC = refNx * v1x + refNy * v1y;\n\n let written = 0;\n const sep0 = refNx * p0x + refNy * p0y - refC;\n if (sep0 <= 0) {\n const point = out.points[written]!;\n point.x = p0x;\n point.y = p0y;\n point.penetration = -sep0;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n const sep1 = refNx * p1x + refNy * p1y - refC;\n if (sep1 <= 0) {\n const point = out.points[written]!;\n point.x = p1x;\n point.y = p1y;\n point.penetration = -sep1;\n point.normalImpulse = 0;\n point.tangentImpulse = 0;\n written++;\n }\n if (written === 0) return false;\n\n // Manifold normal must point a → b; refNormal points off the reference\n // box, which is b when we flipped, so negate in that case.\n const normalX = refIsA ? refNx : -refNx;\n const normalY = refIsA ? refNy : -refNy;\n\n out.a = a;\n out.b = b;\n out.normalX = normalX;\n out.normalY = normalY;\n normalScratch.x = normalX;\n normalScratch.y = normalY;\n Vec2.perp(normalScratch, tangentScratch);\n out.tangentX = tangentScratch.x;\n out.tangentY = tangentScratch.y;\n out.friction = Math.sqrt(a.friction * b.friction);\n out.pointCount = written;\n return true;\n}\n","/** Per-world scratch: contact manifolds, the flat vertex/normal/AABB buffers\n * `world.ts` writes body geometry into each step, and the per-slot sleep\n * state. Growth doubles capacity and rebuilds arrays from scratch — geometry\n * is fully rewritten every step, so it is not copied forward; the sleep\n * arrays are, since a sleeper must survive a body being added. */\n\nimport type { Body } from './body.js';\n\nexport interface SleepOptions {\n /** Linear speed below which a body counts as resting (same units as `velocity`, px/s). */\n linearThreshold: number;\n /** Angular speed below which a body counts as resting (rad/s). */\n angularThreshold: number;\n /** Consecutive under-threshold steps before a body is put to sleep (integer >= 1). */\n steps: number;\n}\n\nexport interface ManifoldPoint {\n x: number;\n y: number;\n penetration: number;\n normalImpulse: number;\n tangentImpulse: number;\n raX: number;\n raY: number;\n rbX: number;\n rbY: number;\n massNormal: number;\n massTangent: number;\n bias: number;\n}\n\nexport interface Manifold {\n a: Body;\n b: Body;\n /** Slot indices of `a` / `b` in `world.bodies`, for the sleep arrays. */\n ai: number;\n bi: number;\n /** Effective inverse mass / inertia the solver uses: 0 for a sleeping\n * body (it acts as infinite mass), the body's own values otherwise. */\n aInvMass: number;\n aInvInertia: number;\n bInvMass: number;\n bInvInertia: number;\n normalX: number;\n normalY: number;\n tangentX: number;\n tangentY: number;\n friction: number;\n pointCount: number;\n points: ManifoldPoint[];\n}\n\nexport interface SolverPool {\n manifolds: Manifold[];\n manifoldCount: number;\n verts: Float64Array;\n normals: Float64Array;\n aabbs: Float64Array;\n capacityBodies: number;\n /** `null` when sleeping is off; then `asleep` stays all-zero and the hot\n * loops that read it unconditionally take their sleep-off path. */\n sleep: SleepOptions | null;\n /** Consecutive under-threshold steps per slot. */\n sleepCounter: Int32Array;\n /** 1 when the slot's body is asleep. */\n asleep: Uint8Array;\n /** position.x, position.y, angle per slot, taken when it fell asleep. */\n sleepSnapshot: Float64Array;\n /** The body each slot's sleep state belongs to; a mismatch resets the slot. */\n sleepOwner: (Body | undefined)[];\n}\n\nfunction createManifoldPoint(): ManifoldPoint {\n return {\n x: 0,\n y: 0,\n penetration: 0,\n normalImpulse: 0,\n tangentImpulse: 0,\n raX: 0,\n raY: 0,\n rbX: 0,\n rbY: 0,\n massNormal: 0,\n massTangent: 0,\n bias: 0,\n };\n}\n\nfunction createManifold(): Manifold {\n return {\n a: undefined as unknown as Body,\n b: undefined as unknown as Body,\n ai: 0,\n bi: 0,\n aInvMass: 0,\n aInvInertia: 0,\n bInvMass: 0,\n bInvInertia: 0,\n normalX: 0,\n normalY: 0,\n tangentX: 0,\n tangentY: 0,\n friction: 0,\n pointCount: 0,\n points: [createManifoldPoint(), createManifoldPoint()],\n };\n}\n\nexport function createPool(sleep: SleepOptions | null = null): SolverPool {\n return {\n manifolds: [],\n manifoldCount: 0,\n verts: new Float64Array(0),\n normals: new Float64Array(0),\n aabbs: new Float64Array(0),\n capacityBodies: 0,\n sleep,\n sleepCounter: new Int32Array(0),\n asleep: new Uint8Array(0),\n sleepSnapshot: new Float64Array(0),\n sleepOwner: [],\n };\n}\n\n/** Grows the per-body buffers to hold at least `count` bodies (8 doubles per\n * body for verts/normals, 4 for the AABB, 3 for the sleep snapshot). No-op\n * if already large enough. */\nexport function ensureBodyCapacity(pool: SolverPool, count: number): void {\n if (count <= pool.capacityBodies) return;\n let cap = pool.capacityBodies === 0 ? 8 : pool.capacityBodies;\n while (cap < count) cap *= 2;\n pool.verts = new Float64Array(cap * 8);\n pool.normals = new Float64Array(cap * 8);\n pool.aabbs = new Float64Array(cap * 4);\n const sleepCounter = new Int32Array(cap);\n sleepCounter.set(pool.sleepCounter);\n pool.sleepCounter = sleepCounter;\n const asleep = new Uint8Array(cap);\n asleep.set(pool.asleep);\n pool.asleep = asleep;\n const sleepSnapshot = new Float64Array(cap * 3);\n sleepSnapshot.set(pool.sleepSnapshot);\n pool.sleepSnapshot = sleepSnapshot;\n while (pool.sleepOwner.length < cap) pool.sleepOwner.push(undefined);\n pool.capacityBodies = cap;\n}\n\n/** Grows `pool.manifolds` to hold at least `count` entries, appending\n * fully-formed `Manifold` objects (each with two pre-created points).\n * Never truncates — manifolds past `manifoldCount` simply go unused, which\n * is what keeps the steady state allocation-free. */\nexport function ensureManifoldCapacity(pool: SolverPool, count: number): void {\n while (pool.manifolds.length < count) {\n pool.manifolds.push(createManifold());\n }\n}\n","/** The rigid-body world: gravity integration + a sequential-impulse contact\n * solver (Box2D-lite structure) with Coulomb friction and Baumgarte\n * de-penetration, plus a restitution term for bounce.\n *\n * Modeled as a plain object + free functions (no class) to match the house\n * style. The pool lives in a module-level `WeakMap` so `World` keeps its\n * frozen three-field public shape — one lookup per `step`, not per body. */\n\nimport type { Vec2 } from '@flare-engine/math';\nimport type { Body } from './body.js';\nimport { isStatic, writeAabb, writeBoxNormals, writeBoxVertices } from './body.js';\nimport { collideInto } from './manifold.js';\nimport {\n createPool,\n ensureBodyCapacity,\n ensureManifoldCapacity,\n type SleepOptions,\n type SolverPool,\n} from './pool.js';\n\nexport interface WorldOptions {\n /** Acceleration applied to every dynamic body each step (px/s²). */\n gravity?: Vec2;\n /** Sequential-impulse iterations per step. More = stiffer stacks. */\n iterations?: number;\n /** Opt-in per-body sleeping; off when absent. See `step` and `isSleeping`. */\n sleep?: SleepOptions;\n}\n\nexport interface World {\n bodies: Body[];\n gravity: Vec2;\n iterations: number;\n}\n\nexport interface StepUntilOptions {\n /** Time budget in ms, compared against `now()` after every step. */\n budgetMs: number;\n /** Step cap per call (integer >= 1). */\n maxSteps?: number;\n /** Polled after each step; `true` ends the call with `complete: true`. */\n done?: () => boolean;\n /** Clock in ms. Default `performance.now`, else `Date.now`; inject for deterministic tests. */\n now?: () => number;\n}\n\nexport interface StepUntilResult {\n /** Steps taken by this call, always >= 1. */\n steps: number;\n /** `true` when `done()` ended the call; `false` for `maxSteps` or the budget. */\n complete: boolean;\n}\n\nconst BAUMGARTE = 0.2;\nconst PENETRATION_SLOP = 0.5;\n\nconst pools = new WeakMap<World, SolverPool>();\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n\nfunction copySleepOptions(sleep: SleepOptions): SleepOptions {\n const { linearThreshold, angularThreshold, steps } = sleep;\n if (!Number.isFinite(linearThreshold) || linearThreshold < 0) {\n throw new RangeError(\n `createWorld: sleep.linearThreshold must be a finite number >= 0, got ${linearThreshold}`,\n );\n }\n if (!Number.isFinite(angularThreshold) || angularThreshold < 0) {\n throw new RangeError(\n `createWorld: sleep.angularThreshold must be a finite number >= 0, got ${angularThreshold}`,\n );\n }\n if (!Number.isInteger(steps) || steps < 1) {\n throw new RangeError(`createWorld: sleep.steps must be an integer >= 1, got ${steps}`);\n }\n return { linearThreshold, angularThreshold, steps };\n}\n\n/** Throws `RangeError` when `opts.sleep` has a negative or non-finite\n * threshold or a `steps` that is not an integer >= 1. */\nexport function createWorld(opts: WorldOptions = {}): World {\n const world: World = {\n bodies: [],\n gravity: opts.gravity ? { x: opts.gravity.x, y: opts.gravity.y } : { x: 0, y: 1800 },\n iterations: opts.iterations ?? 10,\n };\n pools.set(world, createPool(opts.sleep ? copySleepOptions(opts.sleep) : null));\n return world;\n}\n\nexport function addBody(world: World, body: Body): Body {\n world.bodies.push(body);\n return body;\n}\n\n/** Relative velocity at a contact projected onto (dx, dy). The vector never\n * escapes — its only use is a dot product, so it collapses to two scalars. */\nfunction relativeVelocityAlong(\n a: Body,\n b: Body,\n raX: number,\n raY: number,\n rbX: number,\n rbY: number,\n dx: number,\n dy: number,\n): number {\n const vax = a.velocity.x - a.angularVelocity * raY;\n const vay = a.velocity.y + a.angularVelocity * raX;\n const vbx = b.velocity.x - b.angularVelocity * rbY;\n const vby = b.velocity.y + b.angularVelocity * rbX;\n return (vbx - vax) * dx + (vby - vay) * dy;\n}\n\nfunction applyImpulse(\n body: Body,\n invMass: number,\n invInertia: number,\n ix: number,\n iy: number,\n rx: number,\n ry: number,\n): void {\n if (invMass === 0) return;\n body.velocity.x += ix * invMass;\n body.velocity.y += iy * invMass;\n body.angularVelocity += invInertia * (rx * iy - ry * ix);\n}\n\n/** Binds every slot to the body now at its index, resetting a slot whose body\n * changed (awake, counter 0), then wakes any sleeper the consumer wrote to:\n * a non-zero velocity (sleepers are held at exactly 0) or a position/angle\n * that differs from the snapshot taken at sleep. */\nfunction syncSleepSlots(pool: SolverPool, bodies: Body[]): void {\n const { sleepOwner, asleep, sleepCounter, sleepSnapshot } = pool;\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (sleepOwner[i] !== body) {\n sleepOwner[i] = body;\n asleep[i] = 0;\n sleepCounter[i] = 0;\n continue;\n }\n if (asleep[i] === 0) continue;\n const s = i * 3;\n if (\n body.velocity.x !== 0 ||\n body.velocity.y !== 0 ||\n body.angularVelocity !== 0 ||\n body.position.x !== sleepSnapshot[s] ||\n body.position.y !== sleepSnapshot[s + 1] ||\n body.angle !== sleepSnapshot[s + 2]\n ) {\n asleep[i] = 0;\n sleepCounter[i] = 0;\n }\n }\n}\n\n/** Writes every body's world-space vertices, outward normals, and AABB into\n * `pool`, once per step, so the broadphase pair loop never recomputes them. */\nfunction writeGeometry(pool: SolverPool, bodies: Body[]): void {\n for (let i = 0; i < bodies.length; i++) {\n const b = bodies[i]!;\n const vOffset = i * 8;\n const aOffset = i * 4;\n writeBoxVertices(b, pool.verts, vOffset);\n writeBoxNormals(pool.verts, vOffset, b.position.x, b.position.y, pool.normals, vOffset);\n writeAabb(pool.verts, vOffset, pool.aabbs, aOffset);\n }\n}\n\n/** Broadphase (precomputed-AABB reject) + narrowphase (OBB manifold),\n * writing surviving contacts into `pool.manifolds[0..manifoldCount)`. A pair\n * whose two bodies are each static or asleep is skipped. */\nfunction collectManifolds(pool: SolverPool, bodies: Body[]): void {\n pool.manifoldCount = 0;\n const { aabbs, asleep } = pool;\n for (let i = 0; i < bodies.length; i++) {\n const a = bodies[i]!;\n const aResting = isStatic(a) || asleep[i] === 1;\n const aOff = i * 4;\n const aMinX = aabbs[aOff]!;\n const aMinY = aabbs[aOff + 1]!;\n const aMaxX = aabbs[aOff + 2]!;\n const aMaxY = aabbs[aOff + 3]!;\n for (let j = i + 1; j < bodies.length; j++) {\n const b = bodies[j]!;\n if (aResting && (isStatic(b) || asleep[j] === 1)) continue;\n const bOff = j * 4;\n const bMinX = aabbs[bOff]!;\n const bMinY = aabbs[bOff + 1]!;\n const bMaxX = aabbs[bOff + 2]!;\n const bMaxY = aabbs[bOff + 3]!;\n if (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) continue;\n ensureManifoldCapacity(pool, pool.manifoldCount + 1);\n const m = pool.manifolds[pool.manifoldCount]!;\n if (collideInto(a, b, pool.verts, pool.normals, i, j, m)) {\n m.ai = i;\n m.bi = j;\n pool.manifoldCount++;\n }\n }\n }\n}\n\n/** Wakes a sleeper touched by an awake body that was moving on the previous\n * step (sleep counter 0), giving it this step's gravity so it continues as\n * if it had been awake for the integration. A sleeper touched only by a\n * settling body stays asleep. Pairs of two resting bodies never reach here,\n * so a manifold with one sleeper always has an awake dynamic partner. */\nfunction wakeByContact(pool: SolverPool, gravity: Vec2, dt: number): void {\n const { asleep, sleepCounter } = pool;\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const aAsleep = asleep[m.ai] === 1;\n if (aAsleep === (asleep[m.bi] === 1)) continue;\n const sleeperIdx = aAsleep ? m.ai : m.bi;\n const awakeIdx = aAsleep ? m.bi : m.ai;\n if (sleepCounter[awakeIdx] !== 0) continue;\n asleep[sleeperIdx] = 0;\n sleepCounter[sleeperIdx] = 0;\n const sleeper = aAsleep ? m.a : m.b;\n sleeper.velocity.x += gravity.x * dt;\n sleeper.velocity.y += gravity.y * dt;\n }\n}\n\n/** Derives per-point solver mass terms and the velocity/position bias for\n * every live manifold, writing them onto the manifold's own contact points. */\nfunction prepare(pool: SolverPool, gravity: Vec2, dt: number): void {\n const restThreshold = Math.hypot(gravity.x, gravity.y) * dt;\n const { asleep } = pool;\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b } = m;\n const aAsleep = asleep[m.ai] === 1;\n const bAsleep = asleep[m.bi] === 1;\n const aInvMass = aAsleep ? 0 : a.invMass;\n const aInvInertia = aAsleep ? 0 : a.invInertia;\n const bInvMass = bAsleep ? 0 : b.invMass;\n const bInvInertia = bAsleep ? 0 : b.invInertia;\n m.aInvMass = aInvMass;\n m.aInvInertia = aInvInertia;\n m.bInvMass = bInvMass;\n m.bInvInertia = bInvInertia;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n const restitution = Math.min(a.restitution, b.restitution);\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.x - a.position.x;\n const raY = cp.y - a.position.y;\n const rbX = cp.x - b.position.x;\n const rbY = cp.y - b.position.y;\n\n const rnA = raX * normalY - raY * normalX;\n const rnB = rbX * normalY - rbY * normalX;\n const kNormal = aInvMass + bInvMass + aInvInertia * rnA * rnA + bInvInertia * rnB * rnB;\n\n const rtA = raX * tangentY - raY * tangentX;\n const rtB = rbX * tangentY - rbY * tangentX;\n const kTangent = aInvMass + bInvMass + aInvInertia * rtA * rtA + bInvInertia * rtB * rtB;\n\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n const restitutionBias = vn < -restThreshold ? -restitution * vn : 0;\n const baumgarte = (BAUMGARTE / dt) * Math.max(0, cp.penetration - PENETRATION_SLOP);\n\n cp.raX = raX;\n cp.raY = raY;\n cp.rbX = rbX;\n cp.rbY = rbY;\n cp.massNormal = kNormal > 0 ? 1 / kNormal : 0;\n cp.massTangent = kTangent > 0 ? 1 / kTangent : 0;\n cp.bias = restitutionBias + baumgarte;\n }\n }\n}\n\n/** One sequential-impulse sweep over every live manifold's contact points. */\nfunction solve(pool: SolverPool): void {\n for (let mi = 0; mi < pool.manifoldCount; mi++) {\n const m = pool.manifolds[mi]!;\n const { a, b, friction, aInvMass, aInvInertia, bInvMass, bInvInertia } = m;\n const normalX = m.normalX;\n const normalY = m.normalY;\n const tangentX = m.tangentX;\n const tangentY = m.tangentY;\n\n for (let pi = 0; pi < m.pointCount; pi++) {\n const cp = m.points[pi]!;\n const raX = cp.raX;\n const raY = cp.raY;\n const rbX = cp.rbX;\n const rbY = cp.rbY;\n\n // Normal impulse (clamped >= 0 so contacts push, never pull).\n const vn = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, normalX, normalY);\n let dPn = cp.massNormal * (-vn + cp.bias);\n const newNormal = Math.max(cp.normalImpulse + dPn, 0);\n dPn = newNormal - cp.normalImpulse;\n cp.normalImpulse = newNormal;\n const pnX = normalX * dPn;\n const pnY = normalY * dPn;\n applyImpulse(a, aInvMass, aInvInertia, -pnX, -pnY, raX, raY);\n applyImpulse(b, bInvMass, bInvInertia, pnX, pnY, rbX, rbY);\n\n // Friction impulse (clamped to the Coulomb cone mu*Pn).\n const vt = relativeVelocityAlong(a, b, raX, raY, rbX, rbY, tangentX, tangentY);\n let dPt = cp.massTangent * -vt;\n const maxPt = friction * cp.normalImpulse;\n const newTangent = Math.max(-maxPt, Math.min(maxPt, cp.tangentImpulse + dPt));\n dPt = newTangent - cp.tangentImpulse;\n cp.tangentImpulse = newTangent;\n const ptX = tangentX * dPt;\n const ptY = tangentY * dPt;\n applyImpulse(a, aInvMass, aInvInertia, -ptX, -ptY, raX, raY);\n applyImpulse(b, bInvMass, bInvInertia, ptX, ptY, rbX, rbY);\n }\n }\n}\n\n/** Puts every awake dynamic body that stayed under both thresholds for\n * `sleep.steps` consecutive steps to sleep: velocities zeroed, flag set,\n * position/angle snapshotted for the write-wake check. */\nfunction updateSleep(pool: SolverPool, sleep: SleepOptions, bodies: Body[]): void {\n const { asleep, sleepCounter, sleepSnapshot } = pool;\n const linear2 = sleep.linearThreshold * sleep.linearThreshold;\n const angular2 = sleep.angularThreshold * sleep.angularThreshold;\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body) || asleep[i] === 1) continue;\n const vx = body.velocity.x;\n const vy = body.velocity.y;\n const w = body.angularVelocity;\n if (vx * vx + vy * vy < linear2 && w * w < angular2) {\n const count = sleepCounter[i]! + 1;\n sleepCounter[i] = count;\n if (count < sleep.steps) continue;\n body.velocity.x = 0;\n body.velocity.y = 0;\n body.angularVelocity = 0;\n asleep[i] = 1;\n const s = i * 3;\n sleepSnapshot[s] = body.position.x;\n sleepSnapshot[s + 1] = body.position.y;\n sleepSnapshot[s + 2] = body.angle;\n } else {\n sleepCounter[i] = 0;\n }\n }\n}\n\n/** Advance the world by `dt` seconds (fixed steps recommended).\n *\n * By default (no `WorldOptions.sleep`) resting contacts settle to a stable\n * *position* but retain a small, bounded velocity oscillation at the\n * penetration-slop equilibrium — a sequential-impulse solver with Baumgarte\n * bias does not converge velocity to exactly zero at rest, only the position\n * does. `WorldOptions.sleep` opts into per-body sleeping: a dynamic body\n * under both thresholds for `steps` consecutive steps has its velocities\n * zeroed and is skipped by integration and by pair tests against other\n * resting bodies until an awake body touches it, the consumer writes its\n * velocity, position or angle, or `wakeBody` is called. There are no\n * islands, so a woken stack wakes one level per step, and a sleeper does not\n * follow a static body that is moved under it. Damping stays out of scope.\n *\n * A world that never went through `createWorld` (a spread copy or a\n * `structuredClone`) is adopted on its first `step` with sleeping off. */\nexport function step(world: World, dt: number): void {\n if (dt <= 0) return;\n // `World` is a public interface with plain fields, so a consumer can hand\n // back a structurally valid value that never went through `createWorld`\n // (spread copy, `structuredClone`, state round-tripping) and is therefore\n // absent from `pools`. Lazily adopt it instead of crashing on a missing\n // entry; steady-state callers (worlds from `createWorld`) never pay this\n // branch since their pool is already registered.\n let pool = pools.get(world);\n if (!pool) {\n pool = createPool();\n pools.set(world, pool);\n }\n const { bodies } = world;\n ensureBodyCapacity(pool, bodies.length);\n const { sleep, asleep } = pool;\n\n // 0. Bind sleep slots to the bodies at their indices; wake written sleepers.\n if (sleep !== null) syncSleepSlots(pool, bodies);\n\n // 1. Integrate velocities (apply gravity to awake dynamic bodies).\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body) || asleep[i] === 1) continue;\n body.velocity.x += world.gravity.x * dt;\n body.velocity.y += world.gravity.y * dt;\n }\n\n // 2. Broadphase (precomputed AABB reject) + narrowphase (OBB manifold).\n writeGeometry(pool, bodies);\n collectManifolds(pool, bodies);\n if (sleep !== null) wakeByContact(pool, world.gravity, dt);\n\n // 3. Solve velocity constraints.\n prepare(pool, world.gravity, dt);\n for (let iter = 0; iter < world.iterations; iter++) {\n solve(pool);\n }\n\n // 4. Integrate positions.\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body) || asleep[i] === 1) continue;\n body.position.x += body.velocity.x * dt;\n body.position.y += body.velocity.y * dt;\n body.angle += body.angularVelocity * dt;\n }\n\n // 5. Put settled bodies to sleep.\n if (sleep !== null) updateSleep(pool, sleep, bodies);\n}\n\n/** `true` when `body` is asleep in `world`. `false` when the world has\n * sleeping off or never went through `createWorld`, when `body` is not in\n * `world.bodies`, or when its slot has not been bound to it by a `step` yet.\n * O(n) in `world.bodies.length` (an `indexOf`); poll it per body, not per\n * contact. */\nexport function isSleeping(world: World, body: Body): boolean {\n const pool = pools.get(world);\n if (!pool || pool.sleep === null) return false;\n const i = world.bodies.indexOf(body);\n if (i < 0 || pool.sleepOwner[i] !== body) return false;\n return pool.asleep[i] === 1;\n}\n\n/** `true` when every dynamic body in `world.bodies` is asleep — vacuously\n * true with no dynamic bodies, `false` with sleeping off and at least one.\n * The bake stop rule for `stepUntil`: `done: () => isAtRest(world)`. */\nexport function isAtRest(world: World): boolean {\n const pool = pools.get(world);\n const { bodies } = world;\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]!;\n if (isStatic(body)) continue;\n if (!pool || pool.sleep === null || pool.sleepOwner[i] !== body || pool.asleep[i] !== 1) {\n return false;\n }\n }\n return true;\n}\n\n/** Wakes `body` if it is asleep in `world` (flag cleared, counter 0, its\n * velocities stay 0); the next `step` integrates it again. No-op otherwise,\n * including with sleeping off or a body not in `world.bodies`. Use it for a\n * sleeper whose support was removed from `world.bodies` or moved — nothing\n * else wakes it. */\nexport function wakeBody(world: World, body: Body): void {\n const pool = pools.get(world);\n if (!pool || pool.sleep === null) return;\n const i = world.bodies.indexOf(body);\n if (i < 0 || pool.sleepOwner[i] !== body || pool.asleep[i] !== 1) return;\n pool.asleep[i] = 0;\n pool.sleepCounter[i] = 0;\n}\n\n/** Repeats `step(world, dt)` until, checked in this order after each step,\n * `done()` returns true (`complete: true`), `steps >= maxSteps`, or\n * `now() - start >= budgetMs` (both `complete: false`). Always takes at least\n * one step, so a zero budget still makes progress. Writes `out` and returns it;\n * the zero-alloc per-frame form of `stepUntil`. Throws `RangeError` for a\n * non-finite or non-positive `dt`, a negative or non-finite `budgetMs`, or a\n * `maxSteps` that is not an integer >= 1. */\nexport function stepUntilInto(\n world: World,\n dt: number,\n opts: StepUntilOptions,\n out: StepUntilResult,\n): StepUntilResult {\n if (!Number.isFinite(dt) || dt <= 0) {\n throw new RangeError(`stepUntil: dt must be a finite number > 0, got ${dt}`);\n }\n const { budgetMs, maxSteps, done } = opts;\n if (!Number.isFinite(budgetMs) || budgetMs < 0) {\n throw new RangeError(`stepUntil: budgetMs must be a finite number >= 0, got ${budgetMs}`);\n }\n if (maxSteps !== undefined && (!Number.isInteger(maxSteps) || maxSteps < 1)) {\n throw new RangeError(`stepUntil: maxSteps must be an integer >= 1, got ${maxSteps}`);\n }\n const now = opts.now ?? nowMs;\n const start = now();\n let steps = 0;\n let complete = false;\n for (;;) {\n step(world, dt);\n steps++;\n if (done?.()) {\n complete = true;\n break;\n }\n if (maxSteps !== undefined && steps >= maxSteps) break;\n if (now() - start >= budgetMs) break;\n }\n out.steps = steps;\n out.complete = complete;\n return out;\n}\n\n/** Allocating form of `stepUntilInto`; see it for the stop rules. */\nexport function stepUntil(world: World, dt: number, opts: StepUntilOptions): StepUntilResult {\n return stepUntilInto(world, dt, opts, { steps: 0, complete: false });\n}\n"],"mappings":"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"}