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
PatternIdunion — the nine valid pattern names (radial_burst,aimed_volley,cone_slam,telegraphed_circles,beam_sweep,spiral_vortex,wall_sweep,spawn_telegraph,death_beam).AbilityParamValuetype — allowed param scalars (number | string | string[]).AbilityDefinterface —id,pattern,cooldown, optionalstartDelay, andparamspayload.ABILITY_REGISTRY— the live id→def map, seeded fromABILITY_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._wasBusytransient marker oninstance.statefor busy→idle edge detection.
READS FROM
../core/types—AbilityInstance,EnemyEntity,WorldStatetype imports.../../data/abilities—ABILITY_DEFSstatic catalog used to seedABILITY_REGISTRY../patterns—firePatternandtickSustainedfor actual pattern execution.host.abilitiesarray andhost.aliveflag on each enemy.instance.defId,instance.cooldownRemaining,instance.stateper tick.def.cooldownanddef.startDelayfor timing.
PUSHES TO
ABILITY_REGISTRY—registerAbilitymutates this map at module-init time for boss data files registering signature abilities.instance.cooldownRemaining— written on construction (tostartDelay), on busy→idle transition (reset todef.cooldown), on per-frame decrement (-= dt), and as backstop afterfirePattern.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 inpatterns.ts).
DOES NOT
- Does not push bullets, spawn VFX, or read player position — pattern fns in
patterns.tsdo that. - Does not own enemy lifecycle (spawn, death, despawn).
- Does not own ability data —
data/abilitiesis 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 tofirePattern/tickSustained. - Does not silently fail on bad ids — both
getAbilityandregisterAbilitythrow. - Does not tick dead hosts —
tickAllAbilitiesshort-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; iterateshost.abilities.tickAbility(host, instance, dt, world)— per-instance dispatch (exported, but primarily driven viatickAllAbilities).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.
createAbilityInstanceis the sole constructor forAbilityInstance. It seedscooldownRemainingtodef.startDelay ?? 0, so the first fire honors any configured warm-up before any tick decrement.statestarts as an empty object — patterns populate their own transient markers (telegraph timers, emit cadence counters, sweep angles) lazily on first tick. - Cooldown rule.
def.cooldownmeans “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 atsustainMs + cooldowninstead of the designer’s intent. - Busy→idle edge detection. Each tick reads
_wasBusyoffinstance.state, callstickSustainedto learn the current busy state, and writes the new value back. On a busy→idle transition, cooldown resets todef.cooldownand the tick returns early. While busy, the tick returns without touching cooldown. - Cooldown decrement. Once idle and
cooldownRemaining > 0, decrement bydtand return. Only when the timer reaches zero doesfirePatternrun. - Backstop cooldown reset. After
firePattern,cooldownRemainingis set todef.cooldowndirectly. 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.
tickAllAbilitiesuses an index-basedforloop overhost.abilities(no allocations per tick). Dead hosts short-circuit before any work. - Module-init registration. Bulk catalog lives in
ABILITY_DEFS; boss data files useregisterAbilityfor one-off signature abilities. Both paths land in the sameABILITY_REGISTRYmap.