feat(combat): add concealment miss chance, sneak attack, and Precise Shot

This commit is contained in:
2026-09-10 10:22:50 +02:00
parent a1c43e83af
commit 97f4429c2b
7 changed files with 682 additions and 40 deletions
+35 -7
View File
@@ -410,16 +410,41 @@ Règles modélisées :
chemin de la RD. La résistance énergétique s'applique au dégât final (même étape
que la RD), y compris aux dégâts de sorts et aux composantes énergétiques des
armes.
- Concealment (chance de rater) : les conditions `blurred` (20 %, sort flou) et
`displaced` (50 %, concealment total) portent un champ `miss_chance` ; quand la
cible en porte une, après la touche et la confirmation de critique, le moteur
tire un d100 : si `d100 ≤ miss_chance`, l'attaque rate complètement (aucun
dégât, pas de critique, deed non consommé — journalisé « concealment »). Une
cible avec concealment ne peut pas être la victime d'une attaque sournoise.
- Attaque sournoise : un combattant avec la capacité « Sneak Attack » inflige des
d6 de dégâts de précision ((niveau + 1) // 2 dés : 1d6 au niveau 1, 2d6 au
niveau 3…) si la cible est flanquée (attaque de mêlée uniquement) ou privée de
son bonus de Dextérité à la CA (condition `denies_dex_to_ac` ; mêlée, ou
attaque à distance à ≤ 30 ft). Les dés sont jetés avec le RNG du moteur et
ajoutés APRÈS le multiplicateur de critique (la précision n'est jamais
multipliée) et AVANT la RD et les résistances énergétiques ; jamais contre une
cible avec concealment.
- Tirer dans une mêlée / Precise Shot : toute attaque à distance (kind `ranged`
ou `touch` avec incrément) contre une cible engagée dans une mêlée subit −4 au
jet d'attaque ET au jet de confirmation (la cible est « engagée » quand un
ennemi actif autre que l'attaquant, doté d'une arme de mêlée, menace ses
cases) ; la pénalité tombe à −2 si la cible ou l'adversaire menaçant est à
≥ 10 ft (2 cases) de l'attaquant. Le don « Precise Shot » annule la pénalité.
Les attaques de mêlée ne sont pas concernées.
- Couvert : seul le couvert standard (+4, terrain et couvert mou des créatures)
est modélisé ; le couvert partiel (+2) et le couvert amélioré (+8) ne sont PAS
modélisés.
- 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
- Conditions effectives : 23 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.
la CA (CA flat-footed) ; `miss_chance` (flou 20 %, déplacement 50 %) impose la
chance de rater du concealment. À 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, escape,
overrun, dirty trick, steal, reposition et drag sont modélisées, chacune
@@ -496,7 +521,10 @@ Règles modélisées :
spells (résumés enrichis du registre : name, level, school, range_ft,
save, dc, summary, half_on_save, save_negates (E4)),
spell_resistance (int), resistances {type: montant}, immunities
(tuple triée des types immunisés).
(tuple triée des types immunisés), sneak_dice (dés de précision disponibles :
(niveau + 1) // 2 avec « Sneak Attack », sinon 0), engaged_in_melee (bool —
une cible engagée dans une mêlée, signal pour le tir dans la mêlée et
l'attaque sournoise).
- 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.
@@ -504,9 +532,9 @@ Règles modélisées :
threatened (grille bool des cases menacées par les ennemis),
terrain_semantics (un dict par type de terrain : type, move_cost,
blocks_los, cover), condition_definitions (C2 : tuple trié de (nom,
{mods, flags, speed_mult}) — définitions complètes des 21 conditions de
CONDITIONS_BY_NAME pour que la LLM interprète les drapeaux et
modificateurs).
{mods, flags, speed_mult, miss_chance}) — définitions complètes des 23
conditions de CONDITIONS_BY_NAME pour que la LLM interprète les drapeaux,
les modificateurs et les chances de rater (concealment)).
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
@@ -563,7 +591,7 @@ la lecture mais non appliquée dans la résolution.
enhancement…) gardent la valeur la plus élevée. Les pénalités suivent les
mêmes règles. Fondation pour sorts, conditions, dons et capacités de classe.
- `conditions.py` — conditions PF1e : `Condition` (dataclass avec
modificateurs + flags comportementaux) et 18 conditions prédéfinies
modificateurs + flags comportementaux + `miss_chance`) et 23 conditions prédéfinies
(shaken, sickened, fatigued, exhausted, entangled, dazzled, blinded,
deafened, flat-footed, prone, stunned, paralyzed, nauseated, dazed,
staggered, frightened, panicked, cowering). Les modificateurs sont intégrés