docs: document enriched observation snapshot and add LLM demo test

This commit is contained in:
2026-09-06 16:50:42 +02:00
parent bfb8d0a30c
commit d2cd6f0702
2 changed files with 187 additions and 15 deletions
+33 -13
View File
@@ -455,19 +455,39 @@ Règles modélisées :
`spell_slots` sans clés dupliquées.
- Snapshot d'observation (contrat LLM, Phase 2) : avant chaque appel de
politique, le moteur construit une `Observation` sérialisable (`to_dict()` →
JSON) figée — champs par combattant (id, side, pos, occupied_cells,
hp/hp_max, active/dead, conditions noms+durées, resources_left,
weapon_broken/weapon_stolen, grappled_with, moved_this_turn, power_attack),
par paire combattant actif × cible (distance_ft, weapon_for, range_penalty,
can_charge, can_step5_to_attack, has_line_of_effect, flanking) et couches
terrain/élévation de la grille. Les API de décision exposées (items L1-L8)
JSON) figée — le snapshot est la SEULE source d'info de la LLM (aucune
référence `engine.*` exposée, collections copiées/figées). Une politique LLM
se branche via `CombatEngine(..., llm_policy=...)` (`LLMPolicy(observation,
state) -> tuple[Action, ...]`). Schéma enrichi (E1-E5) :
- Par combattant : id, side, pos, occupied_cells, hp/hp_max, active/dead,
conditions (noms + rounds_left), resources_left (+ `spell_slot_{level}`),
weapon_broken/weapon_stolen, grappled_with, moved_this_turn, power_attack,
name, level, size, speed_land_ft, speed_fly_ft, ac {total, touch,
flat_footed}, saves {fort, ref, will}, cmb, cmd, attacks (référentiel
complet : id, name, kind, attack_bonus, damage, damage_bonus, crit_range,
crit_mult, reach_ft, range_increment_ft, count, firearm — y compris les
armes broken/stolen), features (noms des capacités), spells (résumés
enrichis du registre : name, level, school, range_ft, save, dc, summary).
- Par paire combattant actif × cible : target_index, distance_ft, weapon_for,
range_penalty, can_charge, can_step5_to_attack, has_line_of_effect,
flanking, movement_cost_ft, target_threatens_me, enemies_threatening_me.
- Top-level : round, acting_combatant, combatants, pairs, terrain, elevation,
threatened (grille bool des cases menacées par les ennemis),
terrain_semantics (un dict par type de terrain : type, move_cost,
blocks_los, cover).
Les helpers publics du moteur (L5) sont désormais embarqués EN DONNÉES dans
le snapshot (`threatened`, `terrain_semantics`, `movement_cost_ft`,
`target_threatens_me`, `enemies_threatening_me`) — une `LLMPolicy` ne peut
pas appeler `engine.*`, elle lit leurs résultats. Mapping des clés
`resources_left` (vérifié par grep sur `spend_resource`/`resources_left[` :
`channel_energy`, `hexes`, `grit`, `arcane_pool`, `bardic_rounds`, plus
`spell_slot_{level}` ajouté par le snapshot). Limites documentées : pas de
grille `reachable_ft` budget=speed (délibérément rejetée —
`movement_cost_ft` par paire + `terrain_semantics` + `speed_land_ft`
suffisent), pas de couvert par paire (déductible via `has_line_of_effect` +
`terrain_semantics` + positions). Les API de décision exposées (items L1-L8)
s'y retrouvent : `direction`/`trick_condition`/`weapon_id` sur `Action`,
`can_charge` → `ChargeResult`, zones menacées `threatened_squares`/
`enemies_threatening`, coûts `reachable_ft`/`movement_cost_ft`, kinds
`retrieve_weapon`/`repair_weapon`. Une politique LLM se branche via
`CombatEngine(..., llm_policy=...)` (`LLMPolicy(observation, state) ->
tuple[Action, ...]`) ; le snapshot est la SEULE source d'info de la LLM
(aucune référence `engine.*` exposée, collections copiées/figées).
`can_charge` → `ChargeResult`, kinds `retrieve_weapon`/`repair_weapon`.
Non modélisé : la couche `markers` (points nommés de la carte) est validée à
la lecture mais non appliquée dans la résolution.
@@ -555,7 +575,7 @@ la lecture mais non appliquée dans la résolution.
La gate de validation complète (tests + lint + types) :
```bash
uv run pytest -q # 664 tests
uv run pytest -q # 714 tests
uv run ruff check src tests
uv run basedpyright src # mode strict
```