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
+5
View File
@@ -95,6 +95,10 @@ class SpellSpec:
range: SpellRange = "medium"
save: SaveType = "none"
base_dc: int = 10
# PF1e (CRB, Magic — Ranged Touch Attacks): the spell requires a ranged
# touch attack to deliver (rays). Auto-hit spells (Magic Missile) and
# area/save-only spells keep this False.
requires_ranged_touch: bool = False
cast_time: CastTime = "standard"
effects: tuple[SpellEffect, ...] = ()
description: str = ""
@@ -304,6 +308,7 @@ def load_spell(path: str | Path) -> SpellSpec:
range=range_raw,
save=save_raw,
base_dc=_as_int(data.get("base_dc", 10)),
requires_ranged_touch=bool(data.get("requires_ranged_touch", False)),
cast_time=cast_time,
effects=effects,
description=_as_str(data.get("description", "")),