docs(phase2): record the P4 robustness measurements and plan status
This commit is contained in:
@@ -494,18 +494,131 @@ 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.
|
||||
|
||||
### Adversaire et robustesse (P4)
|
||||
|
||||
`adversary.py` : `AdversarySpec(target_priority="nearest" | "lowest_hp" |
|
||||
"weakest_ac" | "healer", spell_policy="default" | "none",
|
||||
retreat_hp_fraction=0.0 | 0.25 | 0.5, use_charges=True | False)` — les
|
||||
leviers adverses (D12 : l'adversaire joue pour gagner) ; un seul
|
||||
`AdversarySpec()` (défaut = `nearest`, la politique focus fire du spike P2)
|
||||
est composé avec la stratégie du camp mesuré en une seule politique de
|
||||
combat par `compile_matchup(party, adversary, party_side=)`. `adversary_name`
|
||||
: nom canonique d'un levier. Test : `tests/test_phase2_adversary.py`.
|
||||
|
||||
`robustness.py` : `matchup_benchmark(scenario, party, adversary, *,
|
||||
party_side, runs, seed) -> MatchupBenchmark` (mesure le danger des DEUX
|
||||
camps — même mesure, les deux sens du §3.2), puis les trois couches :
|
||||
|
||||
- `adversarial_analysis` (P4.2, min-max alterné) : itérations successives —
|
||||
la partie maximise sa survie parmi `party_candidates`, puis l'adversaire
|
||||
minimise le danger de la partie parmi `adversary_candidates` ; chaque
|
||||
itération est rendue par `format_robustness`. Le budget `max_total_runs=`
|
||||
est vérifié **avant** tout combat (`ValueError` nommant
|
||||
`budget`/`max_total_runs`).
|
||||
- `danger_map` (P4.3) : grille `SideSpec.hp_fraction` PJ × adversaire, une
|
||||
cellule par paire de fractions = un `MatchupBenchmark` ; `format_danger_map`
|
||||
rend la grille (survie des deux camps par cellule).
|
||||
- `frontier` (P4.3) : bissection de la fraction de PV PJ où `criterion`
|
||||
croise `target` (convention §3.2), adversaire fixé — le retour est plafonné
|
||||
au pas `precision` (`ceil`, jamais arrondi, pour rester du côté croisant du
|
||||
seuil) ; None quand même `max_fraction` ne croise pas. `format_frontier`
|
||||
rend « no frontier ».
|
||||
- `sensitivity` (P4.3) : classe les 7 leviers adverses par |delta| du
|
||||
critère de la partie vs baseline `AdversarySpec()` (budget exact 8×runs,
|
||||
vérifié avant combat) ; `format_sensitivity` affiche les probabilités en %,
|
||||
`expected_hp_lost` à 1 décimale.
|
||||
|
||||
Tests : `tests/test_phase2_robustness.py`.
|
||||
|
||||
**Mesures représentatives (RUNS=100, seed=7, camp mesuré = `players` =
|
||||
3 gobelins vs 2 orcs, `sample_arena.yaml`)** :
|
||||
|
||||
P4.1 — équilibre du min-max alterné (2 itérations ; l'adversaire best-
|
||||
response converge vers `AdversarySpec()` dès la première itération, les deux
|
||||
itérations sont bit-identiques) :
|
||||
|
||||
| Stratégie PJ | P(survie PJ) | PV perdus PJ | P(survie adv) | PV perdus adv | TPK |
|
||||
|---|---|---|---|---|---|
|
||||
| `default` (nearest) | 96 % | 5,0 | 98 % | 11,6 | 0 % |
|
||||
| `lowest_hp` | 95 % | 6,3 | 99 % | 11,5 | 0 % |
|
||||
| `retreat=0.5` | 97 % | 5,3 | 99 % | 11,4 | 0 % |
|
||||
|
||||
→ la meilleure réponse adverse est le ciblage `nearest` par défaut (le plus
|
||||
dangereux pour la partie, voir sensitivity) ; face à cet équilibre les trois
|
||||
stratégies PJ sont dans le bruit (bande 3σ ±6 points à n=100). Le camp
|
||||
adverse encaisse ~11,5 PV moyens par combat (il perd presque toutes les
|
||||
runs) mais ne **meurt** (hp ≤ −CON) que dans ~2 % des runs (98-99 % de
|
||||
survie adv) : la rencontre bat l'adversaire en le mettant à ≤ 0 pv sans
|
||||
souvent le tuer — cohérent avec la règle de mort `hp ≤ −CON` du moteur.
|
||||
|
||||
P4.2 — danger_map (PJ `default` × adversaire `weakest_ac`, fractions
|
||||
0.5/0.7/1.0, 9 cellules ; grille `Party\Adv` : survie partie/survie
|
||||
adversaire par cellule) :
|
||||
|
||||
| | adv=0.5 | adv=0.7 | adv=1.0 |
|
||||
|---|---|---|---|
|
||||
| party=0.5 | 100 %/100 % | 100 %/99 % | 99 %/99 % |
|
||||
| party=0.7 | 99 %/100 % | 100 %/99 % | 100 %/99 % |
|
||||
| party=1.0 | 100 %/100 % | 100 %/99 % | 100 %/99 % |
|
||||
|
||||
→ la grille est plate (99-100 %) et le TPK nul partout : la mort est un
|
||||
événement rare sur cette rencontre (au plus 1 run sur 100 par cellule) et
|
||||
les écarts observés restent sous la bande 3σ (±6 points) — même décimée à
|
||||
50 % de PV, la partie survit. Une carte discriminante exige une rencontre
|
||||
plus équilibrée (le 2 gobelins vs 3 orcs de P2, où ≥ 1 mort monte à 4 %).
|
||||
|
||||
P4.3 — frontier (`p_survives`, target=0.5, fractions [0.10, 1.00],
|
||||
adversaire `healer`) : **`no frontier`** — la survie de la partie ne
|
||||
descend jamais sous 50 %, même à 10 % de PV initiaux : le plancher de
|
||||
robustesse de la partie est sous la barre des 10 %.
|
||||
|
||||
P4.3 — sensitivity (criterion=`expected_hp_lost`, baseline `default` :
|
||||
PV perdus 4,97, survie 96 %) :
|
||||
|
||||
| Rang | Levier | Value | Delta |
|
||||
|---|---|---|---|
|
||||
| 1 | `healer` | 2,4 | −2,6 |
|
||||
| 2 | `lowest_hp` | 2,4 | −2,6 |
|
||||
| 3 | `weakest_ac` | 2,4 | −2,6 |
|
||||
| 4 | `retreat=0.5` | 3,1 | −1,9 |
|
||||
| 5 | `retreat=0.25` | 4,0 | −1,0 |
|
||||
| 6 | `spell_policy=none` | 5,0 | +0,0 |
|
||||
| 7 | `use_charges=false` | 5,0 | +0,0 |
|
||||
|
||||
→ le ciblage est le levier adverse le plus sensible : quitter `nearest`
|
||||
pour n'importe quel ciblage explicite (healer/lowest_hp/weakest_ac, ex æquo
|
||||
à −2,6) fait tomber l'attrition de la partie de 5,0 à 2,4 PV et remonte sa
|
||||
survie à 100 % — le ciblage de la cible la plus proche (la plus engagée en
|
||||
mêlée) est donc le plus dangereux pour la partie ; des adversaires
|
||||
statistiquement identiques rendent les trois ciblages ex æquo (même cible au
|
||||
même rang, déjà observé en P3). La retraite orc (combat écourté) réduit
|
||||
aussi le danger (−1,9 / −1,0). Leviers inertes : `spell_policy` et
|
||||
`use_charges` (les orcs n'ont ni sorts ni ressources) — delta 0,000.
|
||||
Budget : `adversarial_analysis(max_total_runs=1)` → `ValueError`
|
||||
(« robustness budget exceeded: 800 runs > 1 max_total_runs ») avant tout
|
||||
combat. Déterminisme : deux passes de `matchup_benchmark` bit-identiques
|
||||
(p_survives 1,000 / p_tpk 0,000 / hp_lost 2,4).
|
||||
|
||||
→ la sortie P4 (« adversaire `AdversarySpec` + cartographie de danger
|
||||
complète, bornée en coût ») est atteinte : min-max alterné convergeant,
|
||||
grille de danger 3×3, frontière de survie, classement des leviers adverses,
|
||||
déterminisme et budget vérifiés. Les mesures placent la rencontre de
|
||||
référence 3v2 du côté sûr de l'équilibre : TPK nul, plancher de survie sous
|
||||
10 % de PV, et le seul levier adverse qui y compte est le ciblage par
|
||||
défaut.
|
||||
|
||||
### État du plan
|
||||
|
||||
- **P0** ✅ · **P1** ✅ (`LLMClient` + `FakeLLMClient` scripté + cache
|
||||
composite, garde-fous coût/tokens, `NLPolicy`, `PromptBuilder` versionné) ·
|
||||
**P2** ✅ (`strategy.py` + `SweepRunner`, sortie mesurée) · **P3** ✅
|
||||
(`scenarios.py` + `search.py`, playbook représentatif mesuré) — gate verte
|
||||
(**2134 tests**).
|
||||
(`scenarios.py` + `search.py`, playbook représentatif mesuré) · **P4** ✅
|
||||
(`adversary.py` + `robustness.py`, adversaire `AdversarySpec` + carte de
|
||||
danger complète) — gate verte (**2169 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 : **P4** (adversaire `AdversarySpec` + cartographie complète), P5
|
||||
(fournisseur réel + rapport).
|
||||
- Suite : **P5** (fournisseur réel + rapport).
|
||||
|
||||
Reference in New Issue
Block a user