diff --git a/docs/phase-2-llm-architecture.md b/docs/phase-2-llm-architecture.md index 31ac7db..03098c3 100644 --- a/docs/phase-2-llm-architecture.md +++ b/docs/phase-2-llm-architecture.md @@ -379,13 +379,59 @@ dégelé du JSONL). Test : `tests/test_phase2_replay.py`. Le rejet de replay par défaut est documenté (D3 : pas de mémoire inter-tours ; `delay`/ready rejoués par les actions enregistrées elles-mêmes, sans ré-invocation). +### Étage A (P2 — commit dbd1a58) + +`strategy.py` : `StrategySpec` (cible `nearest`/`weakest_ac`/`lowest_hp`, +`retreat_hp_fraction`, `spell_policy` `default`/`none`, `use_charges`, +`use_class_abilities`) et `compile_strategy(spec) -> Policy` — +`compile_strategy(StrategySpec()) is default_policy` (identité) ; les autres +specs compilent vers des politiques paramétrées (échelle de mêlée, charge +quand la ligne droite existe, retraite sous le seuil de pv, sorts / charges / +capacités de classe désactivables), cible choisie par `select_target` +(CA la plus basse, pv les plus bas, ou le plus proche). Test : +`tests/test_phase2_strategy.py`. + +`sweep.py` : `SweepCell(scenario, strategy, spec, policy, runs, seed)` + +`SweepRunner(party=, workers=)` (registry des sorts chargée **une fois**, +lecture seule partagée entre threads — D10), `run(cells, max_total_runs=)` +(erreur claire nommant `runs`/`budget`, cellules ≥ 1 run) et `run_grid(...)` +en ordre **scénario-majeur** — chaque cellule : `run_encounter(collect_runs=True)` ++ `danger_metrics`. Une politique `None` = `default_policy` (le runner +n'importe pas `strategy.py`). `format_sweep` : rendu texte aligné (ascii). +Tests : `tests/test_phase2_sweep.py` (déterminisme bit-à-bit, workers=1 ≡ +workers=4, cellule `None` ≡ `run_encounter` direct, borne de coût). + +**Mesure représentative (RUNS=100, seed=7, partie `players` = gobelins, +adversaire `focus fire` = orcs)** : + +| Scénario | Monstres | Victoire PJ | Survivent | TPK | ≥1 mort | PV perdus | Rounds | +|---|---|---|---|---|---|---|---| +| 3 gobelins vs 2 orcs | défaut | 93,0 % | 96,0 % | 0,0 % | 4,0 % | 5,0 | 6,5 | +| 3 gobelins vs 2 orcs | focus fire | 98,0 % | 100,0 % | 0,0 % | 0,0 % | 2,4 | 5,6 | +| 2 gobelins vs 3 orcs | défaut | 50,0 % | 96,0 % | 0,0 % | 4,0 % | 7,5 | 10,6 | +| 2 gobelins vs 3 orcs | focus fire | 54,0 % | 96,0 % | 0,0 % | 4,0 % | 7,3 | 10,1 | + +→ la sortie P2 (« sweep ≥ 2×2 avec métriques, borné en coût ») est atteinte : +2×2 = 400 combats en quelques secondes sur 4 workers, métriques de danger +complètes par cellule, déterminisme vérifié. Ce 2×2 gobelins/orcs est un +**smoke test du runner** (n=100, bande 3σ ±6 % sur les taux) : à ce n, le +delta focus fire est dans le bruit sur les morts (4→0 en 3v2, plat en 2v3) — +contrairement au spike warband/liche où il sortait nettement de la bande. Le +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 +cartographie du danger à grande n, PJ **et** monstres, reste **P4**. + ### État du plan -- **P0.0 (D1)** ✅ · **P0.1 (`schema_version`)** ✅ · mesures ✅ · +- **P0** ✅ · **P1** ✅ (`LLMClient` + `FakeLLMClient` scripté + cache + composite, garde-fous coût/tokens, `NLPolicy`, `PromptBuilder` versionné) · + **P2** ✅ (`strategy.py` + `SweepRunner`, sortie mesurée) — gate verte + (**2116 tests**). +- Détail P0 : **P0.0 (D1)** ✅ · **P0.1 (`schema_version`)** ✅ · mesures ✅ · `objectives.py` ✅ · `collect_runs` ✅ · **P0.2** (vue compacte) ✅ · **P0.3** (`candidates`) ✅ · **P0.4** (`llm_policy` + `policy_factory`) ✅ · **P0.5** (ReplayLog `call_seq` + `replay()` identique) ✅. - **Frontière P0 atteinte** : gate verte (2022 tests) ; un run enregistré se rejoue à l'identique. -- Suite : **P1** (client + politiques), P3 (optimiseur), P4 (cartographie - complète), P5 (fournisseur + rapport). +- Suite : **P3** (optimiseur « meilleure stratégie »), P4 (cartographie + complète), P5 (fournisseur réel + rapport).