PURPOSE

Apply per-frame gameplay overrides to a ship while it is inside a warp puddle. Scales thrust, suppresses heat generation, and bleeds existing heat as a function of how deep the ship is inside the puddle (ship.warpT, 0..1).

OWNS

  • applyWarpPuddleOverrides(ship) — the override function called each frame.
  • THRUST_MULT_MAX constant (1.25) — the thrust multiplier at fully-inside.
  • The contract that overrides are absolute writes derived from ship._base, not multiplicative accumulations on ship.

READS FROM

  • ship.warpT — interpolation parameter (0 = outside, 1 = fully inside).
  • ship._base.thrust — snapshotted base thrust value.
  • ship._base.heatBurnRate — snapshotted base heat burn rate.
  • ship.heat — current heat state (read and rewritten).

PUSHES TO

  • ship.thrust — written each frame as base.thrust * thrustMult.
  • ship.heatBurnRate — written each frame as base.heatBurnRate * (1 - warpT).
  • ship.heat — multiplied by (1 - warpT) when warpT > 0.

DOES NOT

  • Does not modify ship._base (the snapshot is read-only here).
  • Does not handle camera zoom — that lives in camera.ts.
  • Does not snapshot ship._base — that is the bridge’s responsibility at mission start.
  • Does not detect puddle entry/exit — only consumes the already-computed warpT.
  • Does not early-exit on warpT === 0; the write path is intentionally idempotent at warpT = 0 so the ship snaps back to base on exit.
  • Does not call Modifiers.recalc and does not depend on the ship being marked dirty.

Signals

None. Pure per-frame mutation of the passed-in ShipState.

Entry points

  • applyWarpPuddleOverrides(ship: ShipState): void — invoked by the ship/world update path each frame while warp puddles are in play.

Pattern notes

  • Idempotent absolute writes: each frame writes the effective value from _base with the warp multiplier folded in, rather than multiplying the live value. At warpT = 0 the multiplier is 1.0, so values immediately match base on exit — no stuck state.
  • Historical bug fixed by this pattern: a previous version used ship.thrust *= N per frame. Because Modifiers.recalc(ship, _base) only runs on dirty frames, the multiply compounded across non-dirty frames, producing ~20× speed after ~30 frames with no recovery on exit.
  • Defensive abort: if ship._base has not yet been snapshotted, the function returns without touching the ship.
  • Type guards (typeof base.thrust === 'number') gate each write so missing base fields don’t write NaN.
  • Thrust scaling is linear in warpT: 1 + (THRUST_MULT_MAX - 1) * warpT.
  • Heat-burn suppression and heat drain both use the (1 - warpT) factor, reaching zero at fully-inside.
  • ship.heat is treated as dynamic state (not a base stat), so a per-frame multiply is acceptable; the t > 0 guard makes it a no-op outside the puddle.
  • Requires bridge.ts to snapshot ship._base.thrust and ship._base.heatBurnRate at mission start.