PURPOSE
Owns the player ship’s Rapier2D dynamic rigid body and its convex-hull collider. Bridges the game’s velocity-based movement system to the Rapier physics solver: push computed velocity in before the step, read corrected position out after the step, and produce a render-frame-interpolated position for smooth drawing between fixed physics ticks.
Rotations on the body are locked. Ship facing angle is managed by gameplay code (turnSpeed) and only mirrored onto the body for collider orientation. Rapier handles translation and collision response only.
OWNS
- Module-level singleton state for one ship body:
body: RAPIER.RigidBody | null— dynamic body with locked rotations, CCD enabled, zero linear damping.collider: RAPIER.Collider | null— convex-hull collider (or ball fallback if hull is degenerate).colliderHandle: number— handle for matching collision events back to the ship.
- Interpolation cache:
prevX, prevY, currX, currY— previous and latest physics positions used to lerp the rendered position. - Collision-group bitmasks for the three participating layers:
COLLISION_GROUP_SHIP(0x00010006) — member group 0, filters terrain + enemies.COLLISION_GROUP_TERRAIN(0x00020001) — member group 1, filters ship.COLLISION_GROUP_ENEMY(0x00040001) — member group 2, filters ship.
- The
RapierShipobject exporting create / destroy / syncToRapier / syncFromRapier / teleport / teleportKeepVelocity / setLinvel / updateCollider / getColliderHandle / getBody / getCollider / setRestitution / setFriction / setCcdEnabled.
READS FROM
RapierWorld.getWorld()— singleton physics world used for body and collider creation/removal.- Caller-supplied hull polygon vertices (unit-space
[-1, 1]) andhullScaleto size the convex hull. - Caller-supplied
restitution(defaults to0.09) andfriction(defaults to0.01). - After each step, reads
body.translation()andbody.linvel()to refreshcurrX,currY, and current velocity.
PUSHES TO
- The Rapier world: creates the rigid body and collider, removes them on destroy, and removes/recreates the collider on hull swap.
- The ship body each tick:
body.setLinvel({ x, y }, true)with the movement system’s computed velocity.body.setRotation(angle, true)to align the convex hull with the ship’s facing angle.
syncFromRapier(alpha)returns{ x, y, vx, vy }— the lerp-interpolated render position and the latest physics velocity — to the caller for use in rendering and gameplay state.
DOES NOT
- Does not own the physics world, step the simulation, or run collision-event drainage (see
rapier-world). - Does not compute thrust, drag, heat boost, or speed caps — that lives in the movement system; this module only mirrors the resulting velocity onto the body.
- Does not manage rotation dynamics. The body’s rotations are locked;
setRotationonly updates the collider’s orientation. - Does not apply gravity (none configured) or linear damping (set to zero — the game handles its own drag).
- Does not support multiple ship bodies. State is module-level singletons; one ship at a time.
- Does not handle enemy or terrain bodies — only the ship.
Signals
- Collider is created with
ActiveEvents.COLLISION_EVENTS | ActiveEvents.CONTACT_FORCE_EVENTSand acontactForceEventThresholdof0.0, so every contact is reported through Rapier’s event channels. restitutionCombineRuleisMax, so the highest restitution wins when the ship contacts another collider.getColliderHandle()exposes the ship collider’s handle so collision-event consumers can identify ship-vs-other contacts.console.warn('[RapierShip] convexHull failed, using ball fallback')fires if the supplied hull polygon is degenerate; the collider falls back to a ball of radiushullScale * 0.5.
Entry points
create(x, y, hullVerts, hullScale, restitution = 0.09, friction = 0.01): void— build the dynamic body with locked rotations, zero linear damping, CCD enabled; build the convex-hull collider (or ball fallback); seed the interpolation cache.destroy(): void— remove the body (which removes its collider) from the world and clear all module state.syncToRapier(shipVx, shipVy, shipAngle): void— push velocity and facing angle to the body before the Rapier step.syncFromRapier(alpha): { x, y, vx, vy }— after the Rapier step, rollcurrX/currYintoprevX/prevY, read the new translation and velocity, and return a lerp-interpolated render position alongside the current velocity.teleport(x, y): void— set translation, zero linear velocity, and reset the interpolation cache to the new position. Used for hub teleport, death reset, level transition, and portal boundary.teleportKeepVelocity(x, y): void— set translation, leave linear velocity untouched, and reset the interpolation cache. Used for portal-boundary wrap and push-out cases.setLinvel(vx, vy): void— set linear velocity directly. Used for speed bleed and speed boost.updateCollider(hullVerts, hullScale, restitution = 0.09, friction = 0.01): void— remove the existing collider and create a new convex hull (or ball fallback) on the same body. Used when the ship hull changes in playground.getColliderHandle(): number,getBody(): RAPIER.RigidBody | null,getCollider(): RAPIER.Collider | null— accessors for collision-event matching and advanced queries.setRestitution(value): void,setFriction(value): void,setCcdEnabled(enabled): void— live tuning for playground.
Pattern notes
- Module-level singleton, no class. State is held in file-scope
letbindings; the exportedRapierShipobject is a method bag over that state. Only one ship body can exist at a time. - Game-authoritative velocity. Movement code computes velocity each frame; the body’s
linearDampingis0and no gravity is applied, so Rapier is used purely as a collision-resolution surface around game-driven motion. Per-tick flow: movement system computes velocity →syncToRapierwrites velocity and angle → world step →syncFromRapierreads corrected position back. - Rotation locking.
RigidBodyDesc.dynamic().lockRotations()prevents collisions from spinning the ship.setRotationis still called each tick so the convex hull tracks the ship’s facing angle for accurate collision shape. - Alpha interpolation. Render position is
prev + (curr - prev) * alpha, wherealphais the fixed-step accumulator remainder.prevX/prevYis rotated forward insidesyncFromRapierbefore the new position is read, so each fixed-step step produces a clean prev→curr pair for the next render frames to lerp across. - All teleport paths (
teleport,teleportKeepVelocity, andcreate) reset bothprevandcurrto the new position, preventing an interpolation streak across the jump. - CCD (continuous collision detection) is enabled on creation to prevent tunneling at high ship velocities; it can be toggled at runtime via
setCcdEnabled. - Convex hull built from unit-space
[-1, 1]ship polygon vertices flattened into aFloat32Arrayand scaled byhullScale. Degenerate-hull fallback is a ball collider sized tohullScale * 0.5. - Collision groups are encoded as a single 32-bit value with the high 16 bits as membership and the low 16 bits as the filter mask: ship
0x0001 | 0x0006(collides with terrain + enemy), terrain0x0002 | 0x0001, enemy0x0004 | 0x0001. - Collider rebuild via
updateColliderremoves the old collider withwakeUp = falseand creates a new one on the same body; restitution, friction, collision groups, and active-event configuration are reapplied each rebuild rather than copied from the prior collider.