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
|
modificateurs d'effets (`StatModifier` avec `target` correspondant) sont
|
||||||
appliqués via `resolve_modifiers` (mêmes règles d'empilement que les
|
appliqués via `resolve_modifiers` (mêmes règles d'empilement que les
|
||||||
attaques). Renvoie un `SaveResult` (success, roll, total, dc).
|
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
|
Non modélisé (couches `elevation`/`markers` présentes mais non appliquées
|
||||||
dans la résolution) :
|
dans la résolution) :
|
||||||
|
|
||||||
- Manœuvres de combat.
|
- Effets mécaniques de hauteur/élévation (les hauteurs sont validées mais non
|
||||||
- Effets mécaniques de hauteur/élévation.
|
appliquées au déplacement, à la portée ou à la ligne d'effet).
|
||||||
- Tailles Large+ (2×2), allonge > 5 ft
|
- Tailles Large+ (2×2), allonge > 5 ft (la mêlée reste limitée à une case) et
|
||||||
et actions immédiates hors-tour.
|
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
|
## Architecture
|
||||||
|
|
||||||
@@ -360,7 +421,9 @@ dans la résolution) :
|
|||||||
des créatures) dans `resolve_attack`.
|
des créatures) dans `resolve_attack`.
|
||||||
- `combat.py` — `CombatEngine` déterministe : initiative, actions, résolution
|
- `combat.py` — `CombatEngine` déterministe : initiative, actions, résolution
|
||||||
des attaques (couvert, ligne d'effet, pénalités de portée), états de vie,
|
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
|
- `effects.py` — système d'effets : `StatModifier` (modificateur de
|
||||||
caractéristique avec type de bonus) et `resolve_modifiers` (application des
|
caractéristique avec type de bonus) et `resolve_modifiers` (application des
|
||||||
règles d'empilement PF1e). Types de bonus cumulables (dodge, racial, trait,
|
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,
|
deafened, flat-footed, prone, stunned, paralyzed, nauseated, dazed,
|
||||||
staggered, frightened, panicked, cowering). Les modificateurs sont intégrés
|
staggered, frightened, panicked, cowering). Les modificateurs sont intégrés
|
||||||
à `resolve_attack` (attaque, dégâts, CA) et `resolve_save` (jets de
|
à `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
|
- `abilities.py` — dons et capacités : `AbilitySpec` (dataclass avec
|
||||||
catégorie feat/class/racial/trait), dons passifs prédéfinis (Weapon Focus,
|
catégorie feat/class/racial/trait) et fabriques de dons (Weapon Focus ciblé
|
||||||
Toughness, Iron Will, Great Fortitude, Lightning Reflexes, Alertness,
|
par arme, Toughness lié au niveau, Power Attack actif −X attaque / +2X
|
||||||
Point-Blank Shot, Dodge) et don actif Power Attack (−X attaque / +2X dégâts,
|
dégâts, X = BAB//4 + 1, mêlée uniquement, declared as free action at turn
|
||||||
X = BAB//4 + 1, mêlée uniquement, declared as free action at turn start via
|
start via `state.power_attack`). Les modificateurs sont collectés par
|
||||||
`state.power_attack`). Les modificateurs sont collectés par
|
|
||||||
`_stat_modifiers` avec filtrage par arme (`weapon_filter`). Le champ
|
`_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,
|
- `spells.py` — sorts : `SpellSpec` (dataclass avec niveau, école, portée,
|
||||||
sauvegarde, DC de base) et types d'effets (`DamageEffect`, `ConditionEffect`,
|
sauvegarde, DC de base) et types d'effets (`DamageEffect`, `ConditionEffect`,
|
||||||
`HealEffect`, `BuffEffect`). Portées PF1e (personal, touch, close, medium,
|
`HealEffect`, `BuffEffect`). Portées PF1e (personal, touch, close, medium,
|
||||||
long) calculées par `spell_range_ft`. Chargeur JSON (`load_spell`,
|
long) calculées par `spell_range_ft`. Chargeur JSON (`load_spell`,
|
||||||
`load_spell_registry`) pour `data/spells/`. Le moteur intègre les sorts via
|
`load_spell_registry`) pour `data/spells/`. Le moteur intègre les sorts via
|
||||||
`Action(kind="cast_spell")` et `_cast_spell` : résolution de sauvegarde,
|
`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),
|
application des dégâts (demi-dégâts sur sauvegarde réussie ; la RD ne
|
||||||
conditions, soins (plafonnés à hp_max) et buffs. Le champ `spells` de
|
s'applique pas aux dégâts de sorts), conditions, soins (plafonnés à hp_max)
|
||||||
`Combatant` contient les noms de sorts connus ; le `spell_registry` est
|
et buffs. Le champ `spells` de `Combatant` contient les noms de sorts connus ;
|
||||||
passé au `CombatEngine`.
|
le `spell_registry` est passé au `CombatEngine`.
|
||||||
- `metrics.py` — statistiques en forme fermée : `win_rate`, `win_rate_sigma`,
|
- `metrics.py` — statistiques en forme fermée : `win_rate`, `win_rate_sigma`,
|
||||||
`win_rate_band` (bande 3σ bornée à [0, 1]).
|
`win_rate_band` (bande 3σ bornée à [0, 1]).
|
||||||
- `runner.py` — `EncounterSpec`/`Side`, `build_states` (placement en zone +
|
- `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) :
|
La gate de validation complète (tests + lint + types) :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run pytest -q # 377 tests
|
uv run pytest -q # 472 tests
|
||||||
uv run ruff check src tests
|
uv run ruff check src tests
|
||||||
uv run basedpyright src # mode strict
|
uv run basedpyright src # mode strict
|
||||||
```
|
```
|
||||||
@@ -421,17 +487,16 @@ uv run basedpyright src # mode strict
|
|||||||
|
|
||||||
## Feuille de route
|
## Feuille de route
|
||||||
|
|
||||||
- **Phase 1** — magie et états : sorts modélisés comme effets paramétrés,
|
- **Phase 1** — magie et états : largement réalisée. Sorts par emplacements
|
||||||
conditions, manœuvres de combat, dons et capacités de classe. Flanquement,
|
(`spells.py` + base `data/spells/`, 11 sorts), DD et sauvegardes, RD non
|
||||||
attaques à outrance, attaques d'opportunité, charge, retraite et pas de
|
appliquée aux dégâts de sorts, concentration par situation, invocation,
|
||||||
placement sont déjà modélisés. Le système d'effets (`effects.py`), les
|
conditions effectives (`conditions.py`), manœuvres de combat, dons (55 noms
|
||||||
règles d'empilement des bonus, les jets de sauvegarde (`resolve_save`), le
|
résolus, `abilities.py` + chargeur) et capacités de classe (channel energy,
|
||||||
système de conditions (`conditions.py`), les dons passifs et actifs
|
hex, deeds, inspire courage / spell combat) sont en place et documentés dans
|
||||||
(`abilities.py`) et les sorts (`spells.py` + base JSON `data/spells/`)
|
« Règles implémentées ». Restent hors du socle (voir « Non modélisé ») : les
|
||||||
sont en place. Le chargeur Foundry extrait automatiquement les dons
|
hauteurs/élévations, tailles Large+, allonges > 5 ft, actions immédiates
|
||||||
(feats, traits, racial) vers des `AbilitySpec` pré-définis et les noms
|
hors-tour, et le reste des manœuvres.
|
||||||
de sorts vers `Combatant.spells`.
|
|
||||||
- **Phase 2** — couche tactique LLM : stratégies en langage naturel traduites
|
- **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
|
- **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