Files
pf1e-simulator/docs/conformance-srd.md
T
ctan 7e4a9f5edf docs(rules): conformance journal round 15 - nonlethal, splash, skills and flight (out of scope -> modeled)
Brings in the last four out-of-scope mechanics: the nonlethal damage track
(unarmed strikes, minimum damage), splash weapons (direct + scatter),
combat skills (skill checks, Acrobatics Tumbling, Stealth/opposed Perception)
and 3D flight over ground terrain (vertical line of effect deferred). Updates
the out-of-scope and residual tables to drop the now-modeled items and marks
the round-2 disposition tables as historical. Gate 1981.
2026-09-20 22:35:55 +02:00

458 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Conformité SRD Pathfinder 1re édition — journal d'audit
Ce document consigne, de façon exhaustive et vérifiable, l'écart entre les règles
implémentées par le moteur et le SRD Pathfinder 1re édition. Il complète le
`README.md` (qui décrit le comportement attendu) en traçant **ce qui a été
corrigé**, **ce qui est hors périmètre**, et **ce qui reste assumé comme
résiduel**.
## Méthode
1. **Audit read-only par domaine** : chaque passe couvre un domaine étanche
(sorts, mouvement, morts-vivants, dons, manœuvres, conditions, données de
sorts, bestiaire). L'auditeur croise le code avec la base de connaissances
SRD (`pathfinder-kb`, collection `pathfinder-1e-rules`) et cite la règle.
2. **Classement** : `High` (change matériellement l'issue d'un combat),
`Med` (clairement faux, impact étroit), `Low` (donnée / cosmétique).
3. **Correction** : un commit par correctif, gate complète verte avant chaque
commit (`ruff` `select=ALL`, `basedpyright` strict, `pytest`).
4. **Non-régression** : le transcript doré (`tests/test_pc_combat_golden.py`)
n'est réécrit que lorsqu'un correctif SRD change légitimement la sortie,
avec justification ligne à ligne.
5. **Traçabilité** : tout constat non corrigé est classé ici en *hors
périmètre* (choix délibéré) ou *résiduel assumé* (écart connu, non corrigé).
## État de la gate
| Contrôle | Résultat |
|---|---|
| `uv run pytest -q` | 1981 tests verts |
| `uv run ruff check src tests` (`select=ALL`) | conforme |
| `uv run basedpyright src` (strict) | 0 erreur |
| Transcript doré depuis la passe 1 | inchangé |
---
## 1. Écarts corrigés
> Les tableaux de disposition ci-dessous (passe 2) sont **historiques** : les
> constats marqués « à corriger » / « résiduel assumé » ont depuis été corrigés
> ou reclassés dans les passes suivantes (voir les tableaux de commits ci-après).
### Correctifs de la passe 2 (commits)
| Commit | Objet |
|---|---|
| `b03a61d` | Données de sorts : niveaux (`abundant_ammunition`, `comprehend_languages`, `deja_vu` 0→1 ; `create_water` 1→0 ; `enhance_water` 2→1), sauvegardes (`blindness_deafness` Will→Fort ; `enhance_water` none→Fort), RM de `summon_monster_i/ii`, écoles (6 sorts), portées (4 sorts) + pins de registre |
| `317f91a` | *Entangled* : jet de concentration DD 15 + niveau ; `dex_skills` −4 |
| `bfe0b76` | *Exhausted* : −6 Dextérité → −3 CA / −3 Réflexes |
| `6220edc` | *Fatigued* : −2 Dextérité → −1 CA / −1 Réflexes |
| `def8533` | *Nauseated* : privé d'attaques d'opportunité |
| `68a03a3` | *Confused* : privé d'attaques d'opportunité (simplification) |
| `cf8a073` | *Grappled/Pinned* : −2 aux jets de manœuvre (hors grapple/escape) |
| `f31cc88` | *Pinned* : ne peut plus attaquer |
| `4d45409` | Weapon Focus : correspondance insensible à la casse (le don s'applique enfin) |
| `2807f71` | Attaque sournoise : toute dissimulation l'annule |
| `350844f` | *Up Close And Deadly* : réservé aux armes à feu |
| `fdc9127` | *Toughness* : +3 pv sous 4 DV (`max(3, niveau)`) |
| `2b5920c` | *Disarm* : l'arme tombe (`weapon_dropped`) au lieu d'une pénalité −4 |
| `06da519` | *Dirty trick* : liste RAW étendue (`shaken`, `sickened`) |
| `0cccea2` | Manœuvres : succès automatique contre cible incapable |
| `3a5948c` | CMD : plus de double comptage de Dextérité (Tiny, CMB pré-calculé) |
| `e89ea42` | README/capacités : grapple (pin hors périmètre), `pinned`/`nauseated` sans attaque, Weapon Focus par groupe |
> Transcript doré : mis à jour **deux fois**, chaque fois par un correctif SRD
> justifié ligne à ligne dans le docstring du test — `4d45409` (Weapon Focus :
> le +1 transforme le jet 15 en 16 contre CA 16) puis `bee224a` (Shield of
> Faith et Divine Favor portent enfin leurs vrais buffs : `_find_buff_spell`
> retient désormais Divine Favor, index 20, ce qui remonte la CA de tammara et
> réécrit son transcript). Le second correctif déplace la mort du gobelin au
> **round 5** (le « round 4 » du premier correctif est superseded).
### Correctifs des passes 3 et 4 (commits)
| Commit | Objet |
|---|---|
| `e0bf484` | Tir dans une mêlée : exemption mesurée **cible → allié le plus proche** (High) |
| `cc9fba3` | CA d'une cible inerte : −5 exact, plus de double comptage d'une Dextérité négative |
| `fa63829` | `trait` et `racial` non cumulables (APG « Traits », CRB) |
| `1c2663d` | Type non cumulable : meilleur bonus **et** pire pénalité conservés séparément |
| `ae037c9` | Dégâts minimum : 1 point sur une touche réduite sous 1 par la RD |
| `b3a7c37` | Types de bonus `alchemical`/`inherent`/`resistance`/`size` (UM Table 2-7) |
| `feaab5e` | `celestial_wolf` aligné sur le loup du SRD + template céleste (DR/RM) |
| `1c94d48` | `harvie` : Sagesse 14, Talons primaires +2 (1d4 ×2), Morsure secondaire |
| `b9477a2` | `ierlieth` : bonus de dégâts −1, Queue secondaire ajoutée |
| `2b5b39a` | Chevaux : sabots 1d4 marqués naturels, Morsure secondaire de Jeanne |
| `611952c` | `misty` : don « Plot Armor » ajouté en no-op tracé |
| `83bbcae` | Ray of Enfeeblement : type `PenaltyEffect` (pénalité de Force lancée, Fort moitié) au lieu de *fatigued* |
| `79d9ff0` | `drunkards_breath` : cône de 30 ft qui rend *nauseated* (niveau 2, conjuration, Fort annule) — l'ancien contenu était **inventé** (High) |
| `48c816b` | `delay_disease` : niveau 2 conjuration (soins) |
| `27f9629` | Données : `alarm` close, `air_bubble`/`endure_elements` touch, `delay_poison` conjuration, conventions 10 ft/0 ft documentées, `daze` 4 DV |
| `47f563e` | Shield of Faith : +2 CA déflexion (+1 / 6 niveaux, max +5) |
| `bee224a` | Divine Favor : +1 chance attaque/dégâts par 3 niveaux (max +3) — doré mis à jour |
| `6d96779` | Spear of Purity / Shard of Chaos : 1d8 / 2 niveaux (max 5d8), Volonté moitié |
| `a5bd3c5` | Défense totale : plus d'attaques d'opportunité |
| `e253b03` | Charge restreinte à 1× pour un accablé/invalide (et au round de surprise) |
| `980d53d` | Aide : expiration au début du tour de **l'aidant** |
| `a144423` | Défense totale et combat défensif non cumulables |
| `0c3f5fd` | Durées de condition scalées au niveau (hold person, doom, touch of fatigue, vanish) |
### Correctifs des passes 5 et 6 (commits)
| Commit | Objet |
|---|---|
| `d0db208` | *Exalted of the Society* → +1 canalisation/jour (`resource_grants`) ; *Fortified Drinker* → no-op (état « alcool » non modélisé) |
| `6d0ee1b` | *Born Damned* et *Axe to Grind* → no-ops documentés (descripteur « curse » absent ; « menacé par vous seul » inexprimable) |
| `a5ebbec` | *Friendly Face* et *Extremely Fashionable* → no-ops (attitude animale et port de vêtements non modélisés) |
| `b74baf1` | *Lonely* / *Bitter* → vraies penalties (fini les faux +1 Will) |
| `81a09c8` | Métamagie : **ne gonfle plus le DD** — seul Heighten (désormais supporté) |
| `418fdc8` | **Charge retenue** : un contact de mêlée manqué conserve le sort |
| `cec3c5f` | Durée d'invocation **1 round/niveau** (`SummonEffect.duration_rounds_per_level`) |
| `65a5e24` | Combat Casting : +4 retiré du jet *entangled* |
| `22724f5` | Extend Spell double la durée d'invocation |
| `b2739c5` / `1902877` / `17c06e1` | Documentation : échec d'armure, convention `spell_slots`, dissipation `(D)` |
| `2dd3fef` | *Larger Than Life*, *Ambitious*, *Focused Study* → no-ops documentés |
| `7262568` | Traits sylphes d'Esha + noms inventés (*Whispering Wind*, *Like the Wind*, *Fey-touched*, *Pingre*) → no-ops tracés |
| `2df9dd7` | *Scaled Skin* (résistance électricité 5) ; *Maw or Claw* morsure 1d6 |
| `13e4a27` | Couverture de test de tout le lot no-op |
| `d9b590e` | Acid Arrow (cap total 7) ; Ice Slick (+1/niveau, max +10) |
| `56310b1` | Mirror Image (images lancées + miss chance dynamique) ; Bleed (seuil ≤ −1 pv) ; Bane (−1 limité à la peur) |
| `35ce41e` | Owl's Wisdom (+2 Volonté) ; docs affinées des utilitaires différés |
| `b95533d` | Bear's Endurance (+2 pv/DV) ; Aid (moral limité aux sauvegardes de peur) ; Charm Person (portée close) |
| `5250763` | **Ligne** s'arrête au premier obstacle de ligne d'effet |
| `389e160` | **Cône** exige une ligne d'effet depuis son origine |
| `948edc1` | **Burst** n'affecte pas une créature en couvert total par rapport au centre |
| `8774e51` | **Couvert → Réflexes** (moitié du bonus de CA, couvert mou exclu) |
| `a2ca78f` | `blocks_los` documenté comme sémantique **ligne d'effet** (vision seule non modélisée) |
| `beb8421` | High ground +1 **mêlée uniquement** (vérifié conforme, Table 8-5) |
| `fb6f58a` | Règle des coins en diagonale documentée comme plus stricte que le RAW (approximation conservatrice assumée) |
| `9f8655d` | README synchronisé (truncature des lignes, LoE cône/burst, couvert Réflexes) |
| `27ce33b` | Couvert au **corps-à-corps adjacent** : règle « n'importe quelle ligne » du SRD |
| `5e478bf` | Charge et course appliquent la **règle des coins** |
| `9086038` | Charge : terrain difficile testé sur **tout le footprint** (« on ne se déplace qu'à la vitesse du terrain le plus difficile occupé ») |
### Correctifs de la passe 6 (suite) et de la passe 7
| Commit | Objet |
|---|---|
| `792cec1` | Les créatures **mourantes mais vivantes** occupent leur case (couvert mou + gabarits de zone) |
| `778d6a8` | Un **aveugle** ne peut ni charger, ni courir, ni retraiter |
| `2e468ba` | Cône/ligne pointé : plus d'exigence de ligne d'effet vers le point visé (bursts seulement) |
| `1e2de7f` | Cône/ligne : **tout le footprint** du lanceur est exclu de son propre gabarit |
| `24e208e` | `square_size_ft ≠ 5` rejeté au chargement (géométrie figée à 5 ft) |
| `2e2873f` | **Canal de soin** : le soin n'est plus compté comme `damage_dealt` |
| `7fefce4` | `run_encounter` ne **plante plus** quand une invocation apparaît (accumulateurs à la demande) |
| `0ad627c` | Saignement, dégâts récurrents et soin-sur-morts-vivants comptés dans `damage_taken` |
| `9459ade` | `downgraded:immediate_swift` ajouté au vocabulaire `ACTION_RESULT_REASONS` |
| `2ee19d3` | Formulation « snapshot figé » corrigée (les dicts internes sont des copies mutables) |
| `ba5eb4c` | Déploiement : deux camps sur la **même lettre de zone** rejetés |
| `9a087a4` | Exemples du README **rafraîchis** contre le moteur réel (Quick start 96,2 % ; exemple vérifié 33,0 %) |
### Correctifs de la passe 8 — vérification indépendante
Une passe de vérification adversariale a confirmé que les 15 correctifs High/Med
échantillonnés sont **réellement implémentés**, puis a trouvé des écarts
**jamais documentés** (politique par défaut, dés, outillage), tous corrigés :
| Commit | Objet |
|---|---|
| `31d47ae` | *Spell Combat* : garde `_has_slot` (sans emplacement, la policy ne provoque plus l'attaque à −2) |
| `bdf15aa` | Soin de la policy : contrôle **portée + ligne d'effet** (fini le tour perdu sur un contact hors portée) |
| `e8b7a73` | `parse_dice("d1"/"1d1")` lève bien `DiceParseError` (plus de `ValidationError` pydantique) |
| `f93c55b` | *Up Close and Deadly* émis seulement si un tir d'arme à feu suit (fini le grit gaspillé) |
| `c23fc36` | Seuil « < 50 % » exact (`hp*2 < hp_max`) |
| `6f01a8e` | Mode soin du canal : seuls les **vivants** comptent (un allié mort-vivant ne le déclenche plus) |
| `238e5f2` | Hex : portée **close scalée** au niveau (25 ft + 5 / 2 niveaux) au lieu d'un 25 ft figé |
| `1b43f4e` | README : `data/pcs/*.json` sont **canoniques** ; `foundry.py` est un outil one-shot incomplet (ne pas régénérer sans réappliquer les correctifs) |
### Correctifs de la passe 9 — résiduels assumés (lots 1-3)
| Commit | Objet |
|---|---|
| `ea83529` | **Course** bornée par le score de **CON** (jet de CON DD 10 par round au-delà, échec → arrêt + repos 10 rounds) ; **don Run** (+4 au jet, conserve la Dextérité à la CA) ; armure lourde ×3 reclassée hors périmètre (pas de champ d'armure) |
| `4665a1c` | Sorts **« harmless »** : plus de jet de sauvegarde inutile sur cible consentante (drapeau `harmless` posé sur les 29 sorts SRD « (harmless) ») |
| `9f0d862` | Bursts **Bless/Bane centrés sur le lanceur** (`centered_on_caster` piloté par les données) |
| `5fe2b72` | **Selective Channeling** modélisé (exclusion par la policy, plafonnée au modificateur de Charisme, sans effet sans le don) |
| `4f8a131` | Fiche du **liche alignée sur le KB** (pv 111, For 10, Int 22, init +2, aura de terreur 60 ft/DD 18) — doré re-pinné (2 lignes, toutes deux SRD-correctes) |
| `5e7436b` | **Invocations** : identifiants désambiguïsés (chacune son emplacement de stats) |
| `e6021a6` | **`healing_done`** exposé par combattant et dans le rapport (« soins prodigués ») |
| `d347dde` | **Terrain difficile multi-cases en `move`** : le pas reflète le terrain le plus difficile sous tout le *footprint* |
| `d5ad3c3` | Un **cône/trait** ne perd plus de longueur à cause des cases du lanceur effleurées |
| `b775d46` | Origine des **bursts** (centre-à-centre vs intersection) : **différé** et documenté précisément (nécessite une coordonnée d'intersection dans le contrat) |
### Correctifs de la passe 10 — sorts utilitaires résiduels (lot 4)
| Commit | Objet |
|---|---|
| `08f9af9` | **Guidance** : nouveau type `OneShotEffect` (+1 compétence one-shot sur un unique jet d'attaque, non appliqué au jet de confirmation) — implémente le variant attaque du SRD |
| `1238c40` | **Resistance** : +1 résistance aux jets de sauvegarde (1 min) ; `_find_buff_spell` ignore désormais les cantrips sans emplacement (évite de préempter les buffs de niveau et de dégrader la policy) |
| `a8a65b1` | **Virtue** : 1 pv temporaire (track `temp_hp` consommé avant les vrais pv) |
| `f788613` | **Stabilize** : `StabilizeEffect` qui stabilise automatiquement une créature mourante |
| — | `spiritual_weapon` (attaque flottante persistante) et `saving_finale` (reroll immédiat) restent **différés** (mécanique porteuse absente) |
### Correctifs de la passe 11 — alignement (sorti du hors-périmètre)
| Commit | Objet |
|---|---|
| `f9e51e3` | Champ **`alignment`** sur `Combatant` (9 valeurs PF1e validées, défaut `N`) + dérivées `is_lawful/is_chaotic/is_good/is_evil` ; alignements renseignés pour tous les monstres et PJ (source KB/feuilles) ; exposé dans l'observation |
| `6a18d45` | Type **`AlignmentGate`** sur les dégâts : cible immunisée / neutre (moitié, sans condition) / pleine (dégâts + condition si sauvegarde ratée) ; *Shard of Chaos* (chaotique/loyal, condition `staggered`) et *Spear of Purity* (bon/mauvais, condition `blinded`) |
| `3be22b9` | Vocabulaire de conditions **`attacker_is_evil/lawful/good/chaotic`** câblé à l'attaquant réel ; **Protection from Evil/Law** (+2 CA déflexion et +2 sauvegardes contre l'alignement opposé) et variantes communales |
### Correctifs de la passe 12 — composantes & échec d'armure (sortis du hors-périmètre)
| Commit | Objet |
|---|---|
| `1f8f068` | Moteur : `components` sur `SpellSpec` (V/S/M/F/DF validés), `armor_spell_failure` + `arcane_caster` sur `Combatant` ; jets d'échec **conditionnels** (sourd + composante verbale → 20 % ; arcanique + somatique + armure → ASF d100), exposés dans l'observation |
| `91666d8` | Encodage des composantes SRD sur **107/108 sorts** (le 108e n'a pas de ligne Composants au KB) |
| `f6292f8` | `arcane_caster` / `armor_spell_failure` posés depuis les feuilles (liche, Thrune, misty, nairda arcaniques) |
| `9f72948` | README synchronisé |
### Correctifs de la passe 13 — objets / inventaire (sortis du hors-périmètre)
| Commit | Objet |
|---|---|
| `74f5d64` | **Sunder réel** : statistiques d'objet par arme (dureté/PV SRD, +2/+2 par point d'amélioration) ; dégâts réels de l'attaque moins la dureté ; arme *broken* à ≤ moitié des PV, détruite à 0 ; **arme cassée utilisable à −2** avec critique sur 20 naturel ×2 (réconcilié avec le *misfire*/*quick_clear*) |
| `d0f7f3a` | **`carried_items`** (objets portés, non tenus) sur le modèle + données depuis les feuilles ; **Steal** transfère réellement un objet porté ; **récupération** d'une arme lâchée (pickup au désarmement à mains nues) |
### Correctifs de la passe 14 — audit du rendu du transcript & des formats
Audit read-only de la dernière zone non vérifiée (`cli.py` rendu du transcript, `map.py`, `loaders/combatant.py`) : **14 bogues confirmés** (4 Med, 10 Low), tous corrigés.
| Commit | Objet |
|---|---|
| `9656ae8` | Clé `spell_slots` non numérique → `CombatantLoadError` (au lieu d'un `ValueError` brut qui plantait la CLI) |
| `b8e9629` | `features` non-liste (ou entrée vide/non-chaîne) → erreur propre (au lieu d'une perte silencieuse) |
| `9adc87c` | `level` normalisé de façon cohérente (fini le *Toughness* calculé au niveau 1 pour un niveau 4) |
| `a8c7e37` | Ressources validées de façon cohérente avant fusion des dons |
| `130087a` | Carte : rejet d'une couche `zones` sans `deployment` |
| `de9309c` | CLI : rejet d'un `--side` dupliqué |
| `e9c2a63` | Désambiguïsation d'ids : le suffixe saute les ids déjà pris |
| `10f811e` | `range_increment_ft` strictement positif (0 provoquait une division par zéro) |
| `c46e636` | `--round-cap` ≥ 1 |
| `205ddec` | Clés de légende à un seul caractère |
| `bce0a59` | Lignes vides dans une couche → erreur ; cellules d'espace final conservées |
| `53c6dc0` | Ids de combattant rejetant `": "` (qui cassaient le parsing du transcript) |
| `ee22f75` | Clés YAML dupliquées rejetées (SafeLoader dédié) |
| `dd0b42b` | `move_cost` omis documenté explicitement comme infranchissable |
### Correctifs de la passe 15 — hors-périmètre : non-létal, éclaboussures, compétences, vol
| Commit | Objet |
|---|---|
| `cba7bbf` | **Piste de dégâts non létaux** séparée ; *staggered* à non-létal = PV, *inconscient* au-delà ; immunité morts-vivants/artificiels ; le soin au-dessus du non-létal rend conscient |
| `83c6e54` | Sources non létales : **unarmed strikes** non létaux par défaut ; **minimum de dégâts** = 1 **non-létal** exact |
| `9cfcd67` / `2e60bbb` / `a1b1c29` / `678a4d8` | **Armes à éclaboussures** : touche directe = dégâts + 1 aux créatures à 5 ft ; échec = dispersion (d8) ; énergie (résistance/immunité), pas de RD ni de critique ; RNG inchangé pour les autres armes |
| `e0fb5be` | **Jet de compétence** général (d20 + rangs + car. + classe +3 si rangs + ACP) |
| `61686b5` | **Acrobaties « Tumbling »** : esquive d'AoO (DC = CMD, +5 si plus grand, +2 par adversaire), demi-vitesse, échec = l'AoO part et le déplacement s'arrête |
| `2c40994` | **Discrétion / Perception opposée** : action de se cacher, `hidden_from` par observateur (concealment total + +2/déni Dex), brisée en attaquant/courant |
| `2ae86ab` | **Vol au-dessus du terrain** : un volant ignore le terrain difficile et franchit les cases infranchissables au sol, sauf les murs `blocks_los` (pleine hauteur) ; LoE/couvert/high ground inchangés |
| `9210a54` | Documentation du vol et de la **LoE verticale/falaise différée** (pas de hauteur de mur dans les données) |
> Vérifiés conformes par l'audit : largeur d'affichage (accents NFC/NFD, CJK), repli en ligne brute sans perte, parsing des lignes conformes, capture du premier combat (`seed+0`), sémantique des nuls, normalisation du couvert (`cover_bonus`, `cover: true` → +4, `blocks_los` → +4), validations de déploiement/markers, ordre `(ligne, colonne)`, bande 3σ bornée.
> **Registre des sorts clos** : les 35 derniers sorts ont été vérifiés champ par champ contre le KB — **0 divergence** (niveaux, sauvegardes, RM, portées, zones, formules conformes). Seuls 2 sorts sont absents du KB (`create_alcool`, `hidden_diplomacy`) et 1 nit de description subsiste (`detect_poison` mentionne « acid »).
### Passe 1 — sorts de buff, mouvement/grille, morts-vivants, armes naturelles
| # | Constat | Sév. | Correctif (commit) |
|---|---|---|---|
| B1 | *Bless* affectait **toutes** les créatures d'un burst de 20 ft (y compris les ennemis) ; le SRD cible « you and each of your allies within a 50-ft. burst » | **High** | `e43066e` — filtre `AreaEffect.targets` (`all`/`allies`/`enemies`), burst 50 ft, alliés + lanceur |
| B2 | *Bane* était un sort mono-cible ; le SRD cible les **ennemis** dans un burst de 50 ft | Med | `5e3505d` — conversion en `AreaEffect` 50 ft, `targets: "enemies"` |
| B3 | *Bladed Dash* : niveau 1 (au lieu de 2), portée `close` (au lieu de `personal`) | Med | `312f9fd` |
| B4 | *Aid* : niveau 1 (au lieu de 2), portée `long` (au lieu de `touch`) | Med | `312f9fd` |
| B5 | *Doom* : portée `close` (au lieu de `medium`) | Med | `312f9fd` |
| B6 | *Bull's Strength* : sauvegarde `fort` (le SRD n'accorde pas de jet ; valeur neutre corrigée en `will`) | Med | `312f9fd` |
| B7 | *Bear's Endurance* : sauvegarde `fort` (idem) | Med | `312f9fd` |
| B8 | *Mage Armor* absent du registre | Low | `3e19d29` — sort ajouté (niveau 1, conjuration, contact, +4 CA d'**armure**, RM non) + type de bonus `"armor"` non cumulable (registre porté à 108 sorts) |
| C1 | Budget de déplacement : parité des diagonales figée à 0 dans les descentes gloutonnes → la règle 5-10-5 était **violée** (un marcheur vitesse 30 parcourait 6 cases en diagonale au lieu de 4) | Med | `2cc2ba1` — parité propagée (`_best_move_step`, `_descend_path`, `_run_line_path`, `_flee_path`) + `_path_cost_5105` pour la charge |
| C2 | Pas de placement (5 ft) autorisé depuis/vers un terrain difficile | Med | `437507e` — rejet si le footprint courant **ou** la destination touche du terrain difficile |
| C3 | Entrer dans la case d'une créature hostile ne provoquait pas d'AoO | Med | `9a16ca0` — `_check_enter_aoo` câblé dans `_walk_steps` |
| C4 | Traversée d'une case hostile à ≥ 3 catégories d'écart de taille non modélisée | Med | `9a16ca0` — `_can_pass_through` (porte « traversée ») distincte de `_blocked` (porte « fin de mouvement ») |
| C5 | Un combattant **fatigué/épuisé** pouvait courir (seule la charge était bloquée) ; le SRD : « can neither run nor charge » | Low | `7fc7a1d` — garde `cant_charge` ajoutée à `_execute_run` |
| D1 | Les morts-vivants (et les créatures artificielles) mouraient à `hp ≤ -CON` au lieu d'être détruits à `hp ≤ 0` | Med | `59caf67` — seuil de mort à 0, pas de bande d'agonie |
| D2 | Fiche du liche non conforme au bloc SRD : BAB 8 (au lieu de +5), contact +10 (au lieu de +5), `damage_bonus` 5 doublant la formule (dégâts réels 1d8+10 au lieu de 1d8+5), CA 25/14/23 (au lieu de 23/14/21), jets 10/10/14 (au lieu de 6/7/11), `con_score` 14 alors qu'un mort-vivant n'a pas de Constitution | Low | `881f2ef` |
| D3 | Les sorts de soin (`cure`) soignaient les morts-vivants au lieu de les **blesser** | Low | `cad35e2` — dégâts (Volonté pour moitié), ni RD ni stabilisation |
| A1 | Armes naturelles : itératifs de BAB accordés à tort et absence de la pénalité −5 des attaques secondaires | Med | `e5691bd` — champs `natural`/`secondary` (une seule attaque, secondaires −5, Multiattack −2) |
### Passe 2 — dons/capacités, manœuvres, conditions, données de sorts
Constats d'audit (6 High, ~28 Med, ~45 Low). La colonne *Disposition* indique
le sort réservé : **corrigé** (commit), **à corriger**, **hors périmètre** (non
modélisable proprement) ou **résiduel assumé** (écart connu, conservé).
#### 2a. Données de sorts (vérifiées une à une contre le KB)
| Constat | Sév. | Disposition |
|---|---|---|
| `abundant_ammunition` niveau 0 → **1** (toutes classes) | High | corrigé (passe 2) |
| `comprehend_languages` niveau 0 → **1** | High | corrigé (passe 2) |
| `deja_vu` niveau 0 → **1** | High | corrigé (passe 2) |
| `blindness_deafness` sauvegarde `will` → **`fort`** | High | corrigé (passe 2) |
| `create_water` niveau 1 → **0** (clerc/druide) | Med | corrigé (passe 2) |
| `enhance_water` niveau 2 → **1**, portée personal → **touch** | Med | corrigé (passe 2) |
| `deja_vu` portée personal → **medium** | Med | corrigé (passe 2) |
| `summon_monster_i` / `summon_monster_ii` RM true → **false** | Med | corrigé (passe 2) |
| `divine_favor` — effet SRD (+1 chance attaque/dégâts par 3 niveaux) absent | Med | résiduel assumé |
| `owls_wisdom` — effet SRD (+4 Sagesse) absent | Med | résiduel assumé |
| `shield_of_faith` — effet SRD (+2 CA déflexion) absent | Med | résiduel assumé |
| `weapon_of_awe` — effet SRD (+2 dégâts sacrés) absent | Med | résiduel assumé |
| Écoles fausses : `arcane_mark` divination→**universal**, `color_spray` enchantment→**illusion**, `dancing_lights` conjuration→**evocation**, `deja_vu` divination→**enchantment**, `enhance_water` abjuration→**transmutation**, `abundant_ammunition` transmutation→**conjuration** | Low | corrigé (passe 2) |
| Portées fausses : `dancing_lights` close→**medium**, `create_water` medium→**close** | Low | corrigé (passe 2) |
| `enhance_water` sauvegarde none → **fort** (inoffensif) | Low | corrigé (passe 2) |
| Effets absents (sorts utilitaires) : `guidance`, `resistance`, `protection_from_evil`, `sanctuary`, `virtue`, `shard_of_chaos`, `spear_of_purity`, `spiritual_weapon` | Low | hors périmètre |
| *Faux positifs* (conformes) : `web` RM, `flurry_of_snowballs` RM, `detect_fiendish_presence` niveau, `scorching_ray` projectiles (max 3), `make_whole`/`restoration_lesser` temps d'incantation (`long`) | — | conformes |
| *Non vérifiable* (le KB omet la ligne RM) : `shield`, `read_magic`, `comprehend_languages` (RM) | — | indéterminé |
| Portées « pieds fixes » (15/30/50/60/120 ft) approchées par les catégories close/medium/long pour les cônes, lignes et bursts (`burning_hands`, `color_spray`, `fear`, `flurry_of_snowballs`, `lightning_bolt`, `bless`, `bane`, `obscuring_mist`, `detect_magic`) | Low | hors périmètre (modèle de portée par catégories) |
#### 2b. Conditions
| Constat | Sév. | Disposition |
|---|---|---|
| *Entangled* : aucun jet de concentration **DD 15 + niveau** à l'incantation | Med | corrigé (passe 2) |
| *Exhausted* : pénalités **−3 CA / −3 Réflexes** manquantes (−6 Dex) | Med | corrigé (passe 2) |
| *Nauseated* : peut porter des **attaques d'opportunité** | Med | corrigé (passe 2) |
| *Confused* : peut porter des attaques d'opportunité (le SRD l'interdit) | Med | corrigé (passe 2) |
| *Grappled* : **−2 aux jets de manœuvre** (sauf grapple/escape) non appliqué | Med | corrigé (passe 2) |
| *Pinned* : peut **attaquer** ; de plus la condition n'est **jamais appliquée** (code mort) | Med | corrigé (f31cc88) / hors périmètre (entretien) |
| *Fatigued* : **−1 CA / −1 Réflexes** manquants (−2 Dex) | Low | corrigé (passe 2) |
| *Fatigued/Exhausted* : pénalité de dégâts appliquée aussi aux armes à distance | Low | résiduel assumé |
| *Entangled* : `dex_skills` −4 manquant (incohérent avec grappled/pinned) | Low | corrigé (passe 2) |
| *Shaken/Sickened/Frightened/Panicked* : pénalités de compétence/caractéristique non modélisées | Low | hors périmètre (pas de tests de compétence) |
| *Frightened/Panicked* : fuite = retraite 2× au lieu de la course 4× ; acculé attend au lieu de se recroqueviller | Low | résiduel assumé |
| *Dazzled* / *Blinded* : malus de Perception (dont opposition) non modélisés | Low | hors périmètre |
| *Deafened* : échec d'incantation 20 % | Low | hors périmètre (déjà documenté) |
| *Stunned/Panicked* : « lâche tout ce qu'elle tient » non modélisé | Low | hors périmètre (pas de modèle d'objet tenu) |
| *Grappled* : « aucune action à deux mains » non modélisé | Low | hors périmètre |
| *Pinned* : composantes somatiques/matérielles non modélisées | Low | hors périmètre |
| *Bleeding* : règle de non-cumul (« le pire effet l'emporte ») non vérifiée | Low | à vérifier |
#### 2c. Manœuvres de combat
| Constat | Sév. | Disposition |
|---|---|---|
| *Grapple* : aucun entretien/pin/dégâts/déplacement ; `pinned` jamais appliqué — **le README « grapple → grappled/pinned » est faux** | High | hors périmètre (README corrigé, e89ea42) |
| *Disarm* : pénalité **−4 synthétique** au lieu du **lâcher d'arme** (le SRD fait tomber l'objet) | High | à corriger |
| *Grappled* : −2 non appliqué aux jets de manœuvre de combat | Med | corrigé (cf. 2b) |
| *Grapple* : plafond arbitraire de 10 rounds ; pas de bonus de +5 d'entretien | Med | hors périmètre |
| *Disarm* : pas d'échec-de-10 → l'attaquant lâche son arme | Med | résiduel assumé |
| *Trip* : `prone` expire en 1 round au lieu de persister jusqu'à `stand_up` | Med | résiduel assumé (README documenté) |
| *Sunder* : dégâts `str_mod + 2` fixes au lieu des dégâts d'arme ; **dureté non modélisée** | Med | résiduel assumé (README documenté) |
| *Sunder* : arme cassée retirée des attaques au lieu de **−2** (SRD : reste utilisable) | Med | résiduel assumé |
| Pas de **succès automatique** contre une cible incapable/inconsciente | Med | à corriger |
| *Reposition* : contrainte d'allonge non appliquée au déplacement | Med | résiduel assumé |
| *Dirty trick* : options **shaken/sickened** manquantes ; pas de retrait par action de mouvement | Med | à corriger |
| *Trip* : créatures sans jambes/volantes non trippables ; +2 par paire de jambes | Low | hors périmètre (pas de traits de créature) |
| *Overrun* : option « se laisser éviter » non modélisée | Low | hors périmètre |
| *Bull rush* : vérifications secondaires contre les créatures sur le passage non modélisées | Low | hors périmètre |
| *Drag* : budget de déplacement de l'attaquant non vérifié | Low | hors périmètre |
| *Disarm* : à mains nues −4, +10 → lâcher des deux objets, attente d'un round | Low | résiduel assumé |
| *Escape* : option « devenir le grappleur » non modélisée | Low | hors périmètre |
| *Grapple* : −4 si pas deux mains libres ; restrictions arme légère/Stealth | Low | hors périmètre (pas de modèle de mains) |
| *CMD* : double comptage de Dextérité latent pour une créature Tiny avec `cmb` pré-calculé | Low | à corriger |
#### 2d. Dons et capacités de classe
| Constat | Sév. | Disposition |
|---|---|---|
| **Weapon Focus (Pistolet) est un don mort** : le filtre de don compare exactement « Pistol » au nom d'arme « Braises d'Automne (pistolet ?) » → le +1 ne s'applique jamais | Med | à corriger |
| Attaque sournoise autorisée contre une **dissimulation partielle** (le SRD bloque dès « concealment » ; le README revendique à tort « total concealment ») | Med | à corriger |
| *Up Close And Deadly* s'applique à **toute** attaque à distance ; le SRD l'exige d'une **arme à feu** | Med | à corriger |
| *Toughness* : +niveau au lieu de **+3 puis +1 par DV au-delà de 3** (faux aux niveaux 1-2 ; données actuelles ≥ 3) | Low | à corriger |
| *Channel Energy* : 2d6 **figés** (pas de progression +1d6 par 2 niveaux) ; le canaliseur est toujours inclus | Low | résiduel assumé |
| *Combat Reflexes* : planché à 1 AoO (le SRD autorise 0 avec un modificateur de Dextérité négatif) | Low | résiduel assumé |
| *Cackle* : étend les `evil_eye` de **tous** les lanceurs, pas seulement les siens | Low | résiduel assumé |
| *Improved Critical* appliqué à **toutes** les armes (SRD : un type d'arme) | Low | résiduel assumé (README documenté) |
| *Inspire Courage* : +1 plat (progression +1 par 4 niveaux non modélisée) | Low | résiduel assumé (README documenté) |
| *Spell Combat* : le coût de pool arcanique est un no-op (misty n'a pas de pool) | Low | sans objet |
---
## 2. Hors périmètre (choix délibéré, documenté)
Ces règles sont **volontairement** non modélisées et signalées dans le
`README.md` (sections « Limites hors périmètre » et « Limites de sorts ») pour
ne pas être confondues avec des bogues.
| Sujet | Raison |
|---|---|
| **Squeeze** (se serrer dans un passage étroit) | La largeur de couloir n'est pas comparée à la taille ; une créature bloquée par son footprint l'est sans option de squeeze payante |
| **Compétences hors combat** (Escalade, Natation, Diplomatie, Sabotage, Connaissances, Art de la magie…) | Seules les compétences utiles au combat sont modélisées (Acrobaties Tumbling, Discrétion/Perception, Évasion) |
| **Créature inerte très grande = obstacle à 2 cases** | Les cases d'une créature inactive ne bloquent pas et ne coûtent pas double |
| **Échec d'incantation du personnage sourd (20 %)** | Modélisé (composante verbale + `deafened`) |
| **Incantation multi-round (`long` : 3 rounds, 10 min, 1 h)** | Non planifiable dans un tour ; jetée du budget (`downgraded:cast_time`) plutôt qu'approximée |
| **Co-occupation au-delà de Tiny vs plus grand** | Deux créatures Small+ ne partagent jamais une case ; plusieurs Tiny sur une case non gérés |
| **Combat à deux armes / arme en main secondaire (×0,5 Force)** | Hors du modèle d'attaque actuel |
| **Vol — LoE verticale / falaise** | Le survol du terrain est modélisé ; la ligne d'effet verticale et les falaises ne le sont pas (les données n'expriment pas de hauteur de mur) |
| **`markers`** (points nommés de la carte) | Validés à la lecture, non appliqués à la résolution |
---
## 3. Résiduels assumés (écart connu, non corrigé)
| Sujet | Nature de l'écart | Décision |
|---|---|---|
| **Origine des bursts** | Mesure **centre-à-centre** au lieu d'**intersection-à-intersection** avec la règle du bord lointain (SRD « Aiming a Spell ») ; l'exactitude exigerait une coordonnée d'intersection dans le contrat d'observation et un choix de coin du lanceur | Différé, documenté précisément (`areas.py`, `_creatures_in_range`) |
| **Course en armure lourde (×3)** | Le modèle n'a pas de champ d'armure ; la course est désormais bornée par la CON et le don Run est modélisé | Hors périmètre (reclassé) |
| **Dégâts non létaux** | **Modélisé** (piste séparée, sources unarmed, minimum de dégâts non létal) | Sorti du hors-périmètre |
| **Armes à éclaboussures** | **Modélisées** (touche directe + éclaboussure, dispersion d8) | Sorties du hors-périmètre |
| **Sorts utilitaires différés** | `spiritual_weapon` (attaque flottante persistante — pas d'attaquant sans porteur dans le moteur) et `saving_finale` (reroll immédiat — pas de crochet de réaction sur un jet de sauvegarde) restent hors d'atteinte ; `guidance`, `resistance`, `virtue` et `stabilize` sont désormais **modélisés** | Différés (raison moteur consignée) |
| **Non vérifiable (KB)** | `create_alcool`, `hidden_diplomacy` absents du KB ; nit de description de `detect_poison` (« acid ») | Indéterminé / nit |
| **Couverture d'audit** | Rendu du transcript et formats de données désormais **audités** (passe 14, 14 bogues corrigés) ; toutes les zones sont couvertes | Couverture complète |
---
## 4. Conformités vérifiées (garde-fous anti-régression)
Constats explicitement contrôlés et **confirmés conformes** :
- **Résistance aux sorts** : le drapeau `spell_resistance` est respecté et
`_check_spell_resistance` court-circuite sans consommer de RNG quand la cible
n'a pas de RM.
- **Description de *Lightning Bolt*** (ligne 120 ft, 1d6/niveau plafonné 10d6,
Réflexes demi) : conforme.
- **Diagonale entrant en terrain difficile** : coût plat de 3 cases, convention
de table explicite dans `grid.py`, cohérente entre Dijkstra et descentes.
- **Terrain mixte** : les coûts de pas s'additionnent sans double comptage.
- **Déplacement d'un personnage aveuglé** : le SRD n'interdit ni le
déplacement, ni la course, ni la charge à un aveugle (seuls la touche, la CA
et les compétences sont affectés) — le constat d'audit initial ne visait pas
une vraie règle.
---
## 5. Journal des audits
| Passe | Domaines | Sessions | Constats | Statut |
|---|---|---|---|---|
| 1 | Sorts (B), Mouvement/grille (C), Morts-vivants (D), Armes naturelles (A) | `bg_9e9e22e6`, `bg_cda707bf`, `bg_ba80396a`, `bg_6a621d4c` | 1 High, 12 Med, ~20 Low | Corrigés + documentés (ci-dessus) |
| 2 | Dons/capacités (E), Manœuvres (F), Conditions (G), Données de sorts (H) | `bg_5314f54c` (re-run), `bg_9fdc610e`, `bg_f5829c68`, `bg_b289533d` + extraction/vérification `bg_e1ccd2da`, `bg_4292da47`, `bg_14d5344a` | 4 High, ~18 Med, ~45 Low | Constats consignés (§ 1 passe 2) ; correctifs data/conditions/dons en cours |
| 3 | Résolution d'attaque & défense, bestiaire/PJ (blocs de stats), économie d'action & tour, sorts restants | `bg_38d99eac`, `bg_8cdcb3e2`, `bg_73e3f878` | 1 High, ~10 Med, ~40 Low | 11 correctifs (`e0bf484`..`611952c`) + Ray of Enfeeblement (`83bbcae`) ; le reste documenté |
| 4 | Sorts restants (champ par champ), dons des 8 PJ vs feuilles Foundry | `bg_29c36b1a` | 1 High, 1 Med, ~30 Low (dont 20 dons) | 11 correctifs (`79d9ff0`..`0c3f5fd`) ; 20 écarts de dons + effets de sorts différés documentés |
| 5 | Sous-système des sorts (invocation, emplacements, métamagie, concentration, livraison) ; cas limites moteur (LoS/LoE, couvert, élévation, terrain, gabarits de zone) | `bg_938bceca`, `bg_27334a51` | 0 High, ~6 Med, ~15 Low | 15 correctifs (`81a09c8`..`9086038`) + documents ; 8 Low moteur résiduels |
| 6 | Résiduels de dons (24) et d'effets de sorts (~13) | `bg_303e14a0`, `bg_151d5a57`, `bg_918e49cf` | 0 High, ~4 Med, ~30 Low | 16 correctifs (`d0db208`..`b95533d`) ; effets non exprimables documentés |
| 7 | Registre des sorts (35 restants, champ par champ) ; moteur : Low documentés ; sous-systèmes runner/métriques/observation/CLI | `bg_0a3e3844`, `bg_2c4d185a`, `bg_cc8f0788` | 2 High, 2 Med, ~8 Low | 12 correctifs (`792cec1`..`9a087a4`) ; **registre des sorts clos (108/108)** |
| 8 | Vérification adversariale (correctifs réels + chasse aux écarts non documentés : `dice`, `rng`, `foundry`, `map`, `default_policy`, transcript) | `bg_d8cba9d1`, `bg_1bac8900` | 15 claims vérifiés réels ; 2 High/Med + ~6 Low nouveaux | 8 correctifs (`31d47ae`..`1b43f4e`) ; reste = résiduels assumés et hors périmètre |
| 9 | Résiduels assumés (lots 1-3) : course/CON+Run, harmless, centrage Bless/Bane, Selective Channeling, liche, invocations, healing_done, terrain multi-cases, cône effleuré | `bg_0a5b0d6b`, `bg_b304c5a3`, `bg_b277ee2d` | ~10 correctifs, 1 différé | 10 correctifs (`ea83529`..`d5ad3c3`), doré re-pinné une fois (liche) |
| 10 | Sorts utilitaires résiduels (lot 4) : guidance, resistance, virtue, stabilize, spiritual_weapon, saving_finale | `bg_9c989e12` | 4 implémentés, 2 différés | 4 correctifs (`08f9af9`..`f788613`) |
| 11 | **Hors périmètre — alignement** : champ + dérivées, Shard of Chaos / Spear of Purity, Protection from Evil/Law (+communales) | `bg_1a3b0faa`, `bg_6980098f`, `bg_07b3d604` | 3 correctifs | Alignement **sorti du hors-périmètre** |
| 12 | **Hors périmètre — composantes & ASF** : composantes sur 107 sorts, sourd 20 %, échec d'armure, `arcane_caster` | `bg_02d4b4e3`, `bg_84baec1d` | 4 correctifs | Composantes/ASF **sortis du hors-périmètre** |
| 13 | **Hors périmètre — objets/inventaire** : Sunder réel (dureté/PV), Steal, récupération d'arme | `bg_f9c69e32`, `bg_44d81a98` | 2 correctifs | Objets/inventaire **sortis du hors-périmètre** |
| 14 | Audit du rendu du transcript + formats (cli/map/loader) | `bg_dc36ad2f` | 14 bogues (4 Med, 10 Low), 0 High | 14 correctifs (`9656ae8`..`dd0b42b`) |
| 15 | **Hors périmètre — non-létal, éclaboussures, compétences, vol** | `bg_388b9b98`, `bg_22a32075`, `bg_137eb485`, `bg_edd042b7` | 15 correctifs | 4 chantiers **sortis du hors-périmètre** |
| 16+ | Reste structurel : LoE verticale/falaise, deux armes, multi-round `long`, squeeze, compétences hors combat, markers | — | — | Documenté (hors périmètre) |
---
## 6. Relancer une passe d'audit
Chaque passe suit le même protocole :
1. Choisir un domaine **non encore audité** (voir le journal § 5).
2. Lancer un auditeur read-only (agent `oracle`) avec la base SRD `pathfinder-kb`
et l'exigence de citer la règle et la ligne de code fautive.
3. Trier chaque constat : **corriger** (si l'impact est matériel et le correctif
propre) ou **classer** en hors périmètre / résiduel assumé (ce document).
4. Un commit par correctif ; gate complète verte ; transcript doré préservé ou
justifié ligne à ligne.
5. Reporter les constats dans les tableaux ci-dessus et la ligne de journal.