engine/physics

PURPOSE — Per-frame integration of ship movement (thrust, drag, turn, speed cap), heat accumulation and burnout, shield regen and invulnerability, hand-rolled SAT polygon collision against terrain/floaters, and the Rapier2D rigid-body world that owns the ship body, enemy sensors, terrain hulls, and sealed-arena walls.

OWNS

  • Ship kinematic state writes — velocity (vx, vy), angle, position fallback path, previous-angle and smoothed-angular-velocity for turn squash, previous-speed latch for stop-shake, turn-squash amount.
  • Ship heat state — heat, coolingAccel, stall flag, stall-spin and stall-spin-decay, burnout HP application.
  • Shield regen state machine — regen delay timer, background-fill progress, recovering flag, partial regen tick, shield-hit timer, shield-broken timer, hit-flash / damage-flash / spawn-intro timers, hull pulse.
  • Invulnerability state — invulnerable flag, invulnTimer, wall-clock watchdog accumulator, force-clear on stuck-invuln, Star Power invuln override.
  • Trail / Star Power / Phoenix speed-boost timer decay.
  • Rapier2D world singleton — world handle, event queue, fixed-timestep accumulator, last-step-count, init/cleanup/rebuild lifecycle.
  • Ship Rapier body and convex-hull collider — body handle, collider handle, previous/current interpolation positions, rotation lock, CCD flag, restitution/friction live tuning, hull rebuild on ship swap.
  • Per-enemy kinematic sensor bodies — identity-to-body map, handle-to-enemy reverse lookup, alive-set diffing, body creation/removal on spawn/death.
  • Static terrain bodies, static floater bodies, sealed-arena cuboid wall bodies — separate maps, defaults for restitution/friction, bulk live-tuning over all colliders.
  • Collision-group constants for ship / terrain / enemy memberships and filters.
  • Hand-rolled SAT scratch buffers — projection lo/hi globals, shared nearby-terrain result array.
  • Rapier debug overlay state — enabled flag, options (colliders/contacts/velocity).

READS FROM

  • engine/core for ship, game, playerInput, world, CFG, CHUNK_SIZE, camera, and shared utility helpers (lerp, clamp).
  • engine/core/signals to fire stall_start.
  • engine/player/states for tickPlayerStates and the Star Power exclusive-state check used by heat lock, thrust boost, max-speed boost, and invuln override.
  • engine/rendering/camera for stop-shake and heat-shake.
  • engine/vfx/particles for burnout particle bursts.
  • engine/vfx/juice for the shield_broken juice event on burnout.
  • Ship per-instance tuning fields read off shipthrust, maxSpeed, turnSpeed, drag, dragCurve, accelCurve, rotates, heatBurnRate, heatCoolRate, burnoutSeverity, shieldRegenDelay, shieldRegenFillTime, shieldRegenRate, shieldMax, stopShakeIntensity, stopSpringAmt, heatShakeIntensity, heatShakeThreshold, terrainRestitution, terrainFriction, plus boost timers and per-mode flags.
  • world.terrain, world.chunks for the nearby-terrain spatial query.
  • @dimforge/rapier2d-compat WASM module.

PUSHES TO

  • engine/rendering/camera (shake calls, plus camera-update / shake compatibility wrappers).
  • engine/vfx particles and juice on burnout.
  • engine/core/signals (stall_start).
  • engine/player/states via tickPlayerStates.
  • Rapier2D world — ship body linvel + rotation each frame, enemy sensor positions each frame, static terrain/floater/arena-wall body creation on demand.
  • ship state itself — corrected position/velocity read back from the Rapier body after each step (the rest of the engine reads ship.x / ship.y).

DOES NOT

  • Hold game state, ship data, or input — engine/core owns those; this module only writes them.
  • Decide when to spawn, despawn, or update the hull of enemies / terrain / floaters — the world loop and chunk system call RapierEnemies.sync, RapierTerrain.addPiece / removePiece / addFloater / removeFloater when those events happen.
  • Detect bullet hits, resolve damage, apply knockback, life steal, shield absorption math, or contact-event-to-damage translation — those live in engine/combat and engine/collision-resolver. This module only spawns the sensors and exposes handle-to-enemy lookup so the resolver can match events to enemies.
  • Step bullets, beams, or any non-ship projectile — bullets stay on the engine’s spatial grid, not in Rapier.
  • Trace terrain polygons, bake asteroid visuals, or compute worldVerts / normals — terrain generation writes those fields; this module reads them.
  • Render the game — the Rapier debug renderer draws collision-shape diagnostics only, gated behind a flag, and is the only rendering this module does.
  • Define ship stats, hull shapes, or drag/heat tuning values — those are on ship or in CFG.
  • Schedule the fixed-step loop or call RapierWorld.step — the engine loop drives that and consumes the returned interpolation alpha.

Signals fired

  • stall_start — emitted once when heat reaches the burnout threshold.

Signals watched — none. Player-state transitions are polled via hasExclusiveState.

Entry points

  • Physics.update — top-level per-frame tick: stall spin-down or normal thrust/drag/speed/heat path, plus shield and invuln updates.
  • Physics._updateShield — internal shield regen state machine and flash-timer decay.
  • Physics._updateInvuln — internal invuln countdown with Star Power override and stuck-invuln watchdog.
  • updateShipPhysics — module-level wrapper used by the engine harness.
  • updateCamera / shakeCamera — compatibility wrappers that forward to the camera module.
  • nearbyTerrain — chunk-indexed spatial query for terrain pieces within a radius (returns a shared, reused result buffer).
  • terrainCollide — push an entity out of overlapping terrain polygons via SAT, optionally using a ship hull polygon (poly-vs-poly) or a circle (circle-vs-poly).
  • floaterCollide — same SAT push-out applied against a caller-supplied floater list.
  • segmentTerrainIntersect — line-of-sight test for a segment against terrain polygons.
  • RapierWorld.init / cleanup / rebuild / isReady — physics-world lifecycle and the WASM-borrow-lock recovery rebuild.
  • RapierWorld.step — accumulator-driven fixed-timestep advance; returns interpolation alpha for the renderer.
  • RapierWorld.resetAccumulator — drop accumulated time after tab background.
  • RapierWorld.getWorld / getEventQueue / getLastStepCount / FIXED_DT — raw handles and timing readback.
  • RapierShip.create / destroy — build or tear down the ship body and convex-hull collider.
  • RapierShip.syncToRapier — push computed velocity and rotation to the body before the step.
  • RapierShip.syncFromRapier — read corrected position back, interpolating between previous and current physics positions.
  • RapierShip.teleport / teleportKeepVelocity — reposition the body with or without zeroing velocity.
  • RapierShip.setLinvel — direct velocity override.
  • RapierShip.updateCollider — rebuild the convex hull on ship swap.
  • RapierShip.setRestitution / setFriction / setCcdEnabled — live property tuning.
  • RapierShip.getColliderHandle / getBody / getCollider — handles and raw refs for collision-event matching and debug.
  • RapierEnemies.sync — diff alive enemies against tracked bodies each frame, creating sensors for new ones and removing dead ones.
  • RapierEnemies.getEnemyByHandle — reverse-lookup used by the collision-event resolver.
  • RapierEnemies.clear — drop every enemy body (level transition).
  • RapierEnemies.getCount / getPhysics — debug count and per-enemy body lookup.
  • RapierTerrain.addPiece / removePiece / hasPiece — register a terrain body from a piece’s worldVerts, or remove on chunk unload.
  • RapierTerrain.addFloater / removeFloater / hasFloater — same for floaters.
  • RapierTerrain.clear — drop terrain, floaters, and arena walls together.
  • RapierTerrain.setDefaults / setAllRestitution / setAllFriction — live tuning for new and existing bodies.
  • RapierTerrain.addArenaWall / clearArenaWalls — fixed cuboid walls for sealed boss arenas.
  • RapierTerrain.getTerrainCount / getFloaterCount — debug counts.
  • RapierDebug.setEnabled / isEnabled / setOptions / getOptions — toggle and configure the debug overlay.
  • RapierDebug.render — draw collider outlines, ship velocity arrow, and ship-contact points to the game canvas.
  • RapierDebug.getStats — one-line HUD summary of steps, terrain count, and enemy-sensor count.

Pattern notes

  • The module straddles two physics implementations: a hand-rolled SAT path (collision.ts) used as the historical / fallback collision model and ad-hoc spatial queries (nearbyTerrain, segmentTerrainIntersect), and a Rapier2D path (everything rapier-*.ts) used for the live ship body and event-driven contact detection. Both are wired up at once — Rapier owns the ship integration when ready, the hand-rolled SAT remains exposed for callers that need polygon push-out or line-of-sight against world.terrain without going through Rapier.
  • Movement integrates entirely outside Rapier — thrust, drag curves, heat scaling, speed cap, turning, and squash all run in plain math each frame. The output is shoved into the Rapier body just before its step, then the corrected position is read back. Rotation is locked on the Rapier side; ship.angle is authoritative.
  • A position fallback exists for when Rapier isn’t ready (boot, tests): integrate vx/vy directly. The same branch appears in the stall path and the normal path.
  • Rapier stepping uses a fixed-timestep accumulator capped to prevent spiral-of-death. The accumulator can be reset on tab background. A rebuild path exists to recover from a WASM borrow-lock without re-initializing the module.
  • Enemies are kinematic sensors only — they detect overlap but produce no physics response. Push-apart between ship and enemies is handled outside this module by the collision resolver, which uses the handle-to-enemy reverse lookup to translate Rapier events back to game enemies.
  • Terrain bodies are created lazily once a piece has worldVerts traced; pieces without traced polygons fall through to a bounding-circle fallback. Sealed-arena walls are tracked in their own list so the arena tear-down can drop them without touching biome terrain.
  • The hand-rolled SAT path uses module-level scratch globals (_projLo / _projHi) and a shared _nearbyResult array — callers must not retain references to the returned terrain list across frames.
  • The invuln watchdog is wall-clock-based (not game-time) so intentional time freezes don’t trip it; it self-exempts during Star Power and when the gauntlet runner sets _dontStuckInvuln.