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_REGISTRYkeyed by string id (registered at module init plus a static catalog table). AbilityInstanceruntime state on host enemies:defIdback-reference,cooldownRemaining, and the opaquestatebag (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
_cullOutsideArenaand_abilityBulletmarker flags. - Param coercion helpers that crash loudly when the wrong type is supplied — abilities reference real data, not optional config.
READS FROM
data/abilitiesfor the staticABILITY_DEFScatalog seeded into the registry.engine/core/typesforAbilityInstance,AbilityDef,EnemyEntity,WorldState,BossArena.engine/core/statefor the sharedgameandshipmodule references — patterns lock the player’s position at telegraph start, aim cones from host facing, and route damage through the ship handle.game.bossArenafor arena geometry — diagonal as bullet max-distance, radius / center for wall geometry and default beam length,cardinalPoints/ringPoints/randomPointfor 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/damageviadamagePlayerfor telegraph-circle detonation, beam sweep DPS application, and death beam insta-kill damage.engine/enemies/spawnerviaGameMaster.spawnEnemyfor the spawn-telegraph pattern, including affix attachment on the freshly spawned enemy.engine/vfx/boss-layersvia the lazyVfxLayerKitfor 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
_cullOutsideArenato 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 ondef.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 withstartDelay(or zero) as the initial cooldown so the first fire respects the configured warm-up.
Pattern notes
- Pattern dispatch is two flat
switchblocks ondef.pattern— one infirePatternfor the telegraph-start fn, one intickSustainedfor the deferred-body fn — both exhaustively checked against thePatternIdunion. Adding a pattern means editing both blocks plus adding the union case. - Telegraph + sustain state is stored in the instance’s opaque
statebag (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
_wasBusyflag on its state bag thattickAbilityreads 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.phaseat'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 untilstate.sustainruns 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
emitTimerand emitsbulletPerEmitrings each interval while rotatingangle. - 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.