Invincibility (iframes) Cap

The ship’s invulnerable state is hard-capped at 5 seconds of continuous wall-clock time. The cap exists as an anti-stack safety net: if every source of iframes stacked freely, the player could accrue dozens of seconds of invulnerability via overlapping triggers (hit-stop + shield-break grace + post-revive iframes + an artifact pulse) and become functionally invincible against entire boss phases. The cap forces all iframe windows to compete for the same 5-second wall-time budget.

Stuck-invuln watchdog

A watchdog runs every physics frame. It tracks _invulnWallTime, the wall-clock seconds the ship has been continuously invulnerable. The accumulator resets the instant ship.invulnerable flips false.

When _invulnWallTime > 5 and the game phase is playing, the watchdog force-clears the state: sets invulnerable=false, zeros invulnTimer, zeros _invulnWallTime, and logs [BUG] ship.invulnerable stuck for Xs — force-clearing to the console. This is treated as a bug guard rather than a balance rule — any legitimate iframe source should expire well before 5 seconds.

The accumulator uses raw dt (real wall-clock seconds), not scaled dt, so intentional game-time freezes (weapon chest pickup, hit-stop pauses) do not inflate the counter. If dt is 0 in a given frame, the counter does not advance.

Bypass: _dontStuckInvuln + setGodMode

The boss gauntlet test harness needs to hold god-mode for the full duration of a 60-second-plus boss fight. To prevent the watchdog from fighting the test, the harness flips two flags before invoking setGodMode:

  • ship._dontStuckInvuln = true — early-returns the watchdog and keeps _invulnWallTime pinned to 0.
  • setGodMode(true) — sets ship.invulnerable = true via the bridge.

The bypass must be set before _configurePlayerForMode flips invulnerable on, otherwise a single stale frame can seed the accumulator and trip a spurious warning later. The gauntlet runner clears both flags on teardown.

Star Power exception

Star Power is the one in-game iframe source that legitimately holds invulnerable past 5 seconds (15 seconds base, 60-plus seconds with stacking). The watchdog explicitly checks hasExclusiveState(ship, 'starpower') and skips the force-clear branch while Star Power is active. While the state is on, ship.invulnerable is reasserted every frame and invulnTimer is held at exclusiveStateTimer.

Sources of invuln

The shorter, watchdog-bound iframe sources that stack into the 5-second budget:

  • Hit-stop / shield-break graceCFG.INVULN (~0.3 seconds) granted when the shield breaks, to prevent the same attack from chunking HP in the same frame.
  • Post-revive iframes — Death Defiance and revive flows grant longer windows (the bridge sets values like 2 seconds and 3 seconds).
  • Artifact pulses — e.g. the warp-puddle module refreshes invulnTimer = 0.1 each frame the player is inside the field.
  • Star Power — the only source exempt from the watchdog (see above).

All of these route through ship.invulnerable and ship.invulnTimer. The damage-chain trace records a blocked attempt with invuln: true and the current invulnTimer value when the invuln gate rejects a hit at the top of damagePlayer.