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 RapierShip object 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]) and hullScale to size the convex hull.
  • Caller-supplied restitution (defaults to 0.09) and friction (defaults to 0.01).
  • After each step, reads body.translation() and body.linvel() to refresh currX, 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; setRotation only 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_EVENTS and a contactForceEventThreshold of 0.0, so every contact is reported through Rapier’s event channels.
  • restitutionCombineRule is Max, 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 radius hullScale * 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, roll currX/currY into prevX/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 let bindings; the exported RapierShip object 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 linearDamping is 0 and 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 → syncToRapier writes velocity and angle → world step → syncFromRapier reads corrected position back.
  • Rotation locking. RigidBodyDesc.dynamic().lockRotations() prevents collisions from spinning the ship. setRotation is 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, where alpha is the fixed-step accumulator remainder. prevX/prevY is rotated forward inside syncFromRapier before 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, and create) reset both prev and curr to 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 a Float32Array and scaled by hullScale. Degenerate-hull fallback is a ball collider sized to hullScale * 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), terrain 0x0002 | 0x0001, enemy 0x0004 | 0x0001.
  • Collider rebuild via updateCollider removes the old collider with wakeUp = false and 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.