feat(combat): apply cumulative -2 range penalties per increment

This commit is contained in:
2026-08-17 22:49:50 +02:00
parent b748980423
commit f67cd017e4
3 changed files with 145 additions and 21 deletions
+20 -14
View File
@@ -72,10 +72,10 @@ players : 3 combatants
monsters : 2 combatants
1000 combats simulés (seed 1, plafond 100 rounds)
Victoires players : 100.0% (1000) — bande 3σ : [100.0%, 100.0%]
Victoires monsters : 0.0% (0) — bande 3σ : [0.0%, 0.0%]
Victoires players : 98.1% (981) — bande 3σ : [96.8%, 99.4%]
Victoires monsters : 1.9% (19) — bande 3σ : [0.6%, 3.2%]
Nuls : 0.0% (0)
Rounds moyens : 5.8
Rounds moyens : 6.6
```
## Utilisation de la CLI
@@ -130,9 +130,11 @@ confiance à 3σ calculée en forme fermée (modèle binomial) :
immobiles, camps inaccessibles…).
Exemple vérifié — 1 gobelin (players) contre 2 orcs (monsters), 1000 runs,
seed 1 : players 55,7 %, bande 3σ [51,0 % ; 60,4 %], 0 nuls, 13,1 rounds moyens.
seed 1 : players 49,6 %, bande 3σ [44,9 % ; 54,3 %], 0 nuls, 11,4 rounds moyens.
Sans ligne de visée depuis la zone de départ, l'archer gobelin doit contourner
le mur central avant de tirer — c'est ce qui coûte des rounds.
le mur central avant de tirer ; une fois la ligne de visée gagnée, il tire de
loin avec la pénalité de portée au lieu de s'approcher — c'est ce qui équilibre
le face-à-face à 1 contre 2.
## Formats de données
@@ -228,9 +230,11 @@ ligne d'effet ⇒ l'attaque est impossible ; couvert ⇒ bonus de +4 CA.
}
```
Champ `range_increment_ft` (optionnel, uniquement pour `kind: ranged`) : portée
maximale de tir — au-delà, l'arme n'est pas utilisable. `damage_bonus` et `dr`
sont optionnels. Deux monstres d'exemple sont fournis : gobelin et orc.
Champ `range_increment_ft` (optionnel, uniquement pour `kind: ranged`) :
incrément de portée en pieds. La pénalité de portée vaut 2 par incrément
complet au-delà du premier ; l'arme reste utilisable jusqu'à 10 incréments,
au-delà elle n'est pas sélectionnable. `damage_bonus` et `dr` sont optionnels.
Deux monstres d'exemple sont fournis : gobelin et orc.
### Fiches de personnages (PJ)
@@ -274,15 +278,17 @@ Règles modélisées :
- Couvert (règle des coins) : la cible gagne +4 CA sur la touche et la
confirmation de critique ; le couvert octroyé par les créatures n'est pas
modélisé.
- Distance : l'arme doit être à portée (range_increment_ft) ; aucune pénalité
de portée au-delà du premier incrément.
- Distance : pénalité cumulative de 2 par incrément de portée complet au-delà
du premier, appliquée au jet d'attaque et à la confirmation de critique ;
l'arme ranged reste utilisable jusqu'à 10 incréments. Les armes de jet
(5 incréments max) ne sont pas distinguées des armes à projectiles.
Non modélisé en Phase 0 (couches `elevation`/`markers` présentes mais non
appliquées dans la résolution) :
- Sorts, jets de sauvegarde, conditions et états.
- Attaques d'opportunité, flanquement, manœuvres de combat.
- Pénalités de portée (distance) et couvert mou des créatures.
- Couvert mou des créatures.
- Effets mécaniques de hauteur/élévation.
- Tailles Large+ (2×2), allonge > 5 ft, itératifs de BAB, économie d'action
complète (charge, pas de placement…).
@@ -301,8 +307,8 @@ appliquées dans la résolution) :
- `los.py` — ligne d'effet et couvert par la règle des coins : gate de ligne
d'effet dans `weapon_for`, bonus de couvert +4 CA dans `resolve_attack`.
- `combat.py``CombatEngine` déterministe : initiative, actions, résolution
des attaques (couvert, ligne d'effet), états de vie, transcripts, politique
par défaut.
des attaques (couvert, ligne d'effet, pénalités de portée), états de vie,
transcripts, politique par défaut.
- `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 +
@@ -318,7 +324,7 @@ appliquées dans la résolution) :
La gate de validation complète (tests + lint + types) :
```bash
uv run pytest -q # 182 tests
uv run pytest -q # 189 tests
uv run ruff check src tests
uv run basedpyright src # mode strict
```