docs(rules): document 3D flight over ground terrain (airborne view: difficult terrain cost 1, cross non-LoS impassable cells, blocks_los still stops flight; elevation climb still applies; LoE/cover/high-ground unchanged) and the deferred 3D line-of-effect/maneuverability with SRD citations (Movement, Position and Distance; Aiming a Spell); map legend + grid.py notes, test count 1981

This commit is contained in:
2026-09-20 22:29:05 +02:00
parent 2ae86ab29f
commit 9210a54589
+64 -8
View File
@@ -235,6 +235,10 @@ ligne d'effet ⇒ l'attaque est impossible ; couvert ⇒ bonus de CA. Le champ
optionnel `cover_bonus` (`2`, `4` ou `8`) encode le niveau de couvert PF1e
(partiel +2, standard +4, amélioré +8) ; `cover: true` sans `cover_bonus`
vaut couvert standard (+4), idem pour un terrain `blocks_los`.
Une créature **aérienne** (vol ≥ sol, voir « Vitesse de déplacement ») ignore
les coûts de `move_cost` > 1 et **enjambe** les cases `move_cost: null` qui ne
portent **pas** `blocks_los` (fosses, gravats, piliers non bloquants) ; une
case `blocks_los` (barrière solide, hauteur supposée pleine) l'arrête aussi.
Sémantique de `blocks_los` — **ligne d'effet, non ligne de vision** (SRD
« Aiming a Spell — Line of Effect ») : « A line of effect is a straight,
@@ -839,11 +843,43 @@ Règles modélisées :
charge 2×, retraite 2×, course 4×) est le **meilleur entre la vitesse au
sol (`speed_land_ft`) et la vitesse de vol (`speed_fly_ft`)** — une créature
dotée d'une vitesse de vol (harvie : vol 80 vs sol 30, ierlieth : vol 40)
se déplace donc à sa vitesse de vol. Simplification documentée : voler
PAR-DESSUS les obstacles ou le terrain difficile n'est PAS modélisé — la
praticabilité et les coûts de terrain de la grille s'appliquent à tout le
budget (pas de vol 3D ni de maniabilité) ; le snapshot expose ce budget
sous la clé additive `speed_effective_ft`.
se déplace donc à sa vitesse de vol. **Vol au-dessus du terrain (3D)** :
une créature dont le budget vient de sa vitesse de vol (`speed_fly_ft >=
`speed_land_ft`, `_is_airborne`) est **aérienne** et se déplace sur une
« vue aérienne » de la grille (`Grid.airborne_view`) : le terrain difficile
au sol (coût > 1) ne la ralentit pas (coût 1) et elle **enjambe** les cases
infranchissables au sol (fosses, eau, gravats, piliers — `move_cost: null`
sans `blocks_los`), conformément au SRD « Movement, Position, and Distance »
(Terrain and Obstacles) : « Flying and incorporeal creatures are not
hampered by difficult terrain » et « Flying and incorporeal creatures are
able to avoid most obstacles ». **Exception** : une case qui **bloque la
ligne d'effet** (`blocks_los` — mur/pilier, barrière solide supposée de
hauteur pleine) **arrête aussi le vol** (elle reste infranchissable dans la
vue aérienne). La règle des coins (diagonale) suit la même vue : un
aérien peut passer en diagonale au ras d'une fosse (SRD « Measuring
Distance » : « You can also move diagonally past other impassable
obstacles, such as pits ») mais pas au ras d'un coin de mur. Les portiers
de course/charge/pas de placement (« You can't run or charge across
difficult terrain », « movement isn't hampered by difficult terrain ») ne
bloquent pas un aérien, puisque son déplacement n'est pas gêné par le
terrain. Le coût de **montée** (élévation) s'applique aussi au vol : la
couche `elevation` encode des **marches** (hauteurs de sol), non des
hauteurs de mur ou de falaise, et n'est donc pas survolable (simplification
documentée). La **ligne d'effet**, le **couvert** et l'**avantage de
hauteur** restent calculés sur la grille réelle (inchangée par le vol) :
la géométrie de combat n'est pas affectée par le statut aérien. Les
créatures non aériennes (`speed_fly_ft` absent, ou vol < sol) sont bit-par
identiques (elles paient le terrain et ne traversent pas les obstacles) ;
les transcripts dorés (arène ouverte, sol uniforme) sont inchangés. Le
snapshot expose le budget sous la clé additive `speed_effective_ft`.
**Hors périmètre (vol 3D incomplet)** : la **ligne d'effet 3D** (un vol à
l'altitude X passe au-dessus d'un mur de hauteur Y, franchit le rebord
d'une falaise) n'est **pas** modélisable — la carte ne porte aucune hauteur
de mur ni de falaise (seule la hauteur de sol `elevation` existe, et
`blocks_los` est un booléen « barrière solide », pas une hauteur) ; la
**maniabilité de vol** (turn/pitch, chute en fin de tour, `hover`) et les
autres vitesses de déplacement (natation, escalade, creusement) ne sont pas
modélisées.
Destination explicite : `Action(kind="move", destination=(r, c))` et
`Action(kind="5foot_step", destination=(r, c))` déplacent le combattant vers
la case `(r, c)` désignée (monter au couvert, quitter un défilé, se
@@ -894,7 +930,11 @@ Règles modélisées :
critique (règle du high ground). Conformance vérifiée (SRD Table 8-5
« Attack Roll Modifiers ») : « On higher ground — Melee +1, Ranged +0 » —
le bonus est à juste titre réservé aux attaques de mêlée, les attaques à
distance ne le reçoivent pas. La ligne d'effet reste 2D (pas de LoS 3D).
distance ne le reçoivent pas. La ligne d'effet reste 2D (pas de LoS 3D) :
la couche `elevation` encode des hauteurs de **sol** (marches), non de murs
ou falaises — survol aérien du terrain au sol modélisé (voir « Vitesse de
déplacement »), mais la ligne d'effet 3D selon l'altitude reste hors
périmètre faute de données de hauteur de mur.
- Actions immédiates hors-tour : politique de réaction optionnelle
`reaction_policy(engine, state, event)` (défaut : aucune) ; sur événement
(`attack_resolved`, `movement_step`, `spell_cast`, `damage_taken`) une
@@ -2078,6 +2118,20 @@ plutôt que bricolées) :
taille — une créature Huge (3×3) est simplement bloquée par tout endroit où
son footprint ne rentre pas ; ni le squeeze payant, ni la réduction de
footprint dans les couloirs ne sont modélisés.
- **Vol 3D — ligne d'effet par l'altitude hors périmètre** : le survol du
terrain au sol est modélisé (voir « Vitesse de déplacement » : terrain
difficile à coût 1, enjambement des obstacles non bloquants, barrières
`blocks_los` infranchissables, SRD « Movement, Position, and Distance ») ;
restent hors périmètre la **ligne d'effet 3D** (un vol à une altitude
donnée passe AU-DESSUS d'un mur de hauteur finie ou franchit le rebord
d'une falaise) et la **maniabilité de vol** (tourner/monter/plonger, chute
en fin de mouvement, don Hover) : la carte ne porte **aucune hauteur de
mur ni de falaise** — seule la hauteur de **sol** (`elevation`, marches)
existe, et `blocks_los` est un booléen « barrière solide » (SRD « Aiming a
Spell — Line of Effect »), pas une hauteur — donc « altitude du vol contre
hauteur de l'obstacle » n'est pas exprimable dans les données ; la ligne
d'effet reste strictement 2D et une barrière `blocks_los` est supposée de
hauteur pleine (elle arrête aussi le vol).
- **Passage à travers une créature (3 catégories d'écart) — approximations** :
la règle est modélisée (traversée + AoO d'entrée, voir « Mouvement ») et le
test d'Acrobatics « Tumbling » est désormais modélisé (voir « Acrobatics —
@@ -2167,7 +2221,9 @@ Limites de sorts (conformance D6-D9, documentées plutôt que bricolées) :
(dimensions, caractères connus, zones, déploiement, markers).
- `grid.py` — grille 5-10-5 : `distance`, `step_cost` (terrain + élévation),
règle des coins (`diagonal_allowed`), Dijkstra `reachable` avec ou sans
budget.
budget ; `airborne_view` = vue de la grille pour les créatures aériennes
(terrain difficile/coûteux → coût 1, cases infranchissables non bloquantes
→ traversables, `blocks_los` reste bloquant).
- `los.py` — ligne d'effet et couvert par la règle des coins : gate de ligne
d'effet dans `weapon_for`, bonus de couvert par niveau (+2/+4/+8 terrain via
`cover_bonus`, +4 couvert mou des créatures) dans `resolve_attack`.
@@ -2273,7 +2329,7 @@ Limites de sorts (conformance D6-D9, documentées plutôt que bricolées) :
La gate de validation complète (tests + lint + types) :
```bash
uv run pytest -q # 1970 tests
uv run pytest -q # 1981 tests
uv run ruff check src tests
uv run basedpyright src # mode strict
```