docs(phase2): record the P3 optimizer playbook and plan status
This commit is contained in:
@@ -421,17 +421,91 @@ signal le plus net ici est la **queue** (worst-decile HP 1,4 → 6,8 en 3v2 ;
|
|||||||
0,0 dans les deux cellules 2v3) et les rounds moyens (6,5 → 5,6 en 3v2). La
|
0,0 dans les deux cellules 2v3) et les rounds moyens (6,5 → 5,6 en 3v2). La
|
||||||
cartographie du danger à grande n, PJ **et** monstres, reste **P4**.
|
cartographie du danger à grande n, PJ **et** monstres, reste **P4**.
|
||||||
|
|
||||||
|
### Optimiseur (P3 — commit dd64c2e)
|
||||||
|
|
||||||
|
`scenarios.py` : `SideSpec(name, zone, combatant_files, hp_fraction=1.0)` et
|
||||||
|
`ScenarioSpec(name, map_file, sides, round_cap=100)` — `build_encounter`
|
||||||
|
compile un scénario en `EncounterSpec` déterministe (aucun chemin de données
|
||||||
|
en dur dans le src, §15), `scenario_variants(base, hp_fractions=...)` étend
|
||||||
|
grossier le scénario par fractions de PV des côtés (grille de robustesse, sans
|
||||||
|
RNG). Test : `tests/test_phase2_scenarios.py`.
|
||||||
|
|
||||||
|
`search.py` : `evaluate(strategy, scenario, *, party, runs, seed, workers=4)`
|
||||||
|
(`StrategyBenchmark` : métriques de danger du camp `party` + bande 3σ sur le
|
||||||
|
taux de victoire), `PlaybookEntry(rank, strategy, benchmark)`,
|
||||||
|
`strategy_name` (nom canonique de `StrategySpec`) et
|
||||||
|
`optimize(scenario, *, party, runs, seed, workers=4, max_total_runs=None,
|
||||||
|
candidates=())` — recherche en 3 étages : grille grossière de 24 cellules
|
||||||
|
(cible × politique de sorts × charges × capacités de classe, repli à 0),
|
||||||
|
affinage des 3 meilleures cellules sur `retreat_hp_fraction ∈ {0.25, 0.5,
|
||||||
|
0.75}` (9 cellules ; cellules déjà mesurées dédupliquées), puis les candidats
|
||||||
|
LLM (`candidates`, P3.3) dédupliqués et évalués. Classement :
|
||||||
|
`(-P(survive), PV perdus, -pire dcile PV, rounds, nom)`. Le budget
|
||||||
|
`max_total_runs` est vérifié **avant** tout combat (`ValueError` nommant
|
||||||
|
`budget`/`max_total_runs`). `format_playbook` rend le classement en colonnes
|
||||||
|
ASCII avec la bande 3σ. Test : `tests/test_phase2_search.py`.
|
||||||
|
|
||||||
|
**Playbook représentatif (RUNS=200, seed=7, workers=4, camp mesuré =
|
||||||
|
`players` = 3 gobelins vs 2 orcs)** — 33 cellules (grille 24 + affinage 9 +
|
||||||
|
candidats 0) ; les rangs non listés sont des variantes à leviers inertes ou
|
||||||
|
de repli strictement identiques à leur cellule de tête :
|
||||||
|
|
||||||
|
| Rang | Stratégie | Victoire PJ (3σ) | Survivent | ≥1 mort | PV perdus | Pire dcile | Rounds |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| 1 | `lowest_hp` (+ inertes, rangs 2-8) | 97,0 % [93,4 ; 100,0] | 99,0 % | 1,0 % | 3,0 | 5,2 | 5,8 |
|
||||||
|
| 9 | `weakest_ac` (+ inertes, rangs 10-16) | 97,0 % [93,4 ; 100,0] | 99,0 % | 1,0 % | 3,0 | 5,2 | 5,8 |
|
||||||
|
| 17 | `lowest_hp, retreat=0.25` (+ inertes) | 96,0 % [91,8 ; 100,0] | 97,5 % | 2,5 % | 3,1 | 3,9 | 6,1 |
|
||||||
|
| 20 | `lowest_hp, retreat=0.5` (+ inertes) | 95,0 % [90,4 ; 99,6] | 97,5 % | 2,5 % | 3,5 | 3,0 | 6,4 |
|
||||||
|
| 23 | `default` (nearest, repli 0) | 95,0 % [90,4 ; 99,6] | 97,0 % | 3,0 % | 5,0 | 2,8 | 6,9 |
|
||||||
|
| 31 | `lowest_hp, retreat=0.75` (+ inertes) | 94,0 % [89,0 ; 99,0] | 95,5 % | 4,5 % | 3,7 | 2,7 | 7,6 |
|
||||||
|
|
||||||
|
TPK 0,0 % dans les 33 cellules. → la sortie P3 (« meilleur candidat du grid +
|
||||||
|
affinage, playbook classé ») est atteinte : 6600 combats en quelques secondes
|
||||||
|
sur 4 workers, classement déterministe (rejoué bit-à-bit), budget vérifié
|
||||||
|
avant exécution.
|
||||||
|
→ **Le ciblage est le levier dominant** : `lowest_hp` et `weakest_ac` battent
|
||||||
|
`default` sur toutes les métriques du camp — PV perdus 5,0 → 3,0, pire dcile
|
||||||
|
2,8 → 5,2, rounds 6,9 → 5,8, survive 97,0 → 99,0 %. Égalité bit-à-bit entre
|
||||||
|
les deux ordres de cibles : les adversaires adverses étant statistiquement
|
||||||
|
identiques (gobelins entre eux, orcs entre eux), les deux heuristiques
|
||||||
|
sélectionnent la même cible au même rang.
|
||||||
|
→ **La retraite agressive est strictement dominée** : seuil 0,25 → 0,5 → 0,75
|
||||||
|
dégrade la survie (99,0 → 97,5 → 97,5 → 95,5 %), le pire dcile (5,2 → 3,9 →
|
||||||
|
3,0 → 2,7) et allonge le combat (5,8 → 6,1 → 6,4 → 7,6 rounds) — une retraite
|
||||||
|
est une action à round complet qui paie le départ sans compenser.
|
||||||
|
→ **Leviers inertes sur cette rencontre** : `spell_policy`, `use_charges` et
|
||||||
|
`use_class_abilities` ne changent rien (gobelins et orcs n'ont ni sorts, ni
|
||||||
|
ressources, ni capacités de classe) — leurs variantes font exactement égalité
|
||||||
|
avec leur cellule de grille (rangs 2-8, 10-16, 24-30). Un scénario avec
|
||||||
|
lanceur est nécessaire pour discriminer ces leviers (rencontres riches en
|
||||||
|
P4/P5).
|
||||||
|
→ **Sémantique de mesure — symétrique** : une politique par cellule, appliquée
|
||||||
|
aux **deux** camps (`CombatEngine` exécute la politique passée au moteur, sans
|
||||||
|
routage par côté ; `compile_strategy` est côté-agnostic) ; le classement porte
|
||||||
|
sur les `DangerMetrics` du camp `party`. Contrairement au spike P2
|
||||||
|
(`focus_fire_policy` routait par côté : orcs uniquement), le playbook mesure
|
||||||
|
donc « la meilleure stratégie quand tout le monde la joue » — l'asymétrie PJ
|
||||||
|
contre adversaire optimisé arrive en P4 (`AdversarySpec`).
|
||||||
|
→ Cohérence : la cellule `default` (95,0 % [90,4 ; 99,6]) contient la ligne P2
|
||||||
|
défaut (93,0 %) dans sa bande 3σ (mêmes seeds, n = 100 → 200).
|
||||||
|
→ Limite : P3.3 (candidats LLM) est câblé et testé (déduplication,
|
||||||
|
0 candidat ici) mais non exercé contre un vrai LLM — aucun fournisseur avant
|
||||||
|
P5. Bande 3σ ±4,6 points à p = 0,97, n = 200 : au sein d'une famille à
|
||||||
|
leviers inertes les écarts sont nuls (bit-identiques), mais un départage plus
|
||||||
|
fin (n plus grand, carte plus riche) reste ouvert.
|
||||||
|
|
||||||
### État du plan
|
### État du plan
|
||||||
|
|
||||||
- **P0** ✅ · **P1** ✅ (`LLMClient` + `FakeLLMClient` scripté + cache
|
- **P0** ✅ · **P1** ✅ (`LLMClient` + `FakeLLMClient` scripté + cache
|
||||||
composite, garde-fous coût/tokens, `NLPolicy`, `PromptBuilder` versionné) ·
|
composite, garde-fous coût/tokens, `NLPolicy`, `PromptBuilder` versionné) ·
|
||||||
**P2** ✅ (`strategy.py` + `SweepRunner`, sortie mesurée) — gate verte
|
**P2** ✅ (`strategy.py` + `SweepRunner`, sortie mesurée) · **P3** ✅
|
||||||
(**2116 tests**).
|
(`scenarios.py` + `search.py`, playbook représentatif mesuré) — gate verte
|
||||||
|
(**2134 tests**).
|
||||||
- Détail P0 : **P0.0 (D1)** ✅ · **P0.1 (`schema_version`)** ✅ · mesures ✅ ·
|
- Détail P0 : **P0.0 (D1)** ✅ · **P0.1 (`schema_version`)** ✅ · mesures ✅ ·
|
||||||
`objectives.py` ✅ · `collect_runs` ✅ · **P0.2** (vue compacte) ✅ ·
|
`objectives.py` ✅ · `collect_runs` ✅ · **P0.2** (vue compacte) ✅ ·
|
||||||
**P0.3** (`candidates`) ✅ · **P0.4** (`llm_policy` + `policy_factory`) ✅ ·
|
**P0.3** (`candidates`) ✅ · **P0.4** (`llm_policy` + `policy_factory`) ✅ ·
|
||||||
**P0.5** (ReplayLog `call_seq` + `replay()` identique) ✅.
|
**P0.5** (ReplayLog `call_seq` + `replay()` identique) ✅.
|
||||||
- **Frontière P0 atteinte** : gate verte (2022 tests) ; un run enregistré se
|
- **Frontière P0 atteinte** : gate verte (2022 tests) ; un run enregistré se
|
||||||
rejoue à l'identique.
|
rejoue à l'identique.
|
||||||
- Suite : **P3** (optimiseur « meilleure stratégie »), P4 (cartographie
|
- Suite : **P4** (adversaire `AdversarySpec` + cartographie complète), P5
|
||||||
complète), P5 (fournisseur réel + rapport).
|
(fournisseur réel + rapport).
|
||||||
|
|||||||
Reference in New Issue
Block a user