# Le « Boss d'école » — modèle de référence

Identifiant : `ecole_boss_phases`. Publié dans le maître **et** le brouillon.

Ce n'est pas du contenu de jeu : c'est un **modèle à lire et à copier**, et un
point de comparaison pour un prochain boss. Il n'utilise que des patterns
d'attaque et des mouvements qui existaient déjà — rien de neuf n'a été créé pour
lui.

## Il n'apparaîtra jamais tout seul

Son `spawn_weight` vaut **0** aux deux niveaux. Les vagues d'une salle nomment
leurs monstres explicitement (`"enemies": {"crazy_squirrel": 4}`), donc un poids
nul le tient hors des tirages sans rien empêcher d'autre. Pour le voir :

1. Bac à sable → onglet **Spawn** → choisir « Boss d'école ».
2. Choisir **N1** ou **N2** (le choix de niveau n'apparaît que pour les monstres
   qui en portent). « auto » laisse le jeu prendre la variante la plus avancée —
   donc **N2**.
3. Le faire agresser : une rencontre démarre au **premier aggro**, pas à
   l'apparition.
4. Onglet **Phases** pour lire ce qui se passe et pourquoi.

## Niveau 1 — trois phases, deux lignes

| Phase | Protection | Attaque | Déplacement |
| --- | --- | --- | --- |
| **Carapace** | imprenable + ininterruptible, recul nul | `shockwave_stomp` 360° | immobile |
| **Ouverture** | vulnérable, **dégâts ×1,5** | `shield_bash` court | `lumber`, lent |
| **Balayage** | ininterruptible, dégâts normaux | `scan_beam` étroit, 380 px | `stalk`, rapide |

**Ligne « Respiration »** (priorité basse) : carapace `[0, 6)` puis ouverture
`[6, 9)`, **boucle infinie**. C'est le rythme du combat.

**Ligne « Sous 50 % »** (priorité haute) : dès que les PV tombent à la moitié, le
balayage prend la main et le **garde** (`hold_last`). Activation **mémorisée** :
un soin ne la désarme pas.

Trois choix valent la peine d'être lus :

- la carapace **attaque quand même**. Une phase imprenable où l'on attend sans
  rien faire n'apprend rien au joueur ;
- l'ouverture garde **3 s d'exposition minimale** : la fenêtre ne se referme pas
  au premier coup qui franchit un seuil ;
- la transition de la ligne haute est `finish_attack` avec 0,75 s de plafond —
  le boss finit son geste avant de basculer, mais pas indéfiniment.

## Niveau 2 — la phase ultime

Le niveau 2 **clone** le niveau 1 et ajoute :

| Phase | Protection | Attaques |
| --- | --- | --- |
| **Ultime** | ininterruptible, **dégâts ×0,75**, recul très résisté | `confetti_storm` (poids 3) + `scan_beam` |

Sa ligne « Sous 20 % » est placée **tout en haut** — l'ordre *est* la priorité,
il n'y a pas de second nombre. Elle est **urgente** : elle passe outre
l'exposition minimale d'une ouverture en cours, parce qu'un seuil à 20 % ne doit
pas attendre trois secondes.

**Le niveau 1 reste à trois phases.** C'est tout l'intérêt de la hiérarchie
monstre → niveau → phases.

## Ce qu'il fait vraiment, mesuré

Déroulé observé en poussant le temps et en forçant les seuils :

```
NIVEAU 1 — 900 PV, 3 phases
   0,1 s  carapace    imprenable   dégâts ×1,00
   6,1 s  ouverture   vulnérable   dégâts ×1,50
   9,1 s  carapace    imprenable   dégâts ×1,00     (la boucle reboucle)
  12,1 s  balayage    vulnérable   dégâts ×1,00     (seuil 50 % franchi)

NIVEAU 2 — 1400 PV, 4 phases
   … identique …
  26,1 s  ultime      vulnérable   dégâts ×0,75, 2 attaques   (seuil 20 %)
```

## Deux défauts trouvés en le construisant

**1. Les conditions de ligne n'étaient pas publiables.** `normalize_encounter_track`
confiait ses conditions au validateur générique, qui ne connaît que `fact.`,
`stat.`, `player.` et les collections. Les trois adresses locales d'une
rencontre — pourtant déclarées au contrat et **proposées par le gestionnaire** —
étaient donc refusées. Un boss qui change de phase à 50 % de PV, le cas d'école
même de ce chantier, ne pouvait pas être publié, alors que Godot les acceptait :
les deux côtés divergeaient exactement là où un contrat partagé devait
l'empêcher.

Les tests ne l'avaient pas vu parce qu'ils n'utilisaient que des **listes de
conditions vides**. C'est corrigé, et les deux tests utilisent maintenant une
vraie condition.

**2. Le smoke test empruntait une fiche d'auteur.** Il choisissait « la première
créature sans visuel » comme cobaye et ne se rabattait sur sa propre fiche que si
le bestiaire était entièrement habillé — ce qu'il était. Cette branche n'avait
donc **jamais tourné**. L'arrivée du boss (sans art) l'a réveillée et cinq
contrôles sont tombés d'un coup, sur du code qui n'avait pas bougé.

La cause : le test posait les visuels sur la fiche de **base**, mais `setup()`
résolvait vers le **niveau 2**, dont les visuels sont vides. Le test pose
désormais toujours sa propre fiche, sans niveaux.

## Le bac à sable choisit maintenant le niveau

Le plan demandait « monstre **+ niveau précis** ». L'onglet Spawn ne le
permettait pas : un boss à plusieurs niveaux apparaissait toujours dans sa forme
la plus avancée, et on ne pouvait pas comparer N1 et N2 dans la même salle. Des
boutons `auto / N1 / N2…` s'affichent désormais pour les monstres qui portent des
niveaux, et un niveau explicite court-circuite le tirage de variante.

## Le modifier

Rien n'est sacré ici : c'est fait pour être copié et charcuté. Aucun test ne
dépend de ses valeurs — un contrôle ne doit pas emprunter les données d'auteur,
c'est précisément la leçon du défaut n° 2. La seule garde générique vérifie
qu'**une rencontre publiée n'est pas perdue au chargement**, quel que soit le
monstre.
