Camera shake stack
Camera.shake(amp, dur) requests a screen shake. amp is amplitude in world-space pixels; dur is duration in seconds. Implementation lives in engine/rendering/camera.ts.
Single slot, strongest wins
The shake state is a single slot (_shakeAmp, _shakeT, _shakeDur), not a list. A new call only replaces the in-flight shake if amp * dur exceeds the remaining strength _shakeAmp * _shakeT. Weaker shakes during a stronger one are dropped; a much stronger shake overrides whatever was decaying. This is the “stack” — strongest active request wins, the rest are ignored.
Decay
Linear decay over dur. Each frame, _shakeT ticks down by dt, and the current magnitude is _shakeAmp * (_shakeT / _shakeDur). Per-frame offset is ±mag random on each axis, applied to camera.x/y after the position lerp, then subtracted next frame so the anchor doesn’t drift.
Tick domain
Sim dt — affected by timeDilation. During slowmo the shake decays slower, matching the rest of the world.
Callers
| Site | Call | Notes |
|---|---|---|
Shield break (combat/damage.ts:1037) | Camera.shake(11, 0.45) | Fixed amplitude when shields drop to zero |
Shield hit (combat/damage.ts:933) | Camera.shake(4 + shieldFrac*5, 0.18 + shieldFrac*0.12) | Scales with shield fraction lost |
Hull hit (combat/damage.ts:1129) | Camera.shake(6 + hullFracForFx*8, 0.25 + hullFracForFx*0.20) | Scales with hull fraction lost |
Boss death (Doomsayer) (data/bosses/doomsayer.ts:478) | Camera.shake(24, 0.8) | Largest one-shot in the game |
Enemy-impact telegraph (engine/bridge.ts:2457) | Camera.shake(amp, 0.15) | Caller computes amp |
Pluggable hitShake on bullets (combat/collision-resolver.ts:739) | Camera.shake(def.hitShake.amp, def.hitShake.dur) | Per-weapon authoring |
Pluggable fireShake on weapons (weapons/weapons.ts:2080) | Camera.shake(def.fireShake.amp, def.fireShake.dur) | Per-weapon authoring |
Heat overlimit (physics/movement.ts:388) | Camera.shake(heatAmp * over, 0.08) | Short duration, refreshed every frame while overheated — acts as a continuous source |
Hard-brake stop (physics/movement.ts:380) | Camera.shake(stopAmp, 0.2) | Wall-impact feel |
Heat shake is continuous, not stacking
The heat overlimit shake at movement.ts:388 uses a 0.08s duration but is re-issued every frame while the ship is over the heat limit. Because each new call is the strongest-recent request, it keeps refreshing the slot and reads as a continuous tremor. Any one-shot shake that exceeds it in amp * dur will still override.
Zoom pulse is separate
Camera.zoomPulse(factor, dur, easing?) is a sibling system with the same strongest-wins rule (compared by |1 - factor| * dur) but operates on camera.zoom multiplicatively, decays on wall-dt (independent of timeDilation), and supports easing curves. See _zoomPulse* state in engine/rendering/camera.ts.