Telegraphed Attack Pattern
What this is
A four-phase attack state machine shared by melee-lunge enemies. The enemy approaches normally, freezes and broadcasts a visible commit (windup), executes a locked dash on the windup-time direction (lunge), sits inert and vulnerable (recovery), then waits a cooldown before being able to commit again. Lunge direction is locked at the moment windup ends — the enemy does not re-aim during windup or lunge, which is the player’s dodge window.
The canonical implementation lives in the charger behavior. Other enemies that route through this same state machine: Brute (slow, tanky melee — same behavior, different stats).
The same windup → commit → recovery → cooldown shape recurs across other archetypes (orb dash-sploot, sniper aim-fire-cooldown), but those use their own phase names and timings; this page covers only the shared melee-lunge variant.
The four phases
| Phase | State id | Movement | Contact damage | Visual / forecast | Exit condition |
|---|---|---|---|---|---|
| Approach | _chargerPhase = 0 | Normal seek toward player along straight line | Off (generic contact damage skipped for charger archetype) | None | Distance to player < windup range AND cooldown elapsed |
| Windup | _chargerPhase = 1 | Frozen (velocity zeroed on entry) | Off | charger_bar loading forecast spawned at enemy position, oriented along locked aim angle, duration = full windup | Windup timer reaches windup duration |
| Lunge | _chargerPhase = 2 | Committed dash along locked aim direction at lunge-speed multiplier of base speed | On — single hit per lunge (_lungeHit flag), body overlap triggers it | Damage flash held at maximum | Travelled distance reaches bar length |
| Recovery | _chargerPhase = 3 | Frozen, inert | Off (explicitly cleared so player can punish freely) | Damage flash cleared | Recovery timer reaches recovery duration |
On recovery exit, phase resets to 0 (approach), and the per-enemy cooldown timer is set; the enemy cannot enter windup again until cooldown ticks to zero, even if the player is in range.
Hit resolution during lunge:
- Damage = lunge-damage constant ×
worldKnobs.rarityScale. - Knockback velocity applied to ship in the enemy’s exact movement direction.
- Hit-freeze pause applied via
_hitFreezeTimerfor impact feel. _lungeHitflag set on first connect, preventing repeated damage during the same lunge.
Per-enemy timings
All numerics below are the shared charger-behavior constants from behaviors.ts. Brute uses identical timings because it routes through the same charger behavior; only the base stats from its archetype data differ.
| Constant | Value | Unit |
|---|---|---|
| Windup trigger range | 250 | px |
| Windup duration | 3.2 | s |
| Lunge speed multiplier | 6.3 | × base speed |
| Lunge / bar length | 300 | px |
| Recovery duration | 1.5 | s |
| Cooldown between commits | 10.0 | s |
| Knockback velocity on hit | 220 | px/s |
| Lunge damage (pre-rarity scale) | 50 | flat |
| Hit-freeze on impact | 0.12 | s wall-time |
Per-enemy base stats that feed into the shared timings:
| Enemy | Behavior | Base speed | Lunge speed (× 6.3) | HP | Radius | Melee | XP |
|---|---|---|---|---|---|---|---|
| Charger | charger | 110 | 693 | 44 | 10 | yes | 12 |
| Brute | charger | 60 | 378 | 100 | 14 | yes | 25 |
Visual / audio telegraphs
| Cue | When | Source |
|---|---|---|
charger_bar forecast | Spawned at windup entry, fills along the locked aim direction for the full windup duration | Forecast object pushed in combat() on windup entry |
| Locked facing | Enemy angle snaps to the aim angle on windup entry and stays locked through windup, lunge, and recovery — does not re-aim mid-attack | Phase-based angle assignment |
| Damage flash | Held at maximum (_dmgFlash = 1, _dmgActive = true) during the lunge phase only | Lunge-phase block in behavior tick |
| Recovery inertness | Damage flash explicitly cleared on recovery entry to signal the punish window | Recovery-phase block in behavior tick |
| Forecast cleanup on death | All charger_bar forecasts owned by this enemy removed on death so the targeting bar does not linger after a mid-windup or mid-lunge kill | onDeath handler |
| Kill SFX | enemy_kill_charger plays a thud + crackle layered impact on death | micro-sfx.ts enemy_kill_charger sample |