docs(phase2): record the runner entry points (P0.4) and replay (P0.5)

run_encounter gains llm_policy and per-run policy_factory (mutually
exclusive with policy/llm_policy); replay.py logs every (observation,
action) keyed by call_seq to JSONL and replay() reproduces the recorded
battle bit-identically (wins/draws/rounds/transcript) with mismatch,
exhausted, duplicate and out-of-order guards. P0 frontier complete: the
recorded run replays identically.
This commit is contained in:
2026-09-22 08:45:52 +02:00
parent 6c6de16e60
commit 26c5b51b98
+30 -5
View File
@@ -357,10 +357,35 @@ hex/deed si ressource présente, défense totale, ready/delay) ; `swift`/`free`/
`full_round` exclus ; liste bornée et triée. **Pas de second moteur de `full_round` exclus ; liste bornée et triée. **Pas de second moteur de
légalité** : le moteur reste l'arbitre. Test : `tests/test_phase2_candidates.py`. légalité** : le moteur reste l'arbitre. Test : `tests/test_phase2_candidates.py`.
### Runner et rejeu (P0.4 + P0.5 — commits 334f8f9, 6c6de16)
`run_encounter` accepte désormais `llm_policy=` (observation-only ou legacy,
transmise à chaque `CombatEngine`) et `policy_factory=` (constructeur de
`Policy` **par run**, appelé une fois par indice dans l'ordre ; `None` pour un
run = repli `default_policy`). Les trois entrées (`policy`, `llm_policy`,
`policy_factory`) sont mutuellement exclusives (erreur claire). Test :
`tests/test_phase2_runner.py` (factory 1 appel/run dans l'ordre, repli,
exclusivité, une bataille complète pilotée par `llm_policy`).
`replay.py` : `ReplayRecord(call_seq, observation, action)` + `ReplayLog`
(spec + seed + round_cap + appels) sérialisé en **JSONL** (clé `call_seq`),
`observation_key()` (digest déterministe, insensible à la collapse
tuple/liste de JSON), et `replay(log) -> LLMPolicy` qui renvoie les actions
enregistrées dans l'ordre des `call_seq`, avec gardes : observation
divergente (`ReplayMismatchError`), log épuisé (`ReplayExhaustedError`),
`call_seq` dupliqué ou hors ordre, JSONL invalide. **Un run enregistré se
rejoue à l'identique** (wins/draws/rounds/transcript — aussi depuis un log
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).
### État du plan ### État du plan
- **P0.0 (D1)** ✅, **P0.1 (`schema_version`)** ✅, mesures ✅, `objectives.py` ✅, - **P0.0 (D1)** ✅ · **P0.1 (`schema_version`)** ✅ · mesures ✅ ·
`collect_runs` ✅, **P0.2** (vue compacte) ✅, **P0.3** (`candidates`) ✅. `objectives.py` ✅ · `collect_runs` ✅ · **P0.2** (vue compacte) ✅ ·
- Restent : **P0.4** (`llm_policy` sur le runner + `policy_factory`), **P0.5** **P0.3** (`candidates`) ✅ · **P0.4** (`llm_policy` + `policy_factory`) ✅ ·
(replay `call_seq`) → puis P1 (client + politiques), P3 (optimiseur), P4 **P0.5** (ReplayLog `call_seq` + `replay()` identique) ✅.
(cartographie complète), P5 (fournisseur + rapport). - **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).