Shield-Broken Grace

When a damage event depletes the player’s shield from a non-zero value to zero, the ship is granted a brief invulnerability window: CFG.INVULN seconds of iframes (currently 0.5s, defined in engine/core/config/_gameplay.ts). The grace is applied at the bottom of the shield-break branch of damagePlayer in engine/combat/damage.ts via:

ship.invulnerable = true;
ship.invulnTimer = Math.max(ship.invulnTimer, CFG.INVULN);

Purpose

Prevent the same attack — or a cluster of simultaneous projectiles — that broke the shield from also chunking hull HP in the same frame. Without the grace, a multi-pellet shotgun, overlapping mortar splash, or a swarm contact frame would shred the shield with one impact and then immediately bite into the hull with the remaining impacts on the same tick. The grace gives the player roughly a half-second to reposition before hull damage can start.

When it fires

The grace is granted exactly when all of the following are true in a single damagePlayer call:

  1. The hit is NOT blocked by the invuln gate at stage A (the ship was vulnerable when the damage arrived).
  2. The damage routed through the shield branch (ship.shield > 0 at entry).
  3. After absorption, ship.shield <= 0 — i.e. this specific event was the one that dropped the shield to zero.

Damage that arrives while the shield is already at zero does not trigger the grace; it routes straight to the hull branch instead.

Visual feedback

Distinct from other invuln sources so the player can read which protection layer is active:

  • Shield-break shatter — 20 large blue glass shards and 16 smaller white-blue dust particles burst radially from the shield boundary.
  • Six partial sonar-ring arcs sweep outward like a glass bubble fragmenting.
  • System shock freezegame._hitFreezeTimer clamps to at least 0.10s and game._invertScreenTimer = 0.10 (full screen color inversion).
  • Heavy camera shake — amplitude 11, duration 0.45s.
  • Juice.fire('shield_broken') plays the break-cue stinger.
  • Sig.fire('shield_break', ...) lets artifacts and effects react (e.g. shockwave pulse, enemy stun, bullet clear via ship._shieldBreakPulse).
  • ship._shieldBrokenTimer = 2.5 drives a 2.5-second visual “broken” overlay distinct from the iframe duration.

Distinct from other iframe sources

The ship.invulnerable flag is multiplexed across several systems — shield-broken grace is only one writer. Other paths:

  • Post-revive iframesReviveSystem in the same file consumes a Death Defiance token and runs a 7-second deathTimer. The revive resurrection itself sets a longer invuln window elsewhere; the bridge restores ship.alive and grants enough iframes for the player to clear the death zone.
  • Hit-stop / freeze-frame iframes — during game._hitFreezeTimer > 0 the simulation pauses (timeDilation = 0); damage doesn’t process at all because the buffer isn’t advancing, which is functionally an iframe but is a global pause rather than a per-ship flag.
  • Star Power / artifact iframes — Star Power and several artifacts flip ship.invulnerable for their full duration. The shield-broken grace’s Math.max(ship.invulnTimer, CFG.INVULN) clamp deliberately does not shorten a longer already-active timer.
  • Level transitions — phase-change windows set the invuln flag while UI animates in/out.

All of these route through the same stage-A invuln gate at the top of damagePlayer, which records the block in the damage-chain trace (invuln: true, dmgFinal: 0) and returns before any knockback or damage calculation. From the gate’s perspective shield-broken grace is identical to any other iframe source — it’s the granting site that differs.

Code references

WhatWhere
CFG.INVULN constantengine/core/config/_gameplay.ts:38
Grace grantedengine/combat/damage.ts shield-break branch (ship.invulnTimer = Math.max(ship.invulnTimer, CFG.INVULN))
Invuln gate (stage A)engine/combat/damage.ts top of damagePlayer
Shield-break VFXSame branch — particles, sonar arcs, screen invert, camera shake, juice cue
_shieldBreakPulse flagBridge reads this on the next frame to spawn shockwave + clear enemy bullets
Background regen stateship._shieldRecovering = true; ship._shieldBackgroundRegen = 0 — drives the post-break recovery in movement.ts