diff --git a/README.md b/README.md index 1b989f5..f1cd9b6 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,10 @@ vers la fiche Foundry d'origine. ## Règles implémentées et limites de la Phase 0 +> Le suivi détaillé des écarts avec le SRD — corrigés, hors périmètre et +> résiduels assumés — est consigné dans +> [`docs/conformance-srd.md`](docs/conformance-srd.md). + Règles modélisées : - 1 naturel = échec automatique ; 20 naturel = touche + menace de critique. diff --git a/docs/conformance-srd.md b/docs/conformance-srd.md new file mode 100644 index 0000000..155ac04 --- /dev/null +++ b/docs/conformance-srd.md @@ -0,0 +1,145 @@ +# 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` | 1491 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 + +### 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 + +> En cours. Les constats de la passe 2 sont ajoutés ci-dessous à mesure que les +> audits reviennent (voir § 6, journal des audits). + +| # | Constat | Sév. | Correctif (commit) | +|---|---|---|---| +| — | *(à compléter)* | — | — | + +--- + +## 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 | +| **Acrobatics « Tumbling »** (traverser une case ennemie sans AoO) | Le moteur n'a pas de système de compétences | +| **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 %)** | `SpellSpec` n'a pas de composante verbale ; il faudrait encoder les composantes sur les 108 sorts | +| **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 3D / vol par-dessus les obstacles** | La vitesse de vol est un budget au sol ; pas de maniabilité ni de survol du terrain | +| **`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 | +|---|---|---| +| **Course — durée liée à la Constitution, don Run, armure lourde (×3)** | Le SRD limite la course à `CON` tours puis impose un jet de Constitution DD 10, et réduit la vitesse de course en armure lourde ; le moteur applique toujours ×4 | Assumé : aucun scénario courant ne course plus de quelques tours | +| **Dégâts non létaux** | Absents du modèle de PV | Assumé : aucune source de dégâts non létaux dans les données | +| **Armes à éclaboussures / dégâts d'éclaboussure** | Absents (pas d'objet concerné dans les données) | Assumé | +| **Sort « harmless » sur cible consentante** | Le moteur roule tout de même une sauvegarde (consommation de RNG) ; l'issue du soin n'en dépend pas | Assumé : fidélité de déterminisme, pas d'effet observable | +| **Réglages du liche non SRD** (pv 100 vs 111, Force, Intelligence, initiative, aura de terreur 30 ft/DD 22 vs 60 ft/DD 18) | Réglages d'équilibrage du scénario doré | Assumé : conservés pour ne pas casser la fiche dorée | +| **Dés énergétiques non multipliés par un critique** (contact du liche) | Règle moteur globale (exceptions CRB « Critical Hits ») | Assumé : cohérent avec les autres attaques | +| **`selective_channeling` non modélisé** | Le canal d'énergie affecte toutes les créatures du type visé dans la zone | Assumé (documenté au README) | + +--- + +## 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_e764efda`, `bg_9fdc610e`, `bg_f5829c68`, `bg_b289533d` | en cours | En cours de triage | +| 3+ | Bestiaire/PJ (données), résolution d'attaque/défense, économie d'action, sorts restants | — | à lancer | À faire | + +--- + +## 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.