PURPOSE

Ability registry and per-frame dispatch for the bosses-as-enemies pattern system. Holds the catalog of AbilityDef entries, constructs per-instance state on hosts, and ticks each instance every frame — decrementing cooldowns, driving sustained/telegraphed work, and firing patterns when ready. This file owns only the registry, dispatch, and shared cooldown bookkeeping; pattern fire fns in patterns.ts own bullet pushes and VFX.

OWNS

  • PatternId union — the nine valid pattern names (radial_burst, aimed_volley, cone_slam, telegraphed_circles, beam_sweep, spiral_vortex, wall_sweep, spawn_telegraph, death_beam).
  • AbilityParamValue type — allowed param scalars (number | string | string[]).
  • AbilityDef interface — id, pattern, cooldown, optional startDelay, and params payload.
  • ABILITY_REGISTRY — the live id→def map, seeded from ABILITY_DEFS.
  • registerAbility(def) — id-unique insertion; throws on collision.
  • getAbility(id) — strict lookup; throws on unknown id.
  • createAbilityInstance(defId) — factory producing { defId, cooldownRemaining: startDelay ?? 0, state: {} }.
  • tickAbility(host, instance, dt, world) — per-instance dispatch: sustained-state tick → busy gating → cooldown decrement → firePattern.
  • tickAllAbilities(host, dt, world) — per-host loop, skipped when !host.alive.
  • _wasBusy transient marker on instance.state for busy→idle edge detection.

READS FROM

  • ../core/typesAbilityInstance, EnemyEntity, WorldState type imports.
  • ../../data/abilitiesABILITY_DEFS static catalog used to seed ABILITY_REGISTRY.
  • ./patternsfirePattern and tickSustained for actual pattern execution.
  • host.abilities array and host.alive flag on each enemy.
  • instance.defId, instance.cooldownRemaining, instance.state per tick.
  • def.cooldown and def.startDelay for timing.

PUSHES TO

  • ABILITY_REGISTRYregisterAbility mutates this map at module-init time for boss data files registering signature abilities.
  • instance.cooldownRemaining — written on construction (to startDelay), on busy→idle transition (reset to def.cooldown), on per-frame decrement (-= dt), and as backstop after firePattern.
  • instance.state._wasBusy — written every tick to record busy state for next-frame edge detection.
  • Indirectly into bullet pools and VFX via firePattern / tickSustained (those calls live in patterns.ts).

DOES NOT

  • Does not push bullets, spawn VFX, or read player position — pattern fns in patterns.ts do that.
  • Does not own enemy lifecycle (spawn, death, despawn).
  • Does not own ability data — data/abilities is the source of truth for the catalog.
  • Does not handle player-side abilities — only enemy/boss patterns.
  • Does not branch by PatternId; dispatch is by delegation to firePattern / tickSustained.
  • Does not silently fail on bad ids — both getAbility and registerAbility throw.
  • Does not tick dead hosts — tickAllAbilities short-circuits on !host.alive.

Signals

None. Communication is direct function calls — no event bus, no pub/sub. Bridge calls tickAllAbilities once per enemy per tick; tickAbility calls into patterns.ts synchronously.

Entry points

  • tickAllAbilities(host, dt, world) — called by the engine bridge once per enemy per frame; iterates host.abilities.
  • tickAbility(host, instance, dt, world) — per-instance dispatch (exported, but primarily driven via tickAllAbilities).
  • createAbilityInstance(defId) — called when attaching abilities to a newly spawned enemy host.
  • registerAbility(def) — called at module-init from boss data files for signature abilities.
  • getAbility(id) — lookup helper used by callers needing the def directly.
  • ABILITY_REGISTRY — exported for read-only inspection of the full catalog.

Pattern notes

  • Ability factory. createAbilityInstance is the sole constructor for AbilityInstance. It seeds cooldownRemaining to def.startDelay ?? 0, so the first fire honors any configured warm-up before any tick decrement. state starts as an empty object — patterns populate their own transient markers (telegraph timers, emit cadence counters, sweep angles) lazily on first tick.
  • Cooldown rule. def.cooldown means “seconds idle between fires,” measured from the end of the telegraph + sustain phase, not from the moment the pattern fires. Without this, sustained patterns (beam_sweep, death_beam, spiral_vortex) would effectively cycle at sustainMs + cooldown instead of the designer’s intent.
  • Busy→idle edge detection. Each tick reads _wasBusy off instance.state, calls tickSustained to learn the current busy state, and writes the new value back. On a busy→idle transition, cooldown resets to def.cooldown and the tick returns early. While busy, the tick returns without touching cooldown.
  • Cooldown decrement. Once idle and cooldownRemaining > 0, decrement by dt and return. Only when the timer reaches zero does firePattern run.
  • Backstop cooldown reset. After firePattern, cooldownRemaining is set to def.cooldown directly. This is a backstop for legacy patterns with no telegraph at all — patterns with a telegraph countdown will be caught by the busy→idle path on a later frame and reset there.
  • Strict id discipline. Collisions throw at registration time; unknown ids throw at lookup time. No silent fallbacks — bad ids surface immediately, consistent with the global “crash on bad data” rule.
  • Per-host loop. tickAllAbilities uses an index-based for loop over host.abilities (no allocations per tick). Dead hosts short-circuit before any work.
  • Module-init registration. Bulk catalog lives in ABILITY_DEFS; boss data files use registerAbility for one-off signature abilities. Both paths land in the same ABILITY_REGISTRY map.

1 item under this folder.