fix(rules): apply the power attack penalty to melee touch attacks

PF1e (CRB Power Attack): "take a -1 penalty on all melee attack rolls ...
The bonus damage does not apply to touch attacks." A melee touch (kind
"touch", no range increment) is a melee attack, so _power_attack_amt now
rides its attack and confirmation rolls; the +2X/+3X damage bonus stays
gated on kind == "melee". Ranged attacks and rays are unaffected. The
test that pinned the old melee-weapon-only simplification was rewritten to
lock the corrected behavior; README and abilities.py updated.

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 23:35:07 +02:00
parent 8a8b813889
commit a2825e73cc
4 changed files with 30 additions and 11 deletions
+3 -2
View File
@@ -139,8 +139,9 @@ DODGE = AbilitySpec(
),
)
# Power Attack: active feat — declare at turn start, -X atk / +2X dmg on all
# melee attacks this turn, where X = BAB//4 + 1 (min 1). No passive modifiers;
# Power Attack: active feat — declare at turn start, -X atk on all melee
# attack rolls (melee touch included) and +2X dmg on melee WEAPON damage only
# (never touch), where X = BAB//4 + 1 (min 1). No passive modifiers;
# the engine checks for this feat and the ``power_attack`` flag on state.
POWER_ATTACK = AbilitySpec(
name="Power Attack",
+10 -2
View File
@@ -2283,7 +2283,12 @@ class CombatEngine:
if self._is_melee_attack(weapon) and self._is_helpless(target):
atk_mods.append(StatModifier(target="attack", value=_HELPLESS_MELEE_BONUS))
attack_mod = resolve_modifiers(atk_mods)
pa_amt = self._power_attack_amt(attacker) if weapon.kind == "melee" else 0
# PF1e (CRB Power Attack): the -X penalty rides ALL melee attack rolls
# — a melee touch (no range increment) is a melee attack. The damage
# bonus it buys stays weapon-only (see _resolve_hit_damage).
pa_amt = (
self._power_attack_amt(attacker) if self._is_melee_attack(weapon) else 0
)
fd_penalty = (
_FIGHT_DEFENSIVELY_ATTACK_PENALTY if attacker.fight_defensively else 0
)
@@ -2445,7 +2450,10 @@ class CombatEngine:
# weapon used in two hands, or a primary natural with 1.5x Strength
# (modeled via the weapon's `two_handed` flag). The off-hand /
# secondary-natural halving (-50%, +1X) is NOT modeled: two-weapon
# fighting is not in the engine (documented simplification).
# fighting is not in the engine (documented simplification). The
# bonus damage never applies to touch attacks (CRB), which is why
# this gate is kind == "melee" while the -X penalty also rides melee
# touch rolls.
if weapon.kind == "melee":
damage += pa_amt * (3 if weapon.two_handed else 2)
if crit: