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:
- The hit is NOT blocked by the invuln gate at stage A (the ship was vulnerable when the damage arrived).
- The damage routed through the shield branch (
ship.shield > 0at entry). - 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 freeze —
game._hitFreezeTimerclamps to at least 0.10s andgame._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 viaship._shieldBreakPulse).ship._shieldBrokenTimer = 2.5drives 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 iframes —
ReviveSystemin the same file consumes a Death Defiance token and runs a 7-seconddeathTimer. The revive resurrection itself sets a longer invuln window elsewhere; the bridge restoresship.aliveand grants enough iframes for the player to clear the death zone. - Hit-stop / freeze-frame iframes — during
game._hitFreezeTimer > 0the 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.invulnerablefor their full duration. The shield-broken grace’sMath.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
| What | Where |
|---|---|
CFG.INVULN constant | engine/core/config/_gameplay.ts:38 |
| Grace granted | engine/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 VFX | Same branch — particles, sonar arcs, screen invert, camera shake, juice cue |
_shieldBreakPulse flag | Bridge reads this on the next frame to spawn shockwave + clear enemy bullets |
| Background regen state | ship._shieldRecovering = true; ship._shieldBackgroundRegen = 0 — drives the post-break recovery in movement.ts |