engine/abilities

PURPOSE — Timed pattern dispatch attached to enemies (bosses or otherwise). Each ability instance lives on a host enemy, tracks cooldown plus per-pattern transient state (telegraph countdown, sustain timer, emit cadence, rotating angle), and on fire hands off to one of nine named pattern fire fns that paint telegraph VFX, schedule the deferred body, and push enemy bullets into the world.

OWNS

  • The global ABILITY_REGISTRY keyed by string id (registered at module init plus a static catalog table).
  • AbilityInstance runtime state on host enemies: defId back-reference, cooldownRemaining, and the opaque state bag (phase, telegraph countdown, sustain countdown, emit cadence, emit timer, angle, locked target position, last sweep direction, cached telegraph points, cached spawn points, busy-on-previous-frame flag).
  • Cooldown bookkeeping rule: the cooldown clock starts on the busy-to-idle transition, not at fire time — single-shot patterns get reset via a backstop after the telegraph elapses, sustained patterns get reset only when sustain ends.
  • The nine pattern fire fns and their deferred bodies / per-frame sustain ticks: radial burst, aimed volley, cone slam, telegraphed circles, beam sweep, spiral vortex, wall sweep, spawn telegraph, death beam.
  • The lazy module-singleton VFX kit used by every pattern fn.
  • Pattern-internal geometry and timing math: telegraph countdowns, sustain countdowns, rotating beam angles, spiral emit accumulators, wall offset spacing, ship-on-line tests, arena-position picking (random / cardinal / ring).
  • The enemy-bullet shape spawned by ability patterns, including the _cullOutsideArena and _abilityBullet marker flags.
  • Param coercion helpers that crash loudly when the wrong type is supplied — abilities reference real data, not optional config.

READS FROM

  • data/abilities for the static ABILITY_DEFS catalog seeded into the registry.
  • engine/core/types for AbilityInstance, AbilityDef, EnemyEntity, WorldState, BossArena.
  • engine/core/state for the shared game and ship module references — patterns lock the player’s position at telegraph start, aim cones from host facing, and route damage through the ship handle.
  • game.bossArena for arena geometry — diagonal as bullet max-distance, radius / center for wall geometry and default beam length, cardinalPoints / ringPoints / randomPoint for telegraph placement.
  • Host enemy fields: position, facing angle, radius, alive flag, ability instance list.

PUSHES TO

  • world.enemyBullets — every spawned ability bullet (radial burst, aimed volley, cone slam fan, spiral emits, wall sweep rows).
  • engine/combat/damage via damagePlayer for telegraph-circle detonation, beam sweep DPS application, and death beam insta-kill damage.
  • engine/enemies/spawner via GameMaster.spawnEnemy for the spawn-telegraph pattern, including affix attachment on the freshly spawned enemy.
  • engine/vfx/boss-layers via the lazy VfxLayerKit for every telegraph, charge, bloom, shockwave band, ground impact circle, additive spark burst, spiral spark burst, telegraph line, telegraph cone, beam charge, and screen-tint pulse rendered by these patterns.

DOES NOT

  • Decide which enemies own which ability instances or when an enemy first acquires one — instance lifecycle (attach / detach / persist) is the spawner’s and host enemy’s responsibility; this module only ticks instances already present on host.abilities.
  • Resolve hit detection or damage math on the bullets it spawns. Bullet-to-ship collision and damage routing live in the bridge / combat path; this module only writes the bullet record and the marker flags the bullet update loop reads.
  • Cull bullets, advance bullet positions, or run bullet despawn rules — the bridge owns the bullet update loop and reads _cullOutsideArena to decide when to drop ability bullets.
  • Pick the next ability to fire on a multi-ability host or do any priority / interrupt logic — instances tick independently, each on its own cooldown.
  • Define ability data: pattern selection, param payloads, cooldown / start-delay values, color choices, gap counts, sustain durations — all live in data/abilities.
  • Render bullets, beams, or telegraph circles — only requests VFX via the kit and writes bullet records.
  • Emit or subscribe to engine signals — patterns communicate downstream by direct call into combat, vfx, and the spawner.
  • Crash when a pattern fires outside a boss encounter — arena-dependent patterns no-op so test-mode probes of abilities on non-boss enemies stay safe.

Signals fired / Signals watched — none. The module talks downstream by direct calls (combat, vfx, spawner).

Entry points

  • tickAllAbilities — bridge-level entry called once per host per frame; iterates instances and dispatches each, skipping dead hosts.
  • tickAbility — per-instance tick; runs the sustained per-frame work first, detects the busy-to-idle transition to reset cooldown, then decrements cooldown or fires.
  • firePattern — dispatch from an idle instance into the per-pattern fire fn keyed on def.pattern.
  • tickSustained — per-frame work for telegraph countdown and sustained emission; returns busy/idle so the caller can suppress cooldown decrement during telegraph or sustain.
  • registerAbility — module-init registration; throws on id collision.
  • getAbility — registry lookup; throws on unknown id.
  • createAbilityInstance — construct a fresh instance for a def id, seeded with startDelay (or zero) as the initial cooldown so the first fire respects the configured warm-up.

Pattern notes

  • Pattern dispatch is two flat switch blocks on def.pattern — one in firePattern for the telegraph-start fn, one in tickSustained for the deferred-body fn — both exhaustively checked against the PatternId union. Adding a pattern means editing both blocks plus adding the union case.
  • Telegraph + sustain state is stored in the instance’s opaque state bag (phase, telegraph, sustain, cadence, emitTimer, angle, targetX, targetY, lastDir), lazily initialised on first read. Pattern-specific scratch (cached telegraph points for circles, cached spawn points for the hatch pattern) is appended directly to the same bag with underscore-prefixed keys.
  • Cooldown rule: cooldown is “seconds idle between fires” and starts on the busy-to-idle edge, not at fire time. The instance carries a _wasBusy flag on its state bag that tickAbility reads each frame to detect the transition and reset cooldown then. A backstop reset at fire time covers patterns that complete inside their telegraph fn with no sustain.
  • Single-shot patterns leave state.phase at 'idle' between fires and rely on cooldown gating. Sustained patterns (beam sweep, death beam, spiral vortex) transition 'telegraph' -> 'sustain' and tick their own per-frame work until state.sustain runs out.
  • Single-shot telegraphed patterns lock the target geometry (player position for aimed volley, facing angle for cone slam, chosen ground points for telegraphed circles and spawn telegraph) at telegraph start so the deferred body honors what the player saw.
  • Telegraphed circles damages the player directly on detonation via a circle-distance check, not by spawning AOE bullets — deterministic, no tick-order dependence on bullet resolution.
  • Spiral vortex has no telegraph phase; it ramps in visually via a spark burst, then accumulates emitTimer and emits bulletPerEmit rings each interval while rotating angle.
  • Wall sweep flips its starting edge each fire via state.lastDir, alternating sides, and gaps are rolled at execute time along the perpendicular axis.
  • Spawn telegraph spawns enemies inline at execute time (not via a deferred queue) and attaches any configured affix ids directly to the freshly spawned enemy.
  • Param coercion is strict: number / string / string-array helpers throw on type mismatch. Optional variants take a fallback. Bad data is loud.
  • Arena-dependent patterns guard on getArena() returning a live boss arena and no-op otherwise, so attaching abilities to non-boss enemies for test-mode probing never crashes.
  • The VFX kit is a lazy module-level singleton built on first call, to dodge VFX-system bootstrap-order issues at module load.