engine/combat

PURPOSE — Resolves all damage and collision events for the live frame: applies incoming damage to the player and to enemies/destructibles through the canonical damage chain, runs the broad- and narrow-phase collision passes between player bullets / enemy bullets / ship body / enemy bodies, and stages the kill-side bookkeeping (streaks, milestones, elite/boss bonuses, knockback, deferred death VFX, signal emission).

OWNS

  • Enemy mutable combat fields: hp, shieldHp, flashAmount, vx/vy knockback, _hitImmune, _contactCooldown, _immuneTextCooldown, _tbonedTextCooldown, _stunTimer, _dmgSquashT, _frozenForLag, _lagHp, _deathVfxT/_deathVfxMax, _explodeDebrisNow, _lastDmgTag, _pendingXpCount/_pendingXpGame, _chargerPhase immunity flash latch.
  • Destructible mutable combat fields: hp, flashAmount, alive.
  • Ship damage-chain side-effects: hp, shield, _hitFlash, _hitFlashColor, _dmgBlueFlash, _dmgWhiteFlash, _hullPulse, shieldHitTimer, shieldHitIntensity, _shieldHitAngle, _shieldRecovering, _shieldBrokenTimer, _shieldBackgroundRegen, _shieldBreakPulse, shieldRegenTimer, invulnerable, invulnTimer, post-knockback vx/vy, alive.
  • GameState combat-cycle counters: killStreak, bestStreak, killStreakTimer, lastStreakMilestone, stats.totalKills / eliteKills / damageDealt / damageTaken, tracking.damageTaken / lowestHpPercent / deathDefianceUsed, dmgFlash, _hullFlash, _hitFreezeTimer, _invertScreenTimer, timeDilation (hit-stop), _pendingBarDamage, _lastBossDeathX/Y, _wcSpawnedThisLevel, phase, deathTimer.
  • Module-private state: combo-kill rolling window and shake cooldown, deferred-death-VFX queue, last-impact-shake throttle timestamp.
  • ReviveSystem singleton (active flag + countdown timer for the Death Defiance grace window).
  • The per-frame enemy spatial-grid contents (rebuilt at the top of resolve).
  • Loot spawns produced as a kill consequence: XP orbs pushed into world.xpOrbs, weapon and artifact boxes pushed into world.weaponBoxes / world.artifactBoxes, floating damage numbers pushed into world.dmgNumbers.

READS FROM

  • engine/core for WorldState / ShipState / GameState, CFG, signal bus, set pool.
  • engine/core/spatial-grid (enemyGrid) for broad-phase enemy queries on every bullet, beam, line, chain, body-contact, and warp-line pass.
  • engine/enemies for EnemyBehaviors onDeath callbacks, GameMaster (AI director pressure), and ENEMY_PROJ_ARM_DISTANCE.
  • engine/boss/encounter for the shared boss VFX kit and per-def onBodyDeath / onDeath cinematic hooks via BOSS_DEFS.
  • engine/affixes for the damage-filter chain and on-death affix chain.
  • engine/effects/enemy-status for the shred multiplier and engine/effects/custom-handlers for flame-zone spawning on incendiary echoes.
  • engine/player/states for the Star Power exclusive-state check (read via hasExclusiveState, available to dependents).
  • engine/world/artifacts for weapon knockback force, stun duration, and the knockback-notify hook.
  • engine/world/pickups and engine/world/xp-orbs for kill-consequence spawns.
  • engine/physics/collision (nearbyTerrain) and engine/physics/rapier-ship / rapier-world to keep the Rapier body synced after a ship-body collision push.
  • data/weapons (WEAPON_MAP) for damageTag / secondaryDamageTag / hitShake on impact.
  • data/enemies (getEnemyCollisionRadius) for collision sizing.
  • data/bosses (BOSS_DEFS) for boss-def death cinematics.
  • data/kill-streaks for streak milestone thresholds and elite/boss kill bonuses.

PUSHES TO

  • engine/vfx — particles, damage-number/HP/shield/XP/notification accumulators, juice cues, explosion FX, AoE explosion, sonar ring shards, post-FX rings/scars/arcs/exhausts/streaks, player glow flashes.
  • engine/rendering — camera shake, HP flash trigger, tutorial trigger, hit-direction indicator, shield visual radius lookup.
  • engine/audio — sampled shield-hit and hull-hit cues, archetype-specific death audio.
  • engine/telemetry — damage-chain trace record on every damagePlayer call, weapon-hit records on every player-bullet collision.
  • engine/combat (self) — damageEnemy is re-entered from beam, chain, line, projectile, body-ram, AoE-blast, warp-line, and chain-explosion sites.
  • Signal bus — see “Signals fired” below.

DOES NOT

  • Pick targets, spawn projectiles, run per-bullet flight behavior, apply weapon stat resolution, or own bullet allocation — that lives in engine/weapons.
  • Move enemies, run AI behavior, or own enemy lifecycle outside of the damage→frozen-for-lag→dying transition (the dying→removed step is in the bridge).
  • Render anything — only writes state the renderers read.
  • Run the fixed-step game loop, schedule the per-frame pipeline, or update timers that aren’t combat-scoped (the ship’s invuln timer is decremented elsewhere; combat only sets it).
  • Decide weapon stats, scaling curves, damage tags, archetypes, or hit-shake values — consumes them from data/weapons.
  • Own the boss encounter state machine, arena, or director — only fires the body-kill / final-sharer signals and dispatches the configured death cinematic.
  • Define affix behavior or scripted DoT — only routes incoming damage through the filter chain and the on-death chain.
  • Reconcile the Rapier physics simulation step — only re-syncs ship position/velocity into Rapier after a body-collision push-out.
  • Persist or post-process damage-chain telemetry — hands it to the collector and returns.

Signals fired

  • damage_dealt — every enemy damage event past gates and caps, carries damage amount, hit position, last damage tag.
  • bullet_hit — every player-bullet hit on an enemy, carries enemy eid, damage, and damage tag (fires once per primary tag, plus once per secondary tag when the weapon spec has one).
  • enemy_kill — non-anchor, non-shared-health enemy killed.
  • tagged_kill — same kill as above, carries the last damage tag for type-conditional artifacts.
  • kill_streak_milestone — when the streak crosses a new threshold, carries the milestone kill count.
  • boss_anchor_destroyed — anchor-typed boss body killed (skips the regular enemy_kill path).
  • boss_body_kill — shared-health boss body killed.
  • boss_kill — final-sharer body killed, ending the encounter.
  • shield_hit — any hit that the player’s shield absorbs.
  • shield_break — the hit that drops the shield to zero.
  • hull_damage — any hit that lands hull damage.
  • player_damage — fired on both branches with a 'shield' / 'hull' tag.
  • tbone_hit — ship-body ram contact, tagged 'tbone' (in forward arc) or 'ram'.

Signals watched — none. The module reacts to call sites, not signals.

Entry points

  • CollisionResolver.resolve — top-of-frame collision pass: rebuilds the enemy spatial grid, runs the bullet / enemy-bullet / ship-body subpasses, and ticks per-enemy combat cooldown timers.
  • CollisionResolver.rebuildEnemyGrid — populates the shared enemy grid for this frame, filtering out spawn-protected, dying, and frozen-for-lag enemies.
  • CollisionResolver.resolvePlayerBulletEnemyCollisions — dispatches each player bullet to the correct collision sub-path by _collisionMode (beam trace, chain arc, line, target-only, first-hit, pierce-all) and runs first-hit collision plus pierce decrement.
  • CollisionResolver.resolveBeamTrace — projects enemies onto the beam line and damages all within beam width along the segment.
  • CollisionResolver.resolveLineCollision — point-to-segment damage between two ball endpoints with per-enemy contact cooldowns (Tesla line family).
  • CollisionResolver.resolveChainArc — acquires the first chain-arc target, applies the chain-explosion stack at the strike point, and seeds the chain state the per-bullet behavior consumes on subsequent jumps.
  • CollisionResolver.resolveEnemyBulletPlayerCollisions — broad-phase circle check against the ship’s outer radius, narrow-phase circle-vs-hull-polygon test against ship.hullPoly, then dispatches damagePlayer with the hit angle and the source-specific hp multiplier.
  • CollisionResolver.resolveShipEnemyBodyCollisions — mass-weighted body contact: push-out, knockback, speed bleed, ram damage when above the threshold (with forward-arc T-bone tagging), per-contact cooldown, post-resolution Rapier sync.
  • CollisionResolver.separateEnemies — boid-style pairwise separation force using the spatial grid (with an O(n²) fallback for empty-grid call sites; not invoked from resolve itself in the current build).
  • updateCollision — compatibility wrapper that calls CollisionResolver.resolve.
  • damageEnemy — canonical enemy damage entry: handles charger lunge immunity, boss hit-immunity windows, shield absorption, shred multiplier, affix damage-filter chain, shared-health boss cap, generic boss cap + per-part / global cooldowns, applies the damage, fires damage_dealt, runs knockback / stun / life-steal, and on lethal damage transitions the enemy into the frozen-for-lag state while firing kill signals, running affix on-death, dispatching boss-def death hooks, deferring XP-orb spawn, and selecting the archetype-flavored kill juice cue.
  • damagePlayer — canonical player damage chain (invuln gate → flat DR → threshold DR → final damage → universal knockback → shield-vs-hull route → shield-break grace), records one trace row per call into telemetry.recordDamageChain.
  • damageDestructible — applies damage to a destructible target and adds a grey floating damage number.
  • warpLineDamage — corridor damage along a warp line for nearby enemies with perpendicular knockback.
  • computePlayerDamage — applies the global player damage multiplier to a base value.
  • spawnXPOrbs — kill-consequence XP-orb spawn around an enemy, with depth/tier scaling and the global XP nerf factor, pushed off overlapping terrain.
  • pushOffTerrain — radial push to escape any overlapping asteroid bounding circle, used by every kill-side spawn site.
  • pointToLineDistance — point-to-segment distance helper, used by warpLineDamage and consumed externally.
  • applyKnockback — generic impulse helper.
  • updateDeathVfx — drains the deferred death-VFX queue (capped per frame), called by the bridge with raw dt so VFX play through hit-stop.
  • ReviveSystem.check / .update / .reset — Death Defiance grace-window state machine; check consumes a token on death and starts the timer, update advances it, reset clears it on respawn.
  • applyChainExplosion — exported AoE step invoked from chain-arc hits (currently a no-op stub pending the artifact rework).
  • transformHullPoly — applies the ship’s per-frame scale/rotate/translate to the unit-space hull polygon, returning a shared buffer that downstream collision tests consume.
  • pointInConvexPolygon — CCW cross-product winding test for point-in-polygon.
  • circleIntersectsConvexPolygon — center-in-polygon test plus per-edge nearest-point-on-segment distance test against the radius.
  • nearestEdgeNormal — push-out normal and distance from a convex polygon to an external point, used by bounce/separation physics.

Pattern notes

  • Lethal damage is a two-stage transition: damageEnemy flips the enemy into _frozenForLag (kill bookkeeping and signals fire immediately, HP bar collapses, debris and shrink-VFX latch is set), and the bridge later promotes _frozenForLag_dying when the white lag bar drains. The deferred-death VFX queue exists so a simultaneous wave of kills spreads its cosmetic spawns across frames without skipping gameplay-critical effects.
  • Combat re-entrancy: AoE blasts, beam traces, chain arcs, warp lines, and ship-body rams all call back into damageEnemy. Boss caps and affix filters short-circuit redundant chunking; pack leaders and lunging chargers short-circuit knockback / stun / damage entirely.
  • Boss damage capping is layered: shared-health bodies cap against the live pool (sum of bar-contributing siblings), non-shared bosses cap against their own hpMax and consume a per-part immunity window after every hit. Non-player damage sources are filtered out for shared-health bodies.
  • damagePlayer is the single source of truth for player damage. Every stage outcome lands in a telemetry record (including the invuln-gate early-return path) so “knocked around but no damage” symptoms can be traced back to which stage zeroed the result.
  • Shield never bleeds through to HP on the same hit. When a hit breaks the shield, a short invuln window is set before the next collision pass; the shield’s background regen restarts from zero.
  • Bullet vs. player narrow-phase uses the hull polygon (circleIntersectsConvexPolygon) rather than a single radius, gated by a cheaper bounding-circle broad-phase.
  • Ship vs. body collision is mass-weighted: ship mass derives from shipClass × shipScale, enemy mass from collision radius. Mass ratio decides who absorbs the push-out, who keeps speed, and how the speed-bleed clamp interpolates between plow-through and bounce. After resolution the Rapier kinematic body is teleported and re-velocitied to match the resolved values.
  • Combo-kill camera shake uses a rolling window plus a hard cooldown so rapid kills produce one scaled hit-stop rather than strobing the camera.
  • The hit-shake throttle in the impact-VFX path is a module-level performance.now() latch shared across all bullets, not a per-bullet timer.
  • The damage-filter chain is the chokepoint for shielded / gated / absorbed enemy affixes — if the chain returns zero, the local body still flashes a “ping” so the player sees the hit registered, but no bar-drain, no shake, no kill bookkeeping fires.
  • pushOffTerrain is applied to every kill-side spawn (XP orbs, elite reward drops, defunct weapon-chest drop site) so loot never lands inside an asteroid.
  • The polygon utilities assume CCW convex hulls — circleIntersectsConvexPolygon relies on pointInConvexPolygon’s sign convention, and transformHullPoly reuses a module-level output buffer that callers must consume before the next call.