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 —
invulnerableflag,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/coreforship,game,playerInput,world,CFG,CHUNK_SIZE,camera, and shared utility helpers (lerp,clamp).engine/core/signalsto firestall_start.engine/player/statesfortickPlayerStatesand the Star Power exclusive-state check used by heat lock, thrust boost, max-speed boost, and invuln override.engine/rendering/camerafor stop-shake and heat-shake.engine/vfx/particlesfor burnout particle bursts.engine/vfx/juicefor theshield_brokenjuice event on burnout.- Ship per-instance tuning fields read off
ship—thrust,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.chunksfor the nearby-terrain spatial query.@dimforge/rapier2d-compatWASM module.
PUSHES TO
engine/rendering/camera(shake calls, plus camera-update / shake compatibility wrappers).engine/vfxparticles and juice on burnout.engine/core/signals(stall_start).engine/player/statesviatickPlayerStates.- Rapier2D world — ship body linvel + rotation each frame, enemy sensor positions each frame, static terrain/floater/arena-wall body creation on demand.
shipstate itself — corrected position/velocity read back from the Rapier body after each step (the rest of the engine readsship.x/ship.y).
DOES NOT
- Hold game state, ship data, or input —
engine/coreowns 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/removeFloaterwhen 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/combatandengine/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
shipor inCFG. - 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’sworldVerts, 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 (everythingrapier-*.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 againstworld.terrainwithout 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.angleis authoritative. - A position fallback exists for when Rapier isn’t ready (boot, tests): integrate
vx/vydirectly. 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
worldVertstraced; 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_nearbyResultarray — 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.