fix(rules): require a ranged touch attack for ray spells

PF1e CRB (Magic - Ranged Touch Attacks): spells delivered by ray require a
ranged touch attack (BAB + Dex vs touch AC, no range increment) to hit a
hostile target; a miss wastes the slot. SpellSpec gains requires_ranged_touch,
set on the 8 SRD-confirmed rays (Ray of Frost, Ray of Enfeeblement, Scorching
Ray, Acid Arrow, Acid Splash, Disrupt Undead, Shard of Chaos, Spear of Purity).
Magic Missile and area spells are unaffected. Simplification (documented): no
size modifier / Weapon Finesse on the roll.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-09-11 20:19:03 +02:00
parent 24af1687ab
commit 6f784d3369
12 changed files with 168 additions and 7 deletions
+16 -2
View File
@@ -745,7 +745,20 @@ Règles modélisées :
`resolve_attack`) ; en cas d'échec le sort n'a aucun effet (l'emplacement
est brûlé). Simplifications documentées : Weapon Finesse et les autres
bonus situationnels de jet de contact ne sont pas modélisés. Une cible
volontaire (soi ou un allié du même camp) ne demande aucun jet. Un sort ciblé avec sauvegarde fait pleins dégâts
volontaire (soi ou un allié du même camp) ne demande aucun jet. Sorts à
contact à distance (CRB « Magic — Ranged Touch Attacks ») : un sort dont le
JSON déclare `requires_ranged_touch: true` (les rayons du SRD — Ray of
Frost, Ray of Enfeeblement, Scorching Ray, Acid Arrow, Acid Splash, Disrupt
Undead, Shard of Chaos, Spear of Purity) impose une attaque de contact à
distance contre une cible **hostile** (hors effet de zone) — d20 + BAB + mod
de **Dextérité** du lanceur contre la CA de contact de la cible (amputée du
modificateur de Dextérité si la cible perd son bonus de Dextérité, même
chemin que `resolve_attack`) ; la portée du sort est une valeur absolue, il
n'y a donc **pas** de pénalité par incrément de portée ; en cas d'échec le
sort n'a aucun effet (l'emplacement est brûlé). Simplifications documentées
(miroir du contact de mêlée) : le modificateur de taille et Weapon Finesse
ne sont pas modélisés. Magic Missile (touche automatiquement), les sorts de
zone et les sorts à sauvegarde simple ne sont pas concernés. Un sort ciblé avec sauvegarde fait pleins dégâts
sur un échec et **aucun dégât sur un succès** (la sauvegarde annule) ; le
demi-dégât sur sauvegarde réussie ne s'applique que là où l'effet le déclare
(`half_on_save: true` pour les rayons/lignes/cônes, `AreaEffect.save_for_half`
@@ -1127,7 +1140,8 @@ la lecture mais non appliquée dans la résolution.
(`loaders/combatant.py`) résout les noms de dons des fiches vers des
`AbilitySpec` réels ou des « no-op » tracés.
- `spells.py` — sorts : `SpellSpec` (dataclass avec niveau, école, portée,
sauvegarde, DC de base, temps d'incantation `cast_time`) et types d'effets
sauvegarde, DC de base, exigence de contact à distance `requires_ranged_touch`
(rayons), temps d'incantation `cast_time`) et types d'effets
(`DamageEffect`, `ConditionEffect`,
`HealEffect`, `BuffEffect`). Portées PF1e (personal, touch, close, medium,
long) calculées par `spell_range_ft`. Chargeur JSON (`load_spell`,