From 9210a545895166c3c3797cc6ed16d5717a14a7fe Mon Sep 17 00:00:00 2001 From: ctan Date: Sun, 20 Sep 2026 22:29:05 +0200 Subject: [PATCH] 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 --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 76e5025..2d52153 100644 --- a/README.md +++ b/README.md @@ -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 ```