# AI Telegraphing & Weight — Ennemis qui se lisent et poids qui se ressent

> **Purpose**: Timings testés, patterns visuels et courbes de mouvement pour des ennemis qui se ressentent justes. Marinade issue de Hollow Knight, Dead Cells, Hades, Cuphead, Celeste et des frame data fighting games (Capcom SF).

---

## 1. Anatomie d'une attaque ennemie

Toute attaque = 3 phases. C'est le ratio entre les trois qui **fait** le game feel.

```
[Anticipation]  →  [Active]  →  [Recovery]
   wind-up         hitbox        vulnerable
```

### Timing de reference (60 FPS)

| Archetype | Anticipation | Active | Recovery (Window of Opportunity) | Total | Jeu reference |
|-----------|-------------|--------|----------------------------------|-------|---------------|
| Jab / attaque rapide | 4-8f (67-133ms) | 2-4f | 6-10f | ~16f (~267ms) | SF6 light attacks, Dead Cells zombies |
| Attaque medium | 10-18f (167-300ms) | 3-6f | 12-20f | ~35f (~583ms) | Hollow Knight Husk Warrior, Hades grunts |
| Heavy / charge | 20-40f (333-667ms) | 4-8f | 20-35f | ~65f (~1.08s) | Hollow Knight Husk Guard, Dead Cells shields |
| Boss wind-up | 30-60f (500ms-1s) | 6-15f | 25-45f | ~90f (~1.5s) | Hollow Knight Hornet (dash=15f anticipation, throw=20f, lasso=20f+30f active) |
| Boss signature | 45-90f (750ms-1.5s) | 10-30f | 30-60f | ~120f (~2s) | Cuphead transformations, Hades area spells |

### La formule GDKeys (la seule qui compte)

```
Anticipation minimum = Reaction joueur + Temps trigger action + Buffer difficulte

Reaction joueur  = ~250ms (15 frames @ 60fps) — moyenne humaine
Trigger action   = ~100ms (6 frames) — temps appui bouton dodge/jump
Buffer difficulte = 0-200ms selon la difficulte voulue
```

**Seuil "unfair"** : en dessous de 200ms d'anticipation totale (12 frames), le joueur moyen ne peut pas reagir — il doit predire. C'est voulu pour les attaques rapides, mais si TOUTES les attaques sont sous 200ms, le joueur se sent triche.

**Seuil "stupid"** : au-dessus de 1.5s d'anticipation, l'ennemi a l'air de poser pour une photo. Exception : les boss signatures et les attaques chargees communiquees par un buildup visuel progressif.

### For Honor comme reference d'equilibre

For Honor a documente ses seuils precisement :
- **Attaque 400ms** : le joueur voit 300ms d'indicateur. Reaction possible = ~233ms. Considere "difficile a reagir" au haut niveau — la plupart des joueurs doivent predire.
- **Attaque 500ms** : le joueur voit 400ms. Reactable par la majorite des joueurs.
- **En dessous de 200ms visible** : pur read, meme les pros ne reagissent pas de facon fiable.

---

## 2. Telegraphe visuel — la couche sensorielle

Le joueur doit SENTIR le danger arriver, pas juste le voir. Multi-canal obligatoire pour les attaques medium+.

### Layers du telegraphe (du subtil au violent)

```
Layer 1: Changement de posture  → l'ennemi se ramasse, leve le bras, recule
Layer 2: Flash couleur          → le sprite devient blanc/rouge pendant 2-3 frames
Layer 3: Particules             → buildup de particules autour de l'arme/du corps
Layer 4: Son                    → "woosh" aspirant, grognement, charge sound
Layer 5: Indicateur sol         → zone danger au sol (Hades red circles)
Layer 6: Screen effect          → screen shake micro, flash bord d'ecran
```

### Regles de combinaison

| Type d'attaque | Layers minimum | Pourquoi |
|----------------|---------------|----------|
| Jab rapide | 1 seul (posture) | Si tu telegraphes trop une attaque rapide, elle devient triviale |
| Medium | 2 (posture + flash OU son) | Le joueur a besoin de deux canaux pour reagir sous pression |
| Heavy / charge | 3-4 (posture + particules + son + optionnel sol) | Le temps est long, il faut le remplir visuellement |
| Boss signature | 4-6 (tout) | Le spectacle EST le telegraphe — Cuphead, Psychonauts voice-over |

### Flash blanc : le telegraphe universel

Le flash blanc avant attaque est LE pattern le plus efficace en 2D. Hollow Knight, Dead Cells, Celeste — tout le monde l'utilise.

**Timing du flash** :
- Flash 2-3 frames **avant** le debut de l'anticipation → "attention, quelque chose va se passer"
- OU flash **pendant** les premiers frames de l'anticipation → confirme le type d'attaque

**Implementation Godot** :
```gdscript
# Pattern flash telegraphe — sur le Sprite2D/AnimatedSprite2D
func telegraph_flash(duration: float = 0.1, color: Color = Color.WHITE) -> void:
    material.set_shader_parameter("flash_intensity", 1.0)
    material.set_shader_parameter("flash_color", color)
    var tween := create_tween()
    tween.tween_property(material, "shader_parameter/flash_intensity", 0.0, duration)
```

```gdshader
// Shader flash telegraphe — canvas_item
shader_type canvas_item;

uniform float flash_intensity : hint_range(0.0, 1.0) = 0.0;
uniform vec4 flash_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);

void fragment() {
    vec4 tex = texture(TEXTURE, UV);
    COLOR = mix(tex, flash_color, flash_intensity * tex.a);
}
```

### Code couleur des telegraphes (convention Hades)

Hades a etabli une convention que les joueurs comprennent instinctivement :
- **Rouge** = danger zone, move away (area attacks)
- **Jaune/orange** = attention, incoming (projectiles)
- **Blanc** = flash pre-attaque (universal)
- **Rose/magenta** = parryable / interruptible (Hollow Knight parry)

**Piege** : ne PAS melanger les codes couleur. Si le rouge = danger ET le boss a des attaques jaunes avec des telegraphes rouges, le joueur est perdu. Hades 2 a eu exactement ce feedback negatif avec certains boss qui melangeaient jaune et rouge.

### Squash & Stretch comme telegraphe

Pas juste un outil d'animation — c'est un telegraphe physique.

```
Anticipation : SQUASH (le corps se comprime, stocke l'energie)
             → le joueur lit "il va bondir/frapper"

Active :      STRETCH (le corps s'etend dans la direction de l'attaque)
             → smear frame, vitesse instantanee

Recovery :    SQUASH inverse (atterrissage, rebond)
             → signal "c'est fini, tu peux punir"
```

**Valeurs testees** :
- Squash anticipation : scale Y = 0.8, scale X = 1.15 (exagere pour les ennemis lourds)
- Stretch active : scale Y = 1.3, scale X = 0.75 (ou smear frame sprite)
- Recovery squash : scale Y = 0.85, scale X = 1.1 (plus subtil que l'anticipation)

---

## 3. Weight & Momentum — faire sentir la masse

Un ennemi qui pese 10kg et un boss de 500kg ne doivent PAS bouger pareil. La difference se fait sur 3 axes : acceleration, vitesse max, et deceleration.

### Profils de poids (pour un jeu 2D a 60fps, viewport ~640x360)

| Profil | Max speed (px/s) | Acceleration (px/s2) | Deceleration (px/s2) | Turn delay | Feeling |
|--------|-------------------|----------------------|----------------------|------------|---------|
| **Feather** (bat, insect) | 250-350 | 2500+ (quasi-instant) | 2500+ | 0 frames | Erratique, nerveux, agacant |
| **Light** (zombie, skeleton) | 120-200 | 800-1200 | 1000-1500 | 1-2 frames | Rapide mais lisible |
| **Medium** (knight, warrior) | 80-150 | 400-700 | 500-800 | 3-5 frames | Delibere, menacant |
| **Heavy** (golem, tank) | 40-80 | 150-300 | 200-400 | 8-15 frames | Inevitable, ecrasant |
| **Colossal** (boss, titan) | 20-50 | 50-150 | 100-250 | 15-30 frames | Seismique, telegraphe naturel |

### Turn delay = le weight que le joueur SENT

Le turn delay c'est le nombre de frames entre "l'ennemi decide de changer de direction" et "il commence a bouger dans cette direction". C'est le parametre le plus important pour la sensation de poids.

**Pourquoi ca marche** : Mario slide quand il tourne. Un golem devrait pivoter encore plus lentement. Si un boss de 3 tonnes change de direction instantanement, le joueur ne le percoit pas comme lourd.

```gdscript
# Pattern turn delay pour enemies avec masse
@export var turn_delay_frames: int = 5
@export var max_speed: float = 100.0
@export var acceleration: float = 500.0

var _desired_direction := Vector2.ZERO
var _current_direction := Vector2.ZERO
var _turn_timer: int = 0

func _physics_process(delta: float) -> void:
    var new_direction := (target.global_position - global_position).normalized()

    if new_direction.dot(_desired_direction) < 0.7:  # Changement significatif
        _desired_direction = new_direction
        _turn_timer = turn_delay_frames

    if _turn_timer > 0:
        _turn_timer -= 1
    else:
        _current_direction = _current_direction.lerp(_desired_direction, 0.15)

    velocity = velocity.move_toward(
        _current_direction * max_speed,
        acceleration * delta
    )
    move_and_slide()
```

### Deceleration asymetrique (pattern Super Mario World)

La gravite de Mario n'est pas la meme en montant et en descendant :
- **Montee** : gravity = 34.79 m/s2 (arc gracieux)
- **Descente** : gravity = 67.82 m/s2 (chute rapide et satisfaisante)

Meme principe pour les ennemis — l'acceleration et la deceleration ne sont PAS symetriques :
- **Acceleration** vers le joueur = lente (le joueur voit venir)
- **Deceleration** apres une charge = rapide si hit, lente si miss (pour laisser la window of opportunity)

### Screen shake adapte au poids

Les ennemis lourds justifient du screen shake a l'atterrissage/impact. Mais PAS a chaque pas.

| Evenement | Shake intensity | Shake duration | Profil poids minimum |
|-----------|----------------|----------------|---------------------|
| Pas (walk) | 0 | 0 | Jamais (sauf cinematique) |
| Atterrissage saut | 1-3 px | 0.05-0.1s | Heavy+ |
| Charge impact mur | 3-6 px | 0.1-0.15s | Medium+ |
| Boss slam | 5-10 px | 0.15-0.25s | Heavy+ |
| Boss phase transition | 8-15 px | 0.2-0.4s | Colossal |

---

## 4. Pattern Mixing — IA previsible mais pas repetitive

Le joueur doit pouvoir apprendre les patterns SANS les memoriser par coeur. La cle : weighted randomness, pas de boucle fixe.

### Anti-pattern : la boucle fixe

```
# FAUX — le joueur memorise en 3 cycles, plus aucun challenge
attack_sequence = [SLASH, PROJECTILE, DASH, SLASH, PROJECTILE, DASH...]
```

### Pattern : weighted random avec cooldown

```gdscript
# Pattern selection d'attaque pondere
var _attack_weights := {
    "slash": 3.0,    # Frequente
    "projectile": 2.0,
    "dash": 1.5,
    "aoe": 1.0,      # Rare
}
var _attack_cooldowns := {
    "slash": 0.0,
    "projectile": 0.0,
    "dash": 0.0,
    "aoe": 0.0,
}
var _last_attack: String = ""
var _consecutive_same: int = 0

func pick_next_attack() -> String:
    var available: Dictionary = {}
    var total_weight: float = 0.0

    for attack in _attack_weights:
        if _attack_cooldowns[attack] <= 0.0:
            var weight: float = _attack_weights[attack]
            # Penaliser la repetition
            if attack == _last_attack:
                weight *= 0.3  # 70% moins probable de repeter
            available[attack] = weight
            total_weight += weight

    if available.is_empty():
        return _last_attack  # Fallback si tout est en cooldown

    var roll: float = randf() * total_weight
    var cumulative: float = 0.0
    for attack in available:
        cumulative += available[attack]
        if roll <= cumulative:
            if attack == _last_attack:
                _consecutive_same += 1
                if _consecutive_same >= 2:
                    # Force un changement apres 2 repetitions
                    available.erase(attack)
                    return _pick_from(available)
            else:
                _consecutive_same = 0
            _last_attack = attack
            _attack_cooldowns[attack] = _get_cooldown(attack)
            return attack

    return _last_attack
```

### Cooldowns entre attaques (rythme respiration)

Le joueur a besoin de RESPIRER entre les attaques. Pas de pause = oppression (voulu pour certains boss phases finales). Trop de pause = ennui.

| Contexte | Pause entre attaques | Feeling |
|----------|---------------------|---------|
| Ennemi basique | 1.0-2.0s | Le joueur a le temps de repositionner |
| Elite / mini-boss | 0.5-1.2s | Pression constante mais geerable |
| Boss phase 1 | 0.8-1.5s | Introduction, le joueur apprend |
| Boss phase 2 | 0.4-1.0s | Escalade, le rythme accelere |
| Boss phase finale | 0.2-0.6s | Urgence, climax |

### Distance-based attack selection (pattern Hades)

L'ennemi choisit son attaque en fonction de la distance au joueur, pas au hasard.

```gdscript
func select_attack_by_distance(distance: float) -> String:
    if distance < melee_range:         # < 60px
        return pick_weighted(["slash", "grab", "backstep"])
    elif distance < medium_range:       # 60-180px
        return pick_weighted(["dash_attack", "projectile", "leap"])
    else:                               # > 180px
        return pick_weighted(["projectile", "charge", "summon"])
```

---

## 5. Boss Design — phases, escalade et rythme

### Structure 3 phases (le standard)

Hollow Knight, Cuphead, Dead Cells — la majorite des boss 2D suivent ce schema :

```
Phase 1 (100%-66% HP) — Introduction
├── 2-3 attaques, telegraphes genereux
├── Pauses longues entre attaques (1.0-1.5s)
├── But : enseigner les patterns
└── Recovery windows larges (30-45f)

Phase 2 (66%-33% HP) — Escalade
├── Attaques phase 1 + 1-2 nouvelles
├── Attaques phase 1 accelerees (anticipation -20-30%)
├── Pauses reduites (0.5-1.0s)
├── But : tester la maitrise
└── Recovery windows reduites (20-35f)

Phase 3 (33%-0% HP) — Climax
├── Toutes les attaques + 1 signature
├── Anticipation reduite (-30-50% vs phase 1)
├── Pauses minimales (0.2-0.5s)
├── Combos d'attaques (2-3 enchaines)
├── But : challenge final
└── Recovery windows plus courtes mais punition plus rentable
```

### Transition entre phases

La transition est un moment de RESPIRATION — le joueur vient de dominer une phase, il merite un break.

**Pattern Hollow Knight** : le boss stagger, animation de douleur/transformation, 1-2s d'invincibilite pendant la transition. Le joueur peut se repositionner et healer.

**Pattern Cuphead** : cutscene micro (transformation visuelle), l'ecran change, nouveau background. Reset complet du contexte visuel.

**Pattern Dead Cells** : pas de phase formelle — l'escalade est continue. Les ennemis deviennent plus agressifs en fonction du biome et du Boss Cell level.

### Escalade par la combinaison, pas par le nerf

```
# BON : phase 2 combine des patterns connus
Phase 1 : Slash (seul), Projectile (seul), Dash (seul)
Phase 2 : Slash → Projectile combo, Dash (plus rapide)
Phase 3 : Slash → Projectile → Dash combo, AoE signature

# MAUVAIS : phase 2 ajoute des attaques inconnues
Phase 1 : A, B, C
Phase 2 : D, E, F (le joueur n'a jamais vu ca)
Phase 3 : G, H, I (completement nouveau = frustration)
```

Le joueur devrait voir chaque attaque individuellement AVANT de la subir en combo. L'escalade vient de la combinaison, de la vitesse, et de la reduction des pauses — pas de patterns inconnus.

### Cuphead : telegraphe par le style cartoon

Le style visuel EST le systeme de telegraphe :
- Postures extremes = les personnages prennent des poses exagerees avant d'attaquer (bras tire en arriere, yeux ecarquilles)
- Objets roses = parryable (convention universelle dans le jeu)
- Changement de position = changement de pattern (le boss se deplace a droite → il va attaquer depuis la droite)
- Chaque phase a un LOOK distinct — transformation visuelle qui signale "les regles changent"

---

## 6. Pieges

### Flash blanc invisible sur fond clair

Le flash blanc standard est invisible si la scene a un background clair. Fix : utiliser un flash qui contraste avec l'environment actuel. Zone sombre → flash blanc. Zone claire → flash rouge ou outline noire qui pulse.

### Anticipation identique pour toutes les attaques = illisible

Si slash, projectile et dash commencent tous par la meme animation de "le bras se leve", le joueur ne peut pas distinguer les attaques. **Chaque attaque doit avoir une pose d'anticipation unique** (GDKeys : "unique in shape and pose"). La pose doit hinter l'attaque qui suit — bras en arriere = swing, corps comprime = charge, mains qui brillent = projectile.

### Recovery trop courte = le joueur ne punit jamais

Si le joueur n'a jamais le temps de frapper apres une attaque, il joue defensif 100% du temps. La recovery EST le gameplay offensif du joueur. Minimum 0.25s (15 frames) de vulnerability visible pour que le joueur puisse reagir.

### Toutes les attaques sous 200ms = le joueur predits au lieu de reagir

Acceptable pour 1-2 attaques rapides dans le pool. Si TOUT est sous 200ms, le joueur se sent triche. La majorite des attaques doivent avoir une anticipation reactable (300ms+).

### Stagger qui reset les patterns = boucle infinie

Si chaque hit du joueur stagger l'ennemi et reset son pattern, l'ennemi n'attaque jamais. Fix : super armor sur certaines attaques (l'ennemi absorbe le hit sans interrompre), ou stagger threshold (X hits pour stagger, pas 1).

### Weight sans deceleration = ennemi glisse

Mettre une acceleration lente sans deceleration correspondante → l'ennemi glisse comme sur de la glace. La deceleration doit etre au moins egale a l'acceleration (et souvent plus elevee pour un feeling "ancre").

### Boss qui change de phase mid-attack = hit fantome

Si le boss passe en phase 2 pendant qu'une hitbox est active, le joueur peut se faire toucher par une attaque "invisible" (l'animation de transition a remplace l'animation d'attaque mais la hitbox est encore la). Fix : desactiver TOUTES les hitboxes au frame de la transition de phase.

---

## Stack combine

```
1. Anticipation unique par attaque     → pose distincte, le joueur lit QUOI arrive
2. Flash + son pour confirmer          → multi-canal, le joueur sent QUAND ca arrive
3. Active frames courts et constants   → hitbox previsible, jamais de surprise
4. Recovery = gameplay offensif joueur → minimum 0.25s pour punir
5. Weight via acceleration/turn delay  → l'ennemi PESE, pas juste lent
6. Pattern mixing weighted + cooldown  → imprevisible mais learnable
7. Boss 3 phases : teach → test → climax → escalade par combinaison
8. Pause entre attaques = respiration  → 0.2s (frenetique) a 2.0s (relax)
9. Telegraphe proportionnel au danger  → jab = 1 layer, boss AoE = 4+ layers
```

Voir aussi : `state_machines.md` pour la FSM ennemie, `component_contracts.md` pour les composants de combat.
