docs: align README with fixed behavior
This commit is contained in:
@@ -335,14 +335,75 @@ Règles modélisées :
|
||||
modificateurs d'effets (`StatModifier` avec `target` correspondant) sont
|
||||
appliqués via `resolve_modifiers` (mêmes règles d'empilement que les
|
||||
attaques). Renvoie un `SaveResult` (success, roll, total, dc).
|
||||
- Incantation : chaque `CombatantState` porte `spell_slots_left` (initialisé depuis
|
||||
le champ `spell_slots` du JSON, ex. `{"1": 4, ...}`) ; `_cast_spell` dépense
|
||||
l'emplacement du niveau concerné (`spend_spell_slot`). Le DD vaut `10 + niveau du
|
||||
sort + mod de la caractéristique d'incantation` (`casting_ability`), la sauvegarde
|
||||
est résolue par `resolve_save`, et la RD ne réduit pas les dégâts des sorts
|
||||
(`test_dr_does_not_reduce_spell_damage`). Incanter dans une
|
||||
case menacée provoque des AoO ; tout dégât subi pendant l'incantation impose un
|
||||
jet de concentration dont le DD dépend de la situation (`_concentration_dc`) :
|
||||
`10 + dégâts + niveau` si blessé, `10 + CMB de l'adversaire + niveau` si agrippé,
|
||||
sinon `15 + 2×niveau` (incantation défensive), jet = d20 + niveau + mod de CON
|
||||
(+4 avec le don Combat Casting). `default_policy` choisit soin / offensif / buff via
|
||||
`_find_heal_spell`, `_find_offensive_spell` et `_find_ally_needing_heal`.
|
||||
- Invocation : les créatures invoquées agissent (calées sur la case d'initiative de
|
||||
leur conjurateur via `_build_round_order`) puis expirent après leur `summon_duration`
|
||||
(`_expire_summons` décrémente puis retire).
|
||||
- Conditions effectives : 18 conditions prédéfinies (`conditions.py`). Leurs drapeaux
|
||||
comportementaux sont appliqués par le moteur : `cant_act` (étourdi, paralysé, dazed,
|
||||
endormi) annule le tour ; `cant_attack` (nauséeux) n'autorise qu'une unique action
|
||||
simple et bloque toute attaque / manœuvre ; `staggered` restreint à une action
|
||||
simple (pas d'attaque à outrance) ; `cant_move` bloque le déplacement ;
|
||||
`denies_dex_to_ac` (à plat ventre, paralysé…) fait perdre le bonus de Dextérité à
|
||||
la CA (CA flat-footed). À plat ventre : −4 CA et −4 aux attaques à distance.
|
||||
Agrippé : −4 Dextérité, −2 attaque/CA/Réflexes, sans déplacement ni AoO.
|
||||
- Manœuvres de combat : trip, disarm, bull_rush, sunder, grapple et escape sont
|
||||
modélisées, chacune comme une tentative unique (1d20 + CMB contre CMD) :
|
||||
trip → `prone` ; disarm → `disarmed` (−4 attaque) ; bull_rush → pousse la cible ;
|
||||
grapple → `grappled`/`pinned` ; escape → **une** sauvegarde (bonus statique de la
|
||||
compétence `escape_artist` ou CMB, + un d20) contre CMD — une seule tentative,
|
||||
sans boucle multi-tour. Un sunder endommage l'**arme**, pas les PV : `str_mod + 2`
|
||||
de dégâts à l'arme, qui se brise à 10 d'usure cumulée (`_WEAPON_DURABILITY`) →
|
||||
`disarmed` ; une arme brisée est exclue de `usable_attacks()`. `default_policy` ne
|
||||
choisit pas ces manœuvres (utilisables via `Action(kind="maneuver")` ou une
|
||||
politique personnalisée).
|
||||
- Combat Reflexes : 1 AoO par combattant par round par défaut ; avec le don Combat
|
||||
Reflexes, `1 + mod de Dextérité` AoO par round (`_aoo_allowance`, plancher 1).
|
||||
- Sortilèges du sorcier (witch hex) : le DD vaut `10 + niveau//2 + mod d'Intelligence`
|
||||
(evil_eye, slumber, cackle) ; `cackle` prolonge d'un round les sortilèges déjà
|
||||
actifs, sur une liste instantanée pour ne pas s'étendre à l'infini. Les sorts à
|
||||
zone (AoE) utilisent le DD d'incantation du jeteur et, sur une sauvegarde réussie,
|
||||
ne font que la moitié des dégâts.
|
||||
- Dons : les 8 PJ portent 55 noms de dons uniques ; le chargeur résout chacun d'eux
|
||||
soit vers un effet réel (Power Attack actif, Weapon Focus ciblé par arme, Toughness
|
||||
lié au niveau, dons passifs via `_stat_modifiers`), soit vers un « no-op » tracé en
|
||||
toute transparence (noms décoratifs / de race) — aucun n'est silencieusement ignoré
|
||||
(`tests/test_feats.py`).
|
||||
- Capacités de classe émises par `default_policy`, chacune conditionnée à une ressource
|
||||
`> 0` dépensée dans la méthode d'exécution : `channel_energy` (oni, allié < 50 % ou
|
||||
ennemis groupés à 30 ft), sortilège `evil_eye` (nairda), deed `up_close_and_deadly`
|
||||
(tammara, action bonus +1d6 à ≤ 30 ft), `inspire_courage` + `spell_combat` (misty)
|
||||
— couvertes par `tests/test_class_abilities.py`.
|
||||
- Le Liche (`data/monsters/lich.json`) : aura de terreur (30 ft, Volonté DD 22 ou
|
||||
`shaken` 1 round, au début de son tour) et contact paralysant (au toucher,
|
||||
`paralysé` 1d4+1 rounds), RD 15/magie+contondant ; son JSON porte un dictionnaire
|
||||
`spell_slots` sans clés dupliquées.
|
||||
|
||||
Non modélisé (couches `elevation`/`markers` présentes mais non appliquées
|
||||
dans la résolution) :
|
||||
|
||||
- Manœuvres de combat.
|
||||
- Effets mécaniques de hauteur/élévation.
|
||||
- Tailles Large+ (2×2), allonge > 5 ft
|
||||
et actions immédiates hors-tour.
|
||||
- Effets mécaniques de hauteur/élévation (les hauteurs sont validées mais non
|
||||
appliquées au déplacement, à la portée ou à la ligne d'effet).
|
||||
- Tailles Large+ (2×2), allonge > 5 ft (la mêlée reste limitée à une case) et
|
||||
actions immédiates hors-tour (consommeraient le prochain swift).
|
||||
- Parmi les manœuvres, seules trip / disarm / bull_rush / sunder / grapple / escape
|
||||
sont câblées ; les autres (overrun, dirty trick, steal, reposition, drag) ne le
|
||||
sont pas. Une arme brisée ne peut pas être réparée en cours de combat.
|
||||
- Les modificateurs d'effets conditionnés par un `condition=` au runtime ne sont pas
|
||||
évalués (`_modifier_condition_holds` renvoie toujours False, le modificateur est
|
||||
alors exclu) ; la distribution des dégâts par type sur un sort n'envoie que des
|
||||
dégâts simples (sans plage « half on save » pour les effets ciblés).
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -360,7 +421,9 @@ dans la résolution) :
|
||||
des créatures) dans `resolve_attack`.
|
||||
- `combat.py` — `CombatEngine` déterministe : initiative, actions, résolution
|
||||
des attaques (couvert, ligne d'effet, pénalités de portée), états de vie,
|
||||
transcripts, politique par défaut.
|
||||
incantation (emplacements, concentration par situation, AoO d'incantation),
|
||||
invocation, manœuvres, capacités de classe, transcripts, politique par
|
||||
défaut.
|
||||
- `effects.py` — système d'effets : `StatModifier` (modificateur de
|
||||
caractéristique avec type de bonus) et `resolve_modifiers` (application des
|
||||
règles d'empilement PF1e). Types de bonus cumulables (dodge, racial, trait,
|
||||
@@ -373,25 +436,28 @@ dans la résolution) :
|
||||
deafened, flat-footed, prone, stunned, paralyzed, nauseated, dazed,
|
||||
staggered, frightened, panicked, cowering). Les modificateurs sont intégrés
|
||||
à `resolve_attack` (attaque, dégâts, CA) et `resolve_save` (jets de
|
||||
sauvegarde) via `_stat_modifiers` qui collecte effets + conditions.
|
||||
sauvegarde) via `_stat_modifiers` qui collecte effets + conditions ; les
|
||||
drapeaux comportementaux (`cant_act`, `cant_attack`, `cant_move`, staggered,
|
||||
etc.) sont appliqués par le moteur pendant le tour.
|
||||
- `abilities.py` — dons et capacités : `AbilitySpec` (dataclass avec
|
||||
catégorie feat/class/racial/trait), dons passifs prédéfinis (Weapon Focus,
|
||||
Toughness, Iron Will, Great Fortitude, Lightning Reflexes, Alertness,
|
||||
Point-Blank Shot, Dodge) et don actif Power Attack (−X attaque / +2X dégâts,
|
||||
X = BAB//4 + 1, mêlée uniquement, declared as free action at turn start via
|
||||
`state.power_attack`). Les modificateurs sont collectés par
|
||||
catégorie feat/class/racial/trait) et fabriques de dons (Weapon Focus ciblé
|
||||
par arme, Toughness lié au niveau, Power Attack actif −X attaque / +2X
|
||||
dégâts, X = BAB//4 + 1, mêlée uniquement, declared as free action at turn
|
||||
start via `state.power_attack`). Les modificateurs sont collectés par
|
||||
`_stat_modifiers` avec filtrage par arme (`weapon_filter`). Le champ
|
||||
`features` de `Combatant` contient les aptitudes permanentes.
|
||||
`features` de `Combatant` contient les aptitudes permanentes ; le chargeur
|
||||
(`loaders/combatant.py`) résout les noms de dons des fiches vers des
|
||||
`AbilitySpec` réels ou des « no-op » tracés.
|
||||
- `spells.py` — sorts : `SpellSpec` (dataclass avec niveau, école, portée,
|
||||
sauvegarde, DC de base) et types d'effets (`DamageEffect`, `ConditionEffect`,
|
||||
`HealEffect`, `BuffEffect`). Portées PF1e (personal, touch, close, medium,
|
||||
long) calculées par `spell_range_ft`. Chargeur JSON (`load_spell`,
|
||||
`load_spell_registry`) pour `data/spells/`. Le moteur intègre les sorts via
|
||||
`Action(kind="cast_spell")` et `_cast_spell` : résolution de sauvegarde,
|
||||
application des dégâts (avec RD et demi-dégâts sur sauvegarde réussie),
|
||||
conditions, soins (plafonnés à hp_max) et buffs. Le champ `spells` de
|
||||
`Combatant` contient les noms de sorts connus ; le `spell_registry` est
|
||||
passé au `CombatEngine`.
|
||||
application des dégâts (demi-dégâts sur sauvegarde réussie ; la RD ne
|
||||
s'applique pas aux dégâts de sorts), conditions, soins (plafonnés à hp_max)
|
||||
et buffs. Le champ `spells` de `Combatant` contient les noms de sorts connus ;
|
||||
le `spell_registry` est passé au `CombatEngine`.
|
||||
- `metrics.py` — statistiques en forme fermée : `win_rate`, `win_rate_sigma`,
|
||||
`win_rate_band` (bande 3σ bornée à [0, 1]).
|
||||
- `runner.py` — `EncounterSpec`/`Side`, `build_states` (placement en zone +
|
||||
@@ -408,7 +474,7 @@ dans la résolution) :
|
||||
La gate de validation complète (tests + lint + types) :
|
||||
|
||||
```bash
|
||||
uv run pytest -q # 377 tests
|
||||
uv run pytest -q # 472 tests
|
||||
uv run ruff check src tests
|
||||
uv run basedpyright src # mode strict
|
||||
```
|
||||
@@ -421,17 +487,16 @@ uv run basedpyright src # mode strict
|
||||
|
||||
## Feuille de route
|
||||
|
||||
- **Phase 1** — magie et états : sorts modélisés comme effets paramétrés,
|
||||
conditions, manœuvres de combat, dons et capacités de classe. Flanquement,
|
||||
attaques à outrance, attaques d'opportunité, charge, retraite et pas de
|
||||
placement sont déjà modélisés. Le système d'effets (`effects.py`), les
|
||||
règles d'empilement des bonus, les jets de sauvegarde (`resolve_save`), le
|
||||
système de conditions (`conditions.py`), les dons passifs et actifs
|
||||
(`abilities.py`) et les sorts (`spells.py` + base JSON `data/spells/`)
|
||||
sont en place. Le chargeur Foundry extrait automatiquement les dons
|
||||
(feats, traits, racial) vers des `AbilitySpec` pré-définis et les noms
|
||||
de sorts vers `Combatant.spells`.
|
||||
- **Phase 1** — magie et états : largement réalisée. Sorts par emplacements
|
||||
(`spells.py` + base `data/spells/`, 11 sorts), DD et sauvegardes, RD non
|
||||
appliquée aux dégâts de sorts, concentration par situation, invocation,
|
||||
conditions effectives (`conditions.py`), manœuvres de combat, dons (55 noms
|
||||
résolus, `abilities.py` + chargeur) et capacités de classe (channel energy,
|
||||
hex, deeds, inspire courage / spell combat) sont en place et documentés dans
|
||||
« Règles implémentées ». Restent hors du socle (voir « Non modélisé ») : les
|
||||
hauteurs/élévations, tailles Large+, allonges > 5 ft, actions immédiates
|
||||
hors-tour, et le reste des manœuvres.
|
||||
- **Phase 2** — couche tactique LLM : stratégies en langage naturel traduites
|
||||
en politiques, balayage de matrices de positionnement.
|
||||
en politiques, balayage de matrices de positionnement. (Non implémentée.)
|
||||
- **Phase 3** — rapporteur LLM local : agrégation des statistiques et
|
||||
rédaction du rapport d'équilibrage en français.
|
||||
rédaction du rapport d'équilibrage en français. (Non implémentée.)
|
||||
|
||||
Reference in New Issue
Block a user