# Élites, boss à phases et mouvements chorégraphiés

**Passation d'analyse et plan d'implémentation — 21 septembre 2026.**
À implémenter par le prochain agent dans `big-egg-3d/`. Aucun moteur de phases,
changement de gameplay ou réécriture du maître n'est livré avec ce document.
Les exemples annexés sont des spécifications, pas des données compatibles avec
le schéma actuel.

## 1. Recommandation et périmètre

**Précision de l'auteur, 22/09/2026 :** le boss à droite, regard à gauche,
parcourant l'écran verticalement en tirant un laser est un **exemple d'utilisation
possible**, pas une commande de création de boss ou d'attaque. Les exemples
ci-dessous guident les capacités génériques et leurs tests. Ne pas créer de
contenu spécifique publié sur cette seule base. L'état d'implémentation réel
est consigné dans [current-task.md](current-task.md).

Conserver l'ennemi existant comme acteur unique. Lui ajouter un **orchestrateur
de phases par instance**, qui sélectionne un profil de combat, puis laisse les
moteurs actuels de perception, d'attaque, de collision, d'effets, de sons et de
présentation exécuter ce profil. Une phase n'est ni une nouvelle apparition ni
un autre monstre. Les PV, l'identité et le combat continuent.

L'édition suit la hiérarchie demandée : **monstre → niveau → phases**, avec une
sous-navigation « Paramètres communs / Phases / Orchestration ». Chaque niveau
conserve sa propre liste de phases et sa propre frise. Le niveau 2 peut donc
ajouter une phase ultime sans modifier le niveau 1.

Retenir une frise à **lignes de priorité exclusives** : parmi les lignes admissibles
qui proposent un segment à l'instant considéré, la plus haute gagne. Une ligne
de repli, non supprimable et épinglée en bas, propose la phase par défaut. Les
lignes ne mélangent pas leurs statistiques : une seule phase est appliquée.

Les boucles sont des plages répétables d'une ligne, avec début/fin manipulables
et répétitions finies ou infinies. La fin d'une séquence peut conserver sa dernière
phase. **Elle ne désactive pas les conditions du combat** : une phase à 20 % de PV
doit encore pouvoir se déclencher après la fin de la partie dessinée de la frise.

Enrichir `movement_patterns` progressivement avec un trajet dans l'arène, des
points de passage et une orientation indépendante. Cela couvre immédiatement
« rester à droite, monter/descendre, regarder à gauche et tirer », tout en restant
réutilisable pour des charges préparées, déplacements entre coins et patrouilles.
Ne pas commencer par un langage de script, un Behavior Tree ou une seconde
physique. Les capacités de mouvement existantes restent compatibles.

Le premier lot complet doit couvrir tous les exemples demandés : niveaux distincts,
priorités, seuil de PV, phases répétées, maintien final, bouclier/vulnérabilité,
trajet latéral et laser répété. Les invocations complexes, boucles imbriquées,
navigation automatique dans les labyrinthes et changement de squelette 3D en
plein combat sont des extensions ultérieures, pas des prérequis.

## 2. État réel inspecté et conséquences

Analyse faite sur les **fichiers de travail**, pas uniquement sur le dernier commit.
HEAD observé : `9cdf3b2`. `resume` signalait 115 entrées modifiées/non suivies,
dont des scripts de combat, la timeline, les normaliseurs et le maître. Ne pas
remplacer ces versions par celles d'un ancien plan ou de Git. Schéma observé :
**76**, confirmé dans le JSON, PHP et Godot. Le plan audio rédigé antérieurement
parlait de 70 ; il ne décrit plus la version courante.

| Élément | Constat vérifié | Conséquence pour ce chantier |
| --- | --- | --- |
| Niveaux | `levels` contient des fiches complètes ; `newEnemyLevel()` clone le dernier niveau. Pas d'héritage vivant entre niveaux. | Conserver cette règle. Les phases d'un niveau sont locales à sa fiche. |
| Initialisation | `Enemy.setup()` remet les PV au maximum, copie les attaques, configure l'aggro, recrée les visuels et tire une taille. | **Ne jamais appeler `setup()` pour changer de phase.** Créer une application ciblée du profil effectif. |
| IA | `EnemyAwareness` gère `undetected/detected/aggroed`, mémoire et perception. `configure()` remet son état à zéro. | Une phase n'est pas un état d'aggro. Ne pas appeler `configure()` à chaque transition. |
| Attaques | Choix pondéré, cooldown global et par ligne ; ces dernières sont repérées par **indice** dans `attacks`. | Donner des IDs stables aux attaques pour éviter d'attribuer un cooldown à une autre ligne après changement de liste. |
| Frise d'attaque | `AttackTimeline` produit un plan ; l'ennemi applique zones, coups, déplacements, armure et i-frames. | La conserver pour chaque geste. La frise des phases est au-dessus, avec une autre échelle temporelle. |
| Mouvement | 11 définitions, 5 types : `linear`, `patrol`, `hop`, `static`, `float`. La patrouille actuelle est aléatoire. | Aucun trajet d'arène, rail vertical ou passage par points déterministes trouvé. |
| Regard | `_steer()` et `_physics_process()` orientent souvent le regard selon la vitesse. | « Monter en regardant à gauche » nécessite une politique explicite de regard. |
| Visée | `AttackTargeting.acquire(..., direction, ...)` utilise l'argument `direction` pour `forward`. `_begin_attack()` lui passe actuellement une direction dérivée de la cible. | Cocher `forward` ne suffit pas pour le laser fixe à gauche : adapter l'appelant et tester ce cas. |
| Protection | `attack_invulnerable`/`attack_armored` sont réécrits par la timeline et remis à zéro en fin/interruption. | Ajouter une protection de phase séparée et une résolution commune des protections effectives. |
| Dégâts périodiques | `EffectHolder` appelle aussi `take_damage(amount)` et `take_damage(-amount)` pour les soins. | Le bouclier doit couvrir tous les dégâts et distinguer les soins ; pas seulement les collisions du joueur. |
| Audio | Système implémenté : contrat commun `data/audio_contract.json`, sons par entité et ligne d'attaque. | Le prolonger ; ne pas recréer un directeur ou une liste divergente d'événements. |
| Salle | `Dungeon.arena` est le rectangle jouable ; `world_rect()` inclut du padding. Il n'y a plus un type de scène de boss séparé. | Ancrer les trajets sur l'arène, pas sur la fenêtre ni le padding. Une phase ne déclenche pas la victoire de salle. |
| Mort | `defeated` alimente les compteurs et les vagues ; `die()` produit le butin. | Aucune émission de mort/butin entre phases ; garantir une seule finalisation de mort. |
| Bac à sable | `sandbox_console.gd` peut réappliquer le maître via `setup()` aux monstres présents. | Donner une politique explicite de redémarrage du combat lors du rechargement ; ne pas appeler cela une transition de phase. |

Références locales : [niveaux](enemy-levels.md), [aggro](enemy-awareness.md),
[ciblage](attack-targeting.md), [audio actuel](systeme-audio.md).
Le document historique `../docs/architecture-donjon.html` contient des décisions
anciennes ; les vagues et l'arène du code actuel font foi. La directive actuelle
reste : salle de boss sans défilement, simulation 2D authoritative, rendu 3D
cosmétique, un pixel logique pour une unité du pont.

## 3. Ce qui reste commun et ce qui change par phase

Les paramètres « communs » ci-dessous sont communs aux phases **du niveau choisi**,
pas automatiquement à tous les niveaux de la famille.

| Paramètre | Choix recommandé | Motif / règle |
| --- | --- | --- |
| Identité, nom, biome, niveau, conditions/poids d'apparition, quantités | Commun, interdit dans les overrides de phase | Une phase ne change pas la famille, les compteurs ou l'éligibilité de l'apparition. |
| PV maximum et réserve de PV courante | Une réserve commune au combat | Préserve une barre lisible, les seuils et le résultat des coups. Pas de soin implicite. |
| Butin, `on_spawn`, `on_kill`, attribution de victoire | Commun et déclenché une seule fois | Empêche double récompense, vague terminée trop tôt et fausse apparition. |
| Taille tirée, forme physique, collision, hurtbox | Commun pour le premier lot | Évite un corps qui grossit dans un mur ou une zone de frappe qui ne correspond plus au modèle. Les transformations physiques demanderaient un chantier propre. |
| Modèle 3D/squelette | Commun | Réutiliser le mapping d'animations du gestionnaire. Pas de reconstruction de l'avatar à chaque cycle. |
| Vitesse, réaction de combat, mouvement d'aggro, besoin de voir pour attaquer | Surcharge possible | Définit poursuite, recul, rail de tir, repos ou charge sans changer d'acteur. |
| Détection et politique d'aggro | Commun en V1 | Ne pas réinitialiser mémoire/cible à chaque segment ; l'engagement d'un boss est géré séparément. |
| Liste d'attaques, poids, dégâts, cooldowns, animations et sons par attaque | Héritage ou liste propre à la phase | Le geste reste un pattern réutilisable ; la phase décide des gestes disponibles. |
| Contact du corps : activation, dégâts, cône, allonge, recul et cadence | Surcharge possible, groupée | Une recharge peut être sûre à approcher ; une carapace peut repousser au contact. |
| Invulnérabilité, multiplicateur de dégâts reçus, super-armure, résistance au recul | Surcharge possible | Distinguer bouclier, résistance, faiblesse et impossibilité d'interrompre. Ce ne sont pas des synonymes. |
| Teinte, VFX de phase, clip de transition, sons d'entrée/sortie, overrides des sons d'état | Surcharge de présentation | Faire comprendre immédiatement la règle active sans piloter le combat depuis l'animation. |
| Effets négatifs déjà appliqués, cooldowns restants, PV, instance/cible | État conservé | Un changement de phase n'est pas une purification ni un respawn. Toute exception future doit être déclarée. |
| Musique du combat | Profil de rencontre commun au départ | Les stingers de phase suffisent au premier lot. Les changements de musique doivent ensuite arbitrer avec les autres boss et la salle, pas se battre par appels directs. |

Représenter les phases comme des **surcharges partielles** d'une base immuable.
Absence d'un champ = hériter. Un champ présent à `false`, `0` ou `[]` est un choix,
pas une absence. En particulier, `attacks: []` signifie aucune attaque spéciale.
Les dictionnaires typés se résolvent champ par champ ; les listes d'attaques sont
remplacées entièrement, jamais fusionnées implicitement par position.
Exception importante : une configuration sonore se remplace au niveau de son
événement entier, conformément au contrat audio ; ne pas fusionner récursivement
un cue `silent` ou `inherit` avec les variantes `custom` du parent.

Ne pas copier tous les champs de la base dans chaque phase à la publication :
cela transformerait l'héritage en copies qui divergent. L'éditeur affiche le
résultat hérité et propose « Personnaliser » / « Rétablir l'héritage ». Une phase
ne peut pas hériter d'une autre phase : pas de chaîne ou de cycle d'héritage.

### Choix concernant les listes d'attaques

Pour le premier lot, conserver `attacks` comme liste de référence du niveau et
autoriser `phase.overrides.attacks` comme liste de remplacement. Réutiliser les
cartes d'attaque actuelles, y compris animation et sons. Un bouton « Personnaliser
à partir des attaques communes » effectue une copie explicite et l'UI indique
qu'elle est désormais détachée. Les patterns géométriques restent partagés.

Cela introduit moins de changements qu'un catalogue supplémentaire de « banques
d'attaques » et couvre le besoin. Si de nombreuses phases réutilisent ensuite
les mêmes lignes, une banque référencée pourra être extraite sur preuve de cette
répétition. Ne pas imposer dès maintenant trois niveaux de références à l'auteur.

Ajouter un `id` stable à chaque ligne d'attaque ; ne pas utiliser `pattern` comme
ID, puisque deux lignes peuvent partager un pattern. Une copie de la liste
commune dans une phase conserve les IDs des gestes qu'elle spécialise. Une
nouvelle ligne ou une duplication **dans la même liste** reçoit un nouvel ID.
L'espace d'ID est local à la variante de niveau, et l'ID reste attaché à une même
intention d'attaque ; changer complètement de geste peut créer un nouvel ID.
Le cooldown est conservé par ID de geste, même si l'attaque disparaît temporairement
de la phase active ; deux overrides d'un même geste partagent cette recharge.
Un geste indépendant peut employer un nouvel ID. La recharge du geste en cours
est calculée depuis son snapshot, pas depuis la phase suivante.

## 4. Contrat de l'orchestrateur

### 4.1 Vocabulaire et horloges

- **Phase** : profil nommé de paramètres. Pas une durée en soi.
- **Segment** : présence d'une phase dans l'intervalle d'une ligne.
- **Ligne** : condition d'activation, horloge, segments et politique de fin.
- **Boucle** : répétition d'une plage de segments d'une ligne.
- **Phase demandée** : résultat actuel de la priorité.
- **Phase active** : profil réellement appliqué, éventuellement encore différent
  pendant la fin propre d'une attaque.

| Horloge | Avancement / usage |
| --- | --- |
| `combat_elapsed` | Temps simulé depuis le déclenchement du combat. Continue pendant attaques, vulnérabilité et étourdissement ; s'arrête en pause et lorsque la rencontre est suspendue. |
| `row_elapsed` | Temps de séquence d'une ligne déclenchée. Par défaut, suspendu lorsqu'une ligne supérieure la masque. Sert aux séquences relatives et aux boucles. |
| `phase_elapsed` | Temps réellement passé dans le passage de phase actif. Sert à garantir une exposition minimale et au diagnostic ; ne remet pas la réserve de PV à zéro. |
| Curseur d'attaque | `AttackTimeline` actuel, propre à chaque geste. Aucun remplacement par l'horloge de rencontre. |

Une ligne a `clock: "sequence"` par défaut : zéro lorsqu'elle s'active. C'est
le bon choix pour « à 50 % de PV, jouer cette séquence depuis le début ».
Le mode avancé `clock: "combat"` emploie directement `combat_elapsed` :
« entre la 30e et la 40e seconde, si cette condition est vraie ». Cette horloge
absolue ne s'arrête pas sous une ligne supérieure.

L'éditeur doit écrire clairement **« secondes depuis activation »** ou
**« secondes depuis début du combat »** sur chaque ligne. Une date d'activation
par PV ne peut pas être connue avant le combat : ne pas afficher un curseur global
unique qui ferait croire que ces deux axes sont toujours synchronisés. L'aperçu
avec un scénario de PV les replace sur un temps de combat commun.

Le passage courant ne persiste pas dans `GameState` ni dans un fait global. Chaque
boss possède ses propres curseurs, verrous, événements consommés et jetons.

### 4.2 Début, engagement et fin du combat

Proposer `start: "first_aggro"` par défaut ; options explicites `spawn` et signal
d'engagement de rencontre pour les mises en scène ultérieures. Au premier aggro,
capturer une seule fois l'origine temporelle et les PV maximum de référence.
Une nouvelle détection n'est pas un nouveau départ.
À t=0, résoudre les lignes avant d'appliquer la toute première phase : ne pas
jouer brièvement le repli puis attendre sa durée minimale avant la ligne gagnante.

Pour un boss, `engagement: "until_encounter_end"` maintient le combat engagé
jusqu'à sa mort, la mort/disparition du joueur ou la sortie de salle. Il ne simule
pas une perception permanente : `EnemyAwareness` continue à fournir visibilité
et dernière position connue. Le droit de combattre devient une fonction commune
qui prend en compte cet engagement ; ne pas seulement remplacer le test dans
`_physics_process()` en oubliant celui de `_try_begin_weighted_attack()`.

Ainsi, regarder à gauche n'arrête pas tout le boss si le joueur passe derrière.
Une attaque exigeant la vue respecte encore ce choix ; le laser fixe sera
explicitement autorisé sans visibilité. Ne pas détourner un délai de mémoire
de 120 secondes pour imiter un verrou d'engagement.

Pour un élite, proposer `engagement: "perception"` avec suspension en cas de perte
durable d'engagement. Reprise au même état, sans reset gratuit des PV/cooldowns.
Une option de reset complet n'est pas nécessaire en V1. Sortie de scène :
libération de toute la rencontre et de ses boucles sonores/visuelles.

La mort est prioritaire sur les phases. Si un coup fait passer de 60 % à 0 %, le
boss meurt ; ne pas inventer plusieurs réserves de vie. Des seuils de vie qui
arrêtent un coup létal pourront être une option future explicite, avec une règle
sur les dégâts excédentaires. Ils ne sont pas implicites dans « multiphase ».

### 4.3 Conditions et activation mémorisée

Réutiliser la forme existante `{path, operator, value}` et les opérateurs actuels
`<=`, `>=`, `<`, `>`, `==`, `!=`, `truthy`, `falsy`. Une liste signifie **ET**,
comme aujourd'hui. Pour un OU, plusieurs lignes peuvent proposer la même phase ;
pas besoin d'étendre tout le système de conditions du jeu dans ce chantier.

Ajouter un contexte **local et en lecture seule**, par exemple :

| Adresse proposée | Sens |
| --- | --- |
| `encounter.health_ratio` | PV restants / PV maximum capturés à l'engagement, borné [0,1]. L'UI affiche un pourcentage. |
| `encounter.combat_elapsed` | Temps simulé de combat. |
| `encounter.target_distance` | Distance logique au joueur valide ; cible absente = prédicat invalide/faux, pas zéro. |

Les deux premières suffisent aux exemples obligatoires ; la distance est utile
aux élites. Les conditions globales existantes restent possibles via l'adaptateur
standard. `GameState.conditions_met()` valide actuellement ses adresses via
`AddressCatalog` et lit `Registry` : il **ne sait pas déjà** lire ces adresses
locales. Ajouter un résolveur contextuel sans les transformer en variables
partagées ni en adresses modifiables par les effets.

Chaque ligne propose `activation: "latched"` (défaut) ou `"while_true"` :

- `latched` : la première validation arme la ligne jusqu'à la fin du combat,
  même si un soin remonte ensuite les PV. Recommandé pour 50 %/20 % et l'enrage.
- `while_true` : la ligne cesse de proposer des phases si sa condition devient
  fausse. À sa réactivation, sa séquence repart à zéro. Pour une distance mobile,
  prévoir un délai de confirmation/réarmement configurable et borné afin d'éviter
  le clignotement au seuil. Ce mode avancé n'est pas nécessaire au boss exemple.

Champs proposés pour cette stabilisation : `activation_delay_seconds` (défaut 0)
et `rearm_delay_seconds` (défaut 0,25), chacun dans [0,60]. Le premier exige une
condition vraie continûment avant d'armer ; le second retarde un nouveau départ
après désarmement. Une ligne `latched` n'a pas de réarmement dans le même combat.

Évaluer les conditions sur les dégâts **acceptés**, puis au tick de simulation.
Une attaque bloquée ne franchit pas un seuil. Plusieurs conditions validées par
le même coup sont départagées par l'ordre des lignes ; aucune traversée artificielle
de toutes les phases intermédiaires. Les chemins dépendant de la phase courante
elle-même sont refusés en V1 pour éviter les alternances auto-déclenchées.

### 4.4 Priorités, trous et préemption

L'ordre du tableau `tracks` est l'unique priorité : index 0 au-dessus. Aucun
second entier `priority` susceptible de contredire l'ordre visuel. Les lignes
désactivées ne participent pas. Les conditions de toutes les lignes restent
observées, y compris celles masquées par une supérieure.

Une ligne admissible propose au maximum un segment. Un trou entre deux segments
est transparent : examiner la ligne suivante. Même règle avant le premier segment
et après la fin si `after_end: "release"`. Les overlaps sur **une même ligne**
sont refusés ; entre lignes, ils sont normaux et résolus par priorité.

Pour une ligne relative, `on_preempted: "pause"` par défaut conserve son curseur
sous une ligne supérieure ; `"continue"` est une option avancée explicite.
**Attention au trou de séquence :** une ligne sans segment courant doit continuer
à avancer tant qu'aucune ligne au-dessus ne la masque. Sinon son curseur reste
figé dans le trou pendant que le repli joue, et elle n'atteint jamais son segment
suivant. « Seule la ligne gagnante avance » serait donc une implémentation erronée.

Une ligne masquée puis reprise conserve son curseur. L'attaque qui a été annulée
ne reprend pas au milieu de sa frappe ; la phase peut relancer un geste selon ses
cooldowns. Le passage repris garde ses compteurs d'entrée et son temps d'exposition
déjà joué, afin de ne pas répéter un effet d'entrée à chaque préemption.

### 4.5 Intervalles, boucles et maintien final

Intervalles **[start, end)**, en secondes, `end > start`. À une frontière exacte,
le segment suivant gagne, jamais les deux. Pour la durée de passage minimale,
utiliser au moins un tick physique ; refuser les segments nuls et les boucles nulles.

Une seule plage de boucle par ligne en V1 : `{start, end, iterations}`. `iterations`
est le nombre **total** de passages, entier >= 1, ou `null` pour l'infini affiché ∞.
Les limites de boucle coïncident avec des frontières de segments, sans les couper.
Pas de boucle imbriquée. Les segments après une boucle infinie sont inatteignables
et doivent être signalés/refusés, pas laissés comme une fausse suite visible.

Pour `loop = [a,b)`, `L = b-a`, et un curseur absolu de ligne `u` :

```text
avant a          : playhead = u
dans les N tours : playhead = a + ((u-a) modulo L)
après N tours    : playhead = u - (N-1)*L
```

Conserver aussi l'indice d'itération pour identifier chaque occurrence ; ne pas
reboucler en remettant tout l'état du monstre à zéro. Deux phases « Bouclier 6 s »
et « Recharge 3 s » dans une boucle infinie de 9 s remplacent toutes les copies.
La priorité d'une ligne HP supérieure est toujours vérifiée pendant cette boucle.
Pour la quitter : nombre fini de tours ou ligne supérieure conditionnelle. Une
instruction arbitraire `goto` n'est pas nécessaire.

`after_end: "hold_last"` est le défaut recommandé : garder la dernière phase
**effectivement jouée par cette ligne**, sans rejouer son entrée. Les mouvements,
attaques et cooldowns de cette phase continuent normalement. Il ne s'agit pas
de figer le corps, une animation ou une attaque en cours pour toujours.

Si une ligne absolue était entièrement masquée et n'a jamais joué, elle ne peut
pas inventer un dernier état à maintenir. Elle reste transparente après sa fin.
Une boucle infinie n'atteint pas sa fin. L'« horizon » dessiné dans l'éditeur
est un réglage de zoom/aperçu, pas une durée limite du combat.

### 4.5 bis Sortie d'un segment sur condition (ligne 21, 12/10/2026)

Un segment peut porter des `exit_conditions` : la **première** qui devient vraie
le termine avant sa fin dessinée (OU, contrairement aux listes qui arment une
ligne). La fin dessinée reste un **plafond** : un rail bloqué ne coince jamais
le boss. Le curseur de la ligne saute alors à la fin du segment ; la suite de la
ligne avance d'autant, boucles comprises. Une ligne à l'horloge du combat suit
le combat et ne sort pas plus tôt ; un segment tenu après la fin de la ligne
(`hold_last`) n'a plus rien à quitter.

Adresses `segment_only` du contrat (elles comptent depuis le début de
l'occurrence de segment en cours et n'arment pas une ligne) :
`encounter.segment_elapsed`, `encounter.movement_arrived` (rail « une fois »
fini ; absente sans rail), `encounter.segment_hero_hits`,
`encounter.segment_hits_taken`, `encounter.segment_damage_ratio`,
`encounter.segment_attacks_done`. S'y ajoutent les trois adresses d'une ligne
(PV, temps de combat, distance). La créature tient ses compteurs
(`Enemy.segment_counts`) et les remet à zéro quand le directeur change
d'occurrence. Éditeur : panneau d'un bloc de la frise, « Conditions de sortie ».
Contrôles : `encounter_check` (moteur et créature), `encounter_contract_test.php`.

### 4.6 Transitions : priorité demandée et application sûre

La sélection et la transition sont deux opérations. Le changement devient
atomique au point de simulation prévu : retirer les contributions de l'ancienne
phase, appliquer le nouveau profil, annoncer le nouvel état et autoriser ses
attaques. Pas de demi-frame avec les dégâts de l'une et le bouclier de l'autre.

Politique d'entrée d'une ligne :

- `finish_attack` par défaut : ne plus démarrer de geste, terminer celui engagé,
  puis changer. `max_wait_seconds` borné (exemple 0,75 s) empêche d'attendre
  indéfiniment une attaque continue ; à l'expiration, annuler proprement.
- `interrupt` : annuler immédiatement le geste et ses zones actives avant de
  changer. Utile à un bouclier urgent, avec signal visuel clair de transition.

Le maintien minimal `min_active_seconds` d'une phase garantit, si choisi, une
vraie fenêtre jouable avant une transition ordinaire. Le temps restant sous
préemption ne compte pas comme exposition. Une ligne explicitement marquée
`urgent` peut dépasser cette protection ; la mort la dépasse toujours. Par défaut,
une recharge de 3 s peut demander 3 s d'exposition pour ne pas disparaître au
premier coup qui franchit 50 %. L'aperçu montre cette attente, il ne promet pas
un changement instantané contraire aux règles.

Lorsqu'une transition attend, arrêter le curseur de séquence à sa frontière
pour ne pas consommer la prochaine phase avant qu'elle soit visible. Le temps
de combat et l'attaque en cours continuent. Une ligne relative nouvellement
demandée reste à son premier segment jusqu'à son entrée. Une horloge `combat`
reste absolue : revalider son segment au moment d'appliquer, sans jouer un segment
déjà expiré. Une condition de priorité supérieure peut remplacer la demande
en attente. Cette attente peut étirer une séquence relative ; c'est affiché dans
le journal. Pour une chorégraphie à durées strictes, choisir `interrupt` et des
gestes qui tiennent dans la fenêtre.

Précision de durée : `max_wait_seconds` borne l'attente du geste **après** satisfaction
du minimum d'exposition ; il ne raccourcit pas ce minimum sauf ligne urgente.
Si un seuil demande une nouvelle ligne au milieu d'une fenêtre vulnérable,
l'ancienne ligne peut avancer jusqu'à la frontière de son segment pendant cette
exposition, puis s'y arrêter. La nouvelle séquence ne consomme pas encore son
premier segment. Cela donne bien recharge [6,9), seuil à 7 s, nouvelle phase à 9 s
dans le scénario A, sans décaler indéfiniment la fin de la recharge.

Prérequis de sécurité : snapshot du geste en cours (définition, sons, dégâts,
animation, ID), jeton d'exécution invalidé à l'annulation, zones attachées au geste
retirées. Les projectiles déjà tirés continuent avec leur snapshot par défaut.
Ne jamais supprimer les projectiles de tous les acteurs. Une purge optionnelle
ne concerne que l'émetteur/passage propriétaire, et doit être un choix explicite.

Une entrée de segment est identifiée par `(track_id, segment_id, iteration)` ;
sa transition possède en plus une génération d'exécution. L'entrée sonne une
fois, pas à chaque tick. Un passage à une autre occurrence de la même phase est
une nouvelle occurrence, mais ne soigne pas et ne remet pas les cooldowns à zéro.

## 5. Modèle de données à introduire

Ajouter `encounter` à chaque fiche de niveau, absent/désactivé par défaut. Exemple
de structure, noms proposés à stabiliser dans le contrat avant le code :

```json
{
  "enabled": true,
  "kind": "boss",
  "start": "first_aggro",
  "engagement": "until_encounter_end",
  "default_phase": "recharge",
  "phases": [
    {"id": "shield", "display_name": "Carapace", "min_active_seconds": 0,
     "overrides": {"defense": {"invulnerable": true, "super_armor": true}}},
    {"id": "recharge", "display_name": "Recharge", "min_active_seconds": 3,
     "overrides": {"defense": {"invulnerable": false, "damage_taken_multiplier": 1.5},
                   "attacks": [], "direct_hit": false, "aggro_movement": "static"}},
    {"id": "laser", "display_name": "Balayage latéral", "min_active_seconds": 0,
     "overrides": {"aggro_movement": "right_rail", "attack_needs_sight": false}}
  ],
  "tracks": [
    {"id": "half_health", "label": "Sous 50 %", "enabled": true,
     "conditions": [{"path": "encounter.health_ratio", "operator": "<=", "value": 0.5}],
     "activation": "latched", "clock": "sequence", "on_preempted": "pause",
     "transition": {"mode": "finish_attack", "max_wait_seconds": 0.75, "urgent": false},
     "segments": [{"id": "beam_hold", "phase": "laser", "start": 0, "end": 8}],
     "after_end": "hold_last"},
    {"id": "shell_cycle", "label": "Cycle de carapace", "enabled": true,
     "conditions": [], "activation": "latched", "clock": "sequence", "on_preempted": "pause",
     "transition": {"mode": "interrupt", "max_wait_seconds": 0, "urgent": false},
     "segments": [{"id": "shell", "phase": "shield", "start": 0, "end": 6},
                  {"id": "opening", "phase": "recharge", "start": 6, "end": 9}],
     "loop": {"start": 0, "end": 9, "iterations": null}, "after_end": "hold_last"}
  ]
}
```

La ligne par défaut est dessinée depuis `default_phase`, pas dupliquée dans
`tracks`. Le JSON ci-dessus illustre l'orchestrateur ; le vrai laser nécessite
aussi la liste d'attaques spécifique décrite plus loin. Ne pas publier tel quel.

IDs de phase, ligne et segment stables ; pas de référence par index d'onglet.
Réordonner ne renomme rien. Supprimer une phase référencée exige un remplacement
explicite ou le retrait de ses segments. Cloner un niveau conserve ses IDs locaux,
car son espace de noms est distinct ; cloner une phase dans le même niveau génère
un nouvel ID de phase. Une définition ne contient jamais l'état de lecture.

Créer un contrat commun, par exemple `data/encounter_contract.json`, pour les
énumérations, champs surchargeables, adresses locales et bornes. Reprendre le
principe déjà adopté pour `audio_contract.json` : PHP, JS et Godot consomment la
même description. Les algorithmes restent testés sur des fixtures partagées ;
le fichier de contrat ne doit pas devenir un langage de script.

## 6. Intégration runtime et invariants à protéger

Composants proposés, petits et testables :

- `encounter_profile.gd` : compilation/résolution des overrides autorisés.
- `encounter_timeline.gd` : conditions locales, curseurs, boucles, priorité,
  candidats et journal déterministe ; sans accès à l'audio ni à la physique.
- `encounter_controller.gd` : cycle de vie par Enemy, attente/commit des transitions.
- `movement_program.gd` : trajet et demande de mouvement/orientation ; décrit
  en section 7. Pas un second appel à `move_and_slide()`.

Il n'est pas nécessaire d'ajouter un autoload global pour chaque composant.
Utiliser des ressources/objets préchargés conformément aux pratiques locales.

Ordre recommandé dans un tick d'ennemi : traiter mort/validité → perception et
engagement → temps/conditions → demande de phase et commit possible → protections
effectives → choix/avance d'attaque → intention de mouvement et de regard → effets
et recul → **un** déplacement physique → synchronisation des présentations.
Les callbacks de dégâts marquent la rencontre à réévaluer ; ne pas déclencher
une cascade récursive d'entrées de phase au milieu d'un parcours de collisions.

Le temps reçu est celui de la simulation. Ne pas utiliser l'heure du système,
une rafale de `SceneTreeTimer` ou des callbacks d'animation pour gouverner les
phases. Pause et tests à delta contrôlé doivent avoir le même sens.

### Résolution des protections et des valeurs

Garder `level_definition` immuable et `effective_definition` résolue. Exposer la
définition effective aux usages qui en ont besoin, notamment sons/visuels, tout
en conservant la base pour identité, PV et butin. `get_enemy_definition()` est
déjà utilisé par `Registry.run_hook()` : vérifier chaque usage avant de changer
sa signification. Comme les overrides excluent butin et hooks structurants,
ceux-ci doivent rester inchangés dans le profil effectif.

Ne pas ajouter les paramètres d'un boss au pool global de modificateurs du joueur.
Appliquer ses valeurs effectives à ses propres champs, puis les effets de combat
de l'instance selon la logique existante ; pas de multiplication cumulative
« ×1,2 à chaque entrée ». À chaque transition, repartir de la base + overrides.

Introduire des requêtes uniques, par exemple `is_damage_blocked()` et
`is_interrupt_protected()`. Résoudre l'invulnérabilité de phase **OU** celle
de l'attaque ; résoudre séparément super-armure et résistance au recul.
Une phase vulnérable ne force pas à faux les i-frames annoncées par une attaque.
Inversement, finir une attaque ne doit pas enlever le bouclier de la phase.

Le pipeline de dégâts positifs vérifie les protections, applique le multiplicateur
de dégâts reçus, soustrait les PV, puis observe les seuils et traite la mort.
Les soins suivent une branche distincte et sont bornés au maximum commun ; une
invulnérabilité aux dégâts n'est pas une interdiction implicite de soin. Tester
contact, projectile, zone continue et DOT. Le feedback de blocage doit être visible
et distinct d'une blessure. Ne pas déclencher les effets `on_hurt` d'un coup refusé.

La mort doit être idempotente pour **tout** son corps de fonction, pas seulement
son appel audio. Le code observé protège le son par `active`, puis continue vers
compteurs/butin : ajouter la garde de finalisation si plusieurs callbacks peuvent
encore l'atteindre. Une phase n'appelle jamais `die()` ou `_on_enemy_defeated()`.

### Attaques chorégraphiées et cooldowns

Conserver le choix pondéré par défaut. Pour une séquence mémorable, ajouter une
politique optionnelle par phase : `attack_selection.mode = "sequence"` avec une
liste d'IDs et répétition. `weighted` continue à utiliser les poids actuels.
Exemple : tir → tir → balayage → pause, ou une seule attaque laser répétée.
La pause courte reste dans la récupération/cooldown de l'attaque ; une vraie
fenêtre vulnérable est une phase, pas un délai caché de sélection.

Si le prochain geste de séquence est indisponible, politique explicite `wait`
par défaut ou `skip` avancé ; aucune boucle de recherche illimitée. Cadences,
portée, contrôle de foule, visibilité requise et cooldown global continuent à
s'appliquer. Un refus ne consomme pas l'étape. Ne pas démarrer une attaque dont
la durée connue dépasserait une frontière stricte de phase, sauf option explicite
d'interruption. Le passage en phase conserve les recharges ; pas de tir gratuit
chaque fois qu'un cycle revient au même profil.

Une attaque en cours et ses projectiles lisent leur snapshot. Les nouveaux gestes
lisent le profil effectif. Ne pas remplacer la définition des dégâts d'un geste
déjà annoncé pendant une transition différée. Réutiliser la frise existante pour
les avertissements, fenêtres de dommages, i-frames et mouvement propre au geste.

## 7. Moteur de mouvements : extension utile et maîtrisée

### 7.1 Limite actuelle et séparation à introduire

Aujourd'hui, la réaction choisit surtout une direction vers/loin du joueur,
puis le type de mouvement produit une vitesse. `patrol` ressemble à `linear`
avec une direction d'errance aléatoire. `hop` et `float` ajoutent une hauteur
visuelle ; cette hauteur ne retire pas la collision du sol.

Pour une chorégraphie, séparer quatre décisions :

1. **Référence spatiale** : par rapport à quoi le trajet est-il défini ?
2. **Trajectoire** : quel point ou déplacement est souhaité maintenant ?
3. **Locomotion** : quelle vitesse le corps peut-il réellement produire ?
4. **Regard et visée** : où regarde le corps, et où part le geste ?

Le moteur retourne une intention, par exemple vitesse souhaitée, regard demandé,
point visé et état du trajet. `Enemy` arbitre avec attaque, immobilisation et recul,
puis appelle sa physique actuelle une seule fois. Ne pas déplacer directement
`global_position` au point mathématique de la trajectoire à chaque image.

Godot fournit la collision et le glissement via `CharacterBody2D`, alors que
`PathFollow2D` fournit une position le long d'un chemin : la recommandation pour
ce projet est d'utiliser le trajet comme **guide** du corps existant, pas de
remplacer le mouvement physique par le déplacement automatique d'un enfant de
chemin. Références : [CharacterBody2D](https://docs.godotengine.org/en/stable/classes/class_characterbody2d.html),
[PathFollow2D](https://docs.godotengine.org/en/stable/classes/class_pathfollow2d.html).

### 7.2 Références spatiales

| Référence | Usage | Politique |
| --- | --- | --- |
| `arena` | Bord droit, coins, centre, rail, tour de salle | Obligatoire en V1 ; coordonnées normalisées [0,1] dans l'arène logique utilisable. |
| `phase_entry` | Petit déplacement autour du point où commence une phase | Origine capturée une fois ; offsets en pixels. Utile mais secondaire. |
| `spawn` | Gardien attaché à sa position de départ | Position capturée au spawn, pas à chaque phase. |
| Cible suivie | Orbite, maintien à distance, accompagnement du joueur | Extension après le rail ; choix explicite cible capturée/suivie, perte de cible et bornage. |
| Écran/caméra | Effet cinématique dépendant du cadrage | Hors V1 : aspect, zoom et mouvement de caméra ne doivent pas déplacer le boss ou ses collisions. |

Ajouter une petite API d'arène commune au donjon et au bac à sable, par exemple
`get_combat_arena_rect()`. Employer `Dungeon.arena`, en convertissant correctement
ses coordonnées locales en coordonnées monde si son parent est transformé.
Retirer une marge tenant compte du rayon physique de l'acteur et d'un padding
d'auteur. `world_rect()` et la taille du viewport ne sont pas le rectangle voulu.

Pour une salle de boss sans défilement, « droite de l'écran » se traduit donc
par droite de l'arène, stable quel que soit le format de fenêtre. Pour un élite
dans une salle qui défile, ce sont toujours les bords de la salle entière.
Un véritable comportement fixé au cadrage serait une extension distincte.

### 7.3 Types et contrôles recommandés

Étendre la collection **existante** `movement_patterns` avec `type: "path"`.
Les 5 anciens types conservent leur exécution et leurs défauts lorsqu'aucun
nouveau réglage n'est utilisé. Le chemin fournit :

- `space` et des points `{x,y,wait_seconds}` ; 1 point = rejoindre puis tenir.
- `traversal: "once" | "loop" | "ping_pong"` ; 2 points en ping-pong = rail.
- Entrée physique vers le premier point, sans téléportation ; état `approaching`
  puis `running`. Une phase peut attendre `movement_ready` pour lancer ses attaques.
- Vitesse depuis la base du monstre et le multiplicateur du pattern. Le premier
  lot utilise une vitesse en pixels/s, pas simultanément une durée et une vitesse
  contradictoires. Afficher une durée indicative calculée du trajet sans obstacle.
- Rayon d'arrivée, petit ralentissement d'approche, attente au point et stratégie
  explicite en cas de blocage. Ne jamais dépasser le point à cause d'un grand delta.
- `facing.mode: "legacy" | "movement" | "target" | "fixed" | "keep"`, angle fixe
  et vitesse de rotation. Réutiliser le champ existant `turn_speed` pour la vitesse
  angulaire, sans seconde valeur concurrente dans `facing`. `legacy`/absence
  conserve exactement le comportement actuel.

Une orbite, une sinusoïde et un huit sont des enrichissements utiles ensuite.
Commencer par des presets qui génèrent des points éditables : rail droit/gauche,
haut/bas, rectangle intérieur et aller au centre. Ils couvrent beaucoup de boss
avec un seul moteur. Une orbite analytique pourra être ajoutée sans modifier
l'orchestrateur, qui ne connaît que l'ID du mouvement.

Éviter un type spécial codé en dur `boss_right_laser` : la combinaison trajet +
regard + attaque doit produire ce comportement depuis les données.

### 7.4 Physique, interruptions et progression du trajet

Le déplacement de route suit le corps réel : avancer au point suivant à l'arrivée
confirmée, pas parce qu'une position théorique a traversé un mur. La vitesse
d'approche est bornée par la distance restante et `delta`, puis soumise aux mêmes
restrictions de mouvement et collisions qu'aujourd'hui.

Une attaque à `free_movement: true` laisse le trajet avancer. Une attaque qui
possède le mouvement suspend le trajet jusqu'à sa fin. Ne pas sommer une charge
et une vitesse de rail : **un propriétaire du mouvement voulu à la fois**.
Les effets qui empêchent de bouger sont prioritaires ; le recul suit ses règles
de protection/résistance. L'immobilisation suspend le progrès physique du trajet,
mais n'arrête pas à elle seule l'horloge du combat ou un enrage.

Après un recul, rejoindre le point courant à vitesse normale. Après une préemption
de ligne, conserver le curseur du mouvement de l'occurrence suspendue si elle
revient ; si l'acteur est ailleurs, rejoindre physiquement sa cible actuelle.
Une nouvelle occurrence de segment commence un nouveau trajet. Pendant un maintien
final, le trajet continue, il ne recommence pas à chaque tick.

Le premier moteur ne sait pas calculer un détour autour d'un mur. Prévoir un
temps sans progrès et un comportement `hold` ou `skip_waypoint` explicite, avec
diagnostic limité. Jamais de téléportation ou de vitesse croissante infinie pour
« rattraper » la route. Le rail de boss utilise une arène dégagée. Tester le cas
bloqué ; une vulnérabilité ne doit pas attendre éternellement un point inaccessible.
`movement_ready` a un timeout et un repli annoncés, indépendants d'une invulnérabilité.

Une orientation fixe à gauche avec un trajet vertical nécessite le déplacement
omnidirectionnel ou le pas de côté. Le couple « regard fixe perpendiculaire +
corps orienté sans strafe » est contradictoire : erreur d'auteur claire, pas un
monstre immobile inexpliqué. Conserver la contrainte de locomotion des créatures
existantes qui doivent pivoter pour avancer.

### 7.5 Exemple complet : boss à droite qui tire vers la gauche

Créer un pattern `right_rail`, déclaré dans l'éditeur de mouvements :

```json
{
  "type": "path",
  "movement_freedom": "omni",
  "allow_strafe": true,
  "speed_multiplier": 1,
  "detected_speed_multiplier": 1,
  "turn_speed": 360,
  "path": {
    "space": "arena",
    "points": [{"x": 0.88, "y": 0.2, "wait_seconds": 0.2},
               {"x": 0.88, "y": 0.8, "wait_seconds": 0.2}],
    "traversal": "ping_pong",
    "entry": "first_point",
    "arrival_radius": 8,
    "blocked_policy": "hold",
    "blocked_timeout_seconds": 2
  },
  "facing": {"mode": "fixed", "angle_degrees": 180}
}
```

Ce sont des champs proposés, pas un pattern déjà reconnu par le schéma 76.
Les coordonnées s'appliquent au rectangle intérieur après marge. La convention
2D est x vers la droite, y vers le bas : 0° droite, 90° bas, 180° gauche.
Le pont mappe ensuite cette direction vers la 3D sans la redéfinir.

Configuration de la phase `laser` : vitesse adaptée à l'arène, `aggro_movement`
vers `right_rail`, `attack_needs_sight: false`, engagement de boss conservé,
contact désactivé ou limité selon le design, liste d'attaques dédiée au laser.
Attendre l'arrivée sur le rail avant la première rafale, avec timeout de sécurité.
Attendre aussi que le regard soit orienté à gauche (tolérance angulaire explicite)
si une rotation progressive est choisie. Une vitesse de rotation nulle qui empêche
cette acquisition doit être signalée comme configuration incohérente.

Créer/dupliquer un pattern d'attaque dédié **dans le gestionnaire**. Aucun pattern
actuel ne doit être considéré comme un vrai laser uniquement à cause de son nom
(`scan_beam` est actuellement un pattern simple de type swipe).

Recette initiale : rectangle long et étroit, ancrage sur l'acteur, préparation
visible 0,5 s, frappe 0,25 s, récupération 0,35 s, `free_movement: true`,
`target_mode: "forward"`, `attack_facing: "keep"`, `aim_turn_speed: 0` et balayage
angulaire nul. La cadence de dégâts reste celle de la frise, avec répit par cible.
La portée est calculée pour atteindre l'autre côté de l'arène et reste bornée.
La distance de déclenchement doit aussi couvrir l'arène, sinon le boss se tait
quand le joueur s'éloigne. Les trois durées sont des valeurs d'essai à équilibrer.

Points de code indispensables pour obtenir le résultat annoncé :

1. Pour `forward` chez l'ennemi, fournir au ciblage la direction de regard
   effective, capturée **avant** que l'acquisition ne la remplace par la cible.
   Préserver les autres modes et les comportements du joueur ; vérifier les
   tests de ciblage existants. `attack_facing: keep` seul ne change pas la visée.
2. Le solveur de regard doit conserver la gauche pendant la montée ET la descente.
   Ne pas laisser `_steer()` ou la branche `look_direction = desired_velocity`
   annuler ce choix après le calcul.
3. À l'image de démarrage d'une attaque, le code actuel pose une vitesse voulue
   nulle. Quand `free_movement` est actif, continuer le moteur de trajet dès cette
   image pour ne pas provoquer un petit arrêt à chaque tir.
4. Préavertissement et rayon utilisent la même orientation et le même ancrage.
   Le rail continue ici pendant la préparation et le tir : le rectangle annoncé
   suit l'acteur. Une version qui tire depuis une position figée exige un choix
   distinct, pas un décalage entre visuel et hitbox.
5. Le choix répété utilise le contrôleur d'attaque et ses cooldowns. Aucun timer
   autonome qui lance un laser pendant la recharge vulnérable ou après la mort.

## 8. Expérience d'édition PHP

### Organisation de la fiche

```text
Monstre : [Big Egg]
Niveaux : [Niveau 1] [Niveau 2] [+]
         [Paramètres communs] [Phases] [Orchestration]

Phases  : [Carapace] [Recharge] [Balayage] [+]
          Hérite des paramètres communs du Niveau 1
          [Attaques] [Déplacement] [Défense/contact] [Présentation/sons]

Orchestration :
  ≡ Sous 20 %  [condition latched]  [ultime ............ → maintien]  (N2)
  ≡ Sous 50 %  [condition latched]  [balayage .......... → maintien]
  ≡ Cycle      [toujours]          ↻ [carapace 6 s][recharge 3 s] ∞
  🔒 Repli     [toujours]          [phase par défaut ................]
```

L'ordre des **onglets de phase** est une commodité d'édition. L'ordre des **lignes
de frise** est la priorité runtime. Les distinguer visuellement et dans l'aide.
« Niveau 2 / Phase Recharge » doit rester visible pendant l'édition pour éviter
une modification au mauvais endroit.

Créer des modules ciblés, par exemple `encounter_editor.js` et
`movement_program_editor.js`, comme l'audio possède déjà son module. Ne pas
ajouter tout le moteur d'édition dans une nouvelle longue branche de `app.js`.
`formRecord()` désigne actuellement le niveau sélectionné : ne pas le faire pointer
globalement vers une phase et détourner tous les autres champs de la fiche.
Utiliser un contexte d'édition explicite `(enemy_id, level_index, phase_id)` et
des chemins de modification dédiés ; les valeurs héritées affichées ne sont pas
automatiquement enregistrées comme overrides.

### Gestes de la frise

- Ajouter un segment depuis une phase, cliquer pour le sélectionner, déplacer
  horizontalement, redimensionner par les deux bords et saisir les temps au clavier.
- Règle temporelle, zoom, déplacement horizontal et aimantation aux voisins/frontières.
  Un déplacement conserve la durée ; un redimensionnement change la durée du
  segment, **pas la vitesse interne des attaques**.
- Poignée verticale distincte pour réordonner les lignes ; boutons monter/descendre
  accessibles au clavier. La ligne de repli ne peut ni bouger ni être effacée.
- Déplacer un segment vers une autre ligne est possible par une commande dédiée
  ou un drag vertical clairement distingué de la poignée de ligne ; tester la
  destination avant application et refuser un chevauchement local.
- Déclarer une boucle en sélectionnant une plage de segments, afficher son crochet,
  son nombre de passages ou ∞. Déplacer ses bornes sur les frontières valides ne
  doit pas déplacer les segments silencieusement. Copier/déplacer tout un groupe
  est une opération séparée, facultative.
- Afficher les conditions et l'horloge à gauche. Un état masqué apparaît atténué
  dans l'aperçu ; une bulle indique la ligne qui gagne et le motif de l'attente.
- Suppression/remplacement de phase référencée, duplication et remise à l'héritage
  doivent être explicites. Une seule action annuler/rétablir par drag complet.

Réutiliser la logique d'accroche et les calculs purs de la frise d'attaque après
extraction minimale, **pas** ses références à `currentItem().timeline` ni ses cinq
pistes fixes. Les fonctions actuelles `bindTimelineDrag`, `timelineSnap`,
`timelineBlockBounds` et `timelineSharedEdge` montrent les règles de voisinage,
mais leur contexte n'est pas générique aujourd'hui.

Au pointerdown, capturer ID stable, échelle secondes/pixel, valeur initiale et
revision du document. Prévisualiser sans autosave intermédiaire ; valider puis
marquer modifié au relâchement. `pointercancel`, perte de capture, Échap,
changement de niveau/onglet et disparition du segment doivent restaurer ou
annuler proprement la transaction et ses listeners. Un re-rendu ne doit pas
transformer un index périmé en écriture sur un autre segment. Conserver les
gardes existantes de version contre la publication d'un brouillon périmé.

### Prévisualisation et bac à sable

Deux compléments rendent l'outil vérifiable :

1. **Aperçu logique dans PHP/JS** : play/pause, pas, vitesse d'aperçu, curseur de
   PV et scénario de coups/soins. Afficher phase active/demandée, piste gagnante,
   cycle, temps local et raison d'un délai. Revenir en arrière reconstruit l'état
   depuis les données et les événements d'essai ; ne pas déplacer seulement le
   curseur en conservant les anciens verrous HP. Aucune écriture de sauvegarde,
   aucun effet `Registry`, aucune prétention de simuler exactement la physique.
2. **Bac à sable Godot existant** : monstre + niveau précis, phase/horloges et
   priorité en diagnostic, réglage de PV local, reset de rencontre et affichage
   du trajet/regard/visée. Distinguer « forcer la phase pour inspecter » et
   « jouer l'orchestration », avec un bouton de retour au fonctionnement normal.
   Un reload de données pendant l'essai redémarre explicitement la rencontre
   de test ; il n'essaye pas de conserver des références supprimées.

Le prévisualiseur JS et le résolveur Godot doivent partager des scénarios attendus
de sélection, boucles et seuils. Ne pas maintenir deux moteurs aux règles
approximativement ressemblantes. La preuve finale de mouvement et de collisions
reste le parcours Godot, pas le dessin PHP.

## 9. Présentation : rendre le combat compréhensible

Une alternance invulnérable/vulnérable est mémorable si le joueur comprend quand
éviter, se placer et attaquer. Prévoir au moins : silhouette/effet de bouclier
clairement visible, signal de rupture/recharge, changement de posture ou teinte,
préparation du laser et feedback d'un coup bloqué. Ne pas compter uniquement sur
une couleur pour distinguer les états. Une barre de boss peut afficher le nom
de la phase ou une icône de défense ; ses textes passent par `Loc`.

Les animations et clips restent configurés dans le gestionnaire PHP. Ajouter
si utile une sélection de clip d'entrée de phase depuis le même catalogue que
les attaques, tout en gardant les mappings du modèle actuel et la locomotion
en couche. La durée de protection est celle de la simulation, jamais celle
d'un clip qui pourrait manquer ou être modifié. La lecture de l'attaque garde
son override de clip ; une phase ne doit pas écraser à chaque frame une animation
de frappe en cours. Le pont lit les changements de présentation, pas les conditions HP.

Réutiliser le système audio en place : résolution des sons depuis le profil
effectif, snapshots par attaque, états hérités/silencieux conservés. Ajouter les
événements de phase nécessaires **une seule fois dans `audio_contract.json`**, puis
leurs véritables émetteurs (entrée/sortie, bouclier bloquant) et la garde de
raccordement. Un champ rempli dans PHP sans appel runtime serait une fausse livraison.
Ne pas rejouer l'entrée lors d'un maintien final, ni relancer la musique à chaque
tour de bouclier. Plusieurs élites ne doivent pas se disputer le morceau du biome.

Dans les boss exemples, augmenter le niveau signifie une nouvelle réponse à
apprendre, pas simplement davantage de PV : au niveau 2, phase ultime plus
resserrée ou combinaison de patterns déjà montrés, avec une ouverture restante.
Éviter d'accumuler invulnérabilité, spam de tirs et contact mortel sur toute l'arène
sans possibilité de réaction. Les durées proposées sont à éprouver avec les
vitesses, stamina et armes réelles de BIG EGG.

## 10. Cartographie des modifications et migration

| Fichier/fonctions actuels | Modification attendue |
| --- | --- |
| `scripts/combat/enemy.gd` : `setup`, `_physics_process`, `_desired_movement`, `_movement_for_behavior`, `_steer` | Initialiser un contrôleur optionnel, appliquer un profil sans respawn, brancher mouvement et regard indépendants. |
| Même fichier : `_try_begin_weighted_attack`, `_apply_attack_definition`, `_begin_attack`, `_finish_attack`, `_interrupt_attack` | IDs/cooldowns stables, politique de sélection, snapshot, admission, terminaison et annulation sûres. |
| Même fichier : `take_damage`, `die`, `get_enemy_definition` | Défenses effectives, seuils après dégâts, mort unique, distinction base/profil. |
| `scripts/core/enemy_awareness.gd` | Préserver perception ; exposer/configurer seulement ce qui est nécessaire sans remise à zéro en phase. |
| `scripts/core/attack_timeline.gd`, `attack_targeting.gd`, `attack_geometry.gd` | Réutiliser l'exécution de gestes. Adapter la direction fournie à `forward` côté acteur ; vérifier laser rectangulaire, ancrage et grands deltas sans remplacer ces moteurs. |
| `scripts/combat/enemy_projectile.gd` | Garder contexte de tir et éventuelle appartenance d'émetteur/passage pour une purge explicite ; pas de resélection de phase à l'impact. |
| `scripts/combat/dungeon.gd` | Fournir l'arène ; relier début/fin de rencontre ; conserver vagues, compteurs et fin de dimension à la mort réelle. |
| `scripts/core/sandbox_console.gd` | Diagnostics de phase/trajectoire et reload de test explicite. |
| `autoload/bridge_3d.gd`, `scripts/core/model_3d_factory.gd` | Refléter regard, bouclier/teinte et éventuel clip d'entrée ; conserver le mapping animation/loco existant, sans logique de combat. |
| `scripts/core/data_catalog.gd` | Normalisation d'encounter, overrides partiels, IDs d'attaque, nouveaux mouvements et refs des variantes ; version de schéma. |
| `tools/game-data-manager/config.php` | Miroir de ces validations, chargement du contrat, messages adressant niveau/phase/ligne/segment. Ne pas laisser le normaliseur jeter les champs. |
| `tools/game-data-manager/assets/app.js` + nouveaux modules ciblés | Navigation imbriquée, édition des overrides, frise et mouvement ; formRecord, clonage, références/usages, couleurs des sections modifiées et routage des changements. |
| `tools/game-data-manager/assets/audio_editor.js` | Accepter le contexte de phase et conserver les champs hérités sans écrire dans les sons communs. |
| Contrat/directeur/audio audit | Nouveaux événements réellement émis, pas une deuxième bibliothèque sonore. |
| Export/import, `bulk_workbook.php`, outils d'équilibrage, localisation | Inventorier les attaques de phase et références de mouvement ; inclure les nouvelles données dans les exports et recherches d'usages. L'Excel actuel est export seul. |
| `tools/dev/checks.py` et tests ciblés | Enregistrer le domaine de phases/mouvements et les vérifications d'intégration pertinentes. |

Avant toute modification d'implémentation, lire les directives courantes,
sauvegarder selon le niveau de risque (schéma/refonte), puis mesurer une baseline
ciblée. La racine et le brouillon ont des modifications utilisateur : ne pas
les réinitialiser pour faciliter une migration.

Migration versionnée et idempotente depuis la version réellement présente
(76 observée, **recontrôler**). Synchroniser constantes PHP/Godot, maître et
brouillon en préservant leurs différences. `encounter` absent garde le chemin
d'exécution historique ; ne pas activer de phases sur les 48 monstres d'office.
Attribuer les IDs d'attaque une seule fois et préserver ceux déjà présents ; ne
pas les recalculer lors d'un tri, d'un chargement ou d'une normalisation.

Normaliser les **patches de phase comme des patches** : ne pas compléter un champ
absent avec la valeur par défaut du monstre. Valider ensuite le profil composé
pour chaque phase. Préserver `{}` versus `[]` avec les conventions PHP existantes.
Les listes vides volontaires, zéros et faux sont essentiels ici.

Valider en particulier : IDs/references, noms localisables, champs autorisés,
nombres finis, temps/durées/poids bornés, loops non nulles, limites alignées,
absence de chevauchement local, phases atteignables, ID de mouvement connu,
séquence d'attaque cohérente, chemin à au moins un point et incompatibilités
regard/locomotion. Limites initiales proposées, documentées dans le contrat :
32 phases, 16 lignes, 128 segments par ligne, 32 points par trajet, temps d'auteur
<= 3 600 s, 1 000 passages finis maximum. Ce sont des garde-fous éditoriaux,
pas un prétexte pour dérouler toutes les répétitions en mémoire.

Les erreurs structurelles bloquent la publication avec un chemin précis. Les
risques de design — ligne toujours masquée, boss invulnérable sans fenêtre visible,
route obstruée probable, laser impossible à déclencher — sont des avertissements
expliqués. Une donnée runtime invalide désactive proprement le nouveau contrôleur
avec diagnostic limité et profil commun sûr ; ne pas tomber dans une boucle infinie.

### Coût runtime et stabilité

Compiler une fois par définition/version les profils de phase, la table des IDs
d'attaque, les bornes de segments et les paramètres de boucle. Ne pas dupliquer
profondément toutes les phases à chaque tick. Une instance ne garde que ses
curseurs, snapshots nécessaires et caches de recharges ; l'ancienne IA sans
encounter ne paie pas les parcours de la frise. Les conditions globales coûteuses
doivent suivre les mécanismes d'invalidation existants plutôt qu'un inventaire
complet de la partie à chaque image.

Pas de noeud, timer ou Tween créé par frame pour les décisions. Les lecteurs
sonores et présentations gardent leurs propres budgets existants. Limiter le
journal à une taille fixe (par exemple les 128 derniers changements), avec
diagnostics de blocage agrégés. Le contrôleur de frise est déterministe ; ses
calculs ne doivent pas consommer le hasard du son ni ajouter des tirages au
chemin historique d'un monstre sans phases.

Mesurer un combat avec un boss et des ennemis ordinaires, puis plusieurs élites :
temps de la mise à jour, allocations, nombre de transitions et stabilité du nombre
de noeuds/lecteurs. Fournir la mesure avant/après et la configuration testée, sans
promettre un nombre d'images par seconde sur la base du seul plan.

Ne pas ouvrir l'éditeur réel pour des captures automatiques sans tenir compte du
`reset_draft` d'initialisation. Utiliser le harnais/fixtures. Aucun nouveau service
ou plugin requis ; MCP Godot et le bac à sable existant servent à observer le rendu
et le jeu, pas à remplacer les validations PHP et les tests purs.

## 11. Situations de référence à réaliser

Les données illustratives et traces attendues sont dans
[exemples-reference.json](boss-phases/exemples-reference.json). Elles n'affirment
pas que le schéma ou le runtime proposés sont déjà implémentés.

### A. Boss niveau 1 : trois phases

Phase de repli : recharge vulnérable, pour ne pas créer un verrou invulnérable si
une piste d'essai est désactivée. Ligne normale : carapace 6 s → recharge 3 s,
boucle infinie. Ligne supérieure : dès PV <= 50 %, déclencher le balayage latéral,
puis le conserver. Une recharge commencée conserve ses 3 s d'exposition avant
la transition ordinaire ; la sélection du seuil est déjà mémorisée.

Sans seuil franchi : [0,6) carapace, [6,9) recharge, [9,15) carapace, etc.
Un coup au temps 7 s laissant 48 % des PV arme le balayage ; il commence au
temps 9 s si aucune autre attente n'intervient. Une fois sa séquence de 8 s
terminée, le boss continue sa route et ses attaques, sans réémettre l'entrée.

### B. Même boss niveau 2 : quatrième phase ultime

Cloner la fiche du niveau 1, ajouter la phase `ultimate` et une ligne tout en
haut avec PV <= 20 %, activation mémorisée. Le niveau 1 reste à trois phases.
Si un même coup non létal passe de 60 % à 18 %, les deux seuils sont armés,
mais la ligne ultime est demandée directement. Aucun passage de durée nulle
par le laser ni trois sons de transition sur le même coup.

Un soin ultérieur ne retire pas l'ultime. Si le coup est létal, c'est une mort
et une seule attribution de butin. Le nombre de phases n'impose pas des PV cachés.

### C. Élite simple

Repli héritant de la fiche actuelle, une phase d'enrage à PV <= 35 % avec vitesse
et liste d'attaques différentes. Pas de boucle nécessaire. Cet exemple démontre
qu'utiliser le système n'impose pas une grande frise ni le verrou d'engagement
destiné aux boss.

### D. Boucle finie puis phase finale

Préparation [0,2), carapace [2,8), recharge [8,11), boucle [2,11) jouée trois fois,
ultime [11,15) après la sortie de boucle. Durée effective avant ultime :
2 + 3×9 = 29 s. L'ultime occupe [29,33), puis reste active en `hold_last`.
Tester précisément 2, 8, 11, 20, 29 et 33 s, y compris avec un delta traversant
une frontière. L'affichage montre la durée déroulée sans dupliquer les segments.

### E. Priorité temporaire et reprise

Une ligne supérieure `while_true` masque momentanément le cycle inférieur.
Avec `pause`, il revient au même curseur ; avec `continue`, il revient à la
position atteinte pendant son absence. Dans les deux cas, un geste annulé n'est
pas repris en pleine frappe. Un trou de la piste supérieure rend momentanément
la main à la piste inférieure, sans geler l'horloge du trou.

## 12. Découpage et preuves de fonctionnement

| Lot | Livrable vérifiable |
| --- | --- |
| 1 — contrat | Schéma arrêté, règles de temps/priorité/boucle, fixtures et migration sans perte. IDs stables ; ancien ennemi inchangé. |
| 2 — moteur pur | Résolution d'overrides, conditions contextuelles, frise, boucles, masque/reprise, maintien ; traces déterministes Godot/JS comparables. |
| 3 — intégration Enemy | Une vraie alternance bouclier/recharge, protections composées, transitions sûres, PV/effets/cooldowns conservés et mort unique. |
| 4 — outil PHP | Niveaux/phases/frise éditables, drag des bords et lignes, boucle, héritage, références et publication. Une phase ajoutée par l'UI produit réellement un comportement. |
| 5 — mouvements chorégraphiés | Trajets configurables dans movement_patterns, regard indépendant, visée devant le corps, compatibilité avec les attaques et collisions. Le scénario du laser est un cas d'usage de test, pas un contenu à créer. |
| 6 — lisibilité et essais | Présentation/sons/diagnostic, bac à sable, boss N1/N2 et élite de référence dans une fixture ou un espace d'essai explicite. Ne pas remplacer silencieusement tous les boss publiés. |
| 7 — validation/livraison | Contrôles ciblés, parcours 3D, documentation d'auteur, checkpoints et relève. Ajouter ensuite les presets/orbites utiles si les fondamentaux sont validés. |

Ne pas lancer la suite complète à chaque modification d'éditeur. Utiliser le
runner `tools/dev/dev.py check --area ...` et enregistrer les nouveaux domaines.
Pour des tests neufs, appliquer le rouge/vert unique prescrit par `CLAUDE.md`.
En fin de chantier transversal, exécuter une validation globale pertinente une
fois. Les contrôles bruts peuvent toucher la sauvegarde réelle : garder l'isolation.
Ne pas lancer un éditeur headless concurrent du Godot graphique servant le MCP.

### Matrice minimale de tests

| Groupe | Preuves requises |
| --- | --- |
| Compatibilité | `encounter` absent/désactivé : mêmes choix et comportement existants ; niveaux/poids/no_return inchangés ; pas de tirages aléatoires supplémentaires pour les ennemis historiques. |
| Profil | Héritage, faux/zéro/liste vide, champ interdit, normalisation PHP/Godot identique, pas de fuite d'une phase vers une autre ou un autre boss. |
| Priorité | Deux seuils simultanés, réordre des lignes, ligne désactivée, trou transparent, ligne de repli immuable, horloge relative versus absolue. |
| Activation | Seuil sur dégâts acceptés, soin après activation mémorisée, while_true réarmé, cible invalide, aucun fait global partagé entre deux instances. |
| Boucles | Exactes frontières [start,end), N=1/3/∞, suffixe après boucle finie, refus des boucles nulles/overlaps, pas d'explosion mémoire avec une longue durée simulée. |
| Temps | Pause/reprise, grand delta, préemption pause/continue, trou qui avance, attente de transition, maintien sans re-entrée ; nouveaux déclencheurs après l'horizon d'aperçu. |
| Transitions | Attaque terminée/interrompue, délai maximum, fenêtre minimale de vulnérabilité, demande supérieure remplaçant une attente, morts pendant préparation et pendant changement. |
| Dégâts | Bouclier + i-frames en OU, armure distincte des dégâts, retour vulnérable réel, DOT et soin, recul, une mort/butin/compteur malgré plusieurs coups dans le même tick. |
| Attaques | IDs/recharges conservés après réordre/changement de phase, snapshot de projectile, mode séquence refus sans consommation, attente finie et absence de tir autonome après changement. |
| Route | Départ hors rail rejoint physiquement, bornage au corps, demi-tour, attente aux points, blocage sans téléportation, immobilisation/recul, priorité du mouvement d'attaque, aucun stop artificiel au début du tir libre. |
| Regard/laser | Monte/descend en regardant gauche ; rayon part gauche même si joueur derrière ; avertissement superposé au hit ; cible hors cône n'annule pas l'engagement du boss. |
| UI réelle | Créer N2/P4, drag/resize/snap, réordonner, éditer boucle, annuler pointercancel, changer niveau pendant drag, undo transactionnel, sauvegarder/recharger/exporter sans changer N1. |
| Intégrations | Audio entrée une fois, son réellement émis ; modèles/animations PHP conservés ; vague/porte ne se terminent qu'à la mort finale ; reset bac à sable propre. |

Les grands deltas demandent une règle explicite : le résolveur parcourt les
frontières temporelles pertinentes et produit une trace ordonnée, mais le jeu
ne rejoue pas dans une même frame une rafale de toutes les attaques historiques
qui auraient eu lieu. Découper les mises à jour dans une limite bornée ; si cette
limite est dépassée, rejoindre un état cohérent, annuler les gestes périmés et
signaler le rattrapage en diagnostic. Les calculs de boucles restent arithmétiques,
sans dérouler un million de segments. Tester le moteur temporel seul à grands
deltas puis la simulation réelle à son pas physique normal.

Tests d'intégration ciblés existants à examiner : `enemy_levels_check`,
`enemy_awareness_check`, `attack_targeting_check`, les tests de frise d'attaque,
de mouvement/validation PHP, et les checks `bridge_3d`, `flow`, `audio` pertinents.
Mesurer la baseline du chantier ; les anciens rapports de la passation audio
ne certifient pas les scripts modifiés depuis. Ne pas corriger des systèmes
voisins uniquement pour rendre une ancienne batterie entièrement verte.

### Critères de livraison et relais

L'auteur doit pouvoir créer le boss N1 à trois phases, cloner N2 et ajouter
l'ultime, régler seuils/durées/priorité/boucles, publier, puis observer le même
résultat dans le bac à sable et une salle du jeu. Il doit pouvoir obtenir le rail
laser depuis les champs de mouvements et d'attaques, sans script spécial de boss.
Les diagnostics expliquent pourquoi une phase attend ou ne se déclenche pas.

Conserver les changements utilisateur. Pour cette refonte, faire la sauvegarde
complète demandée par les directives avant migration (hors `.godot/`), puis des
checkpoints locaux par lot avec chemins explicites. Aucun push. Documenter les
résultats réels et les limites artistiques/performance restantes.

À chaque lot fini : actualiser `docs/current-task.md`, exécuter
`python tools/dev/dev.py handoff`, et indiquer prochaine action, schéma, données
touchées, tests effectivement lancés et modes restant incomplets. Un éditeur de
phases sans moteur raccordé, ou un moteur sans édition PHP des mouvements, ne
satisfait pas ce mandat.
