fix(rules): helpless Dex-0 AC adds exactly -5 on flat-footed base; touch base strips full Dex mod when helpless (no negative-Dex double count)

This commit is contained in:
2026-09-16 21:32:19 +02:00
parent e0bf484394
commit cc9fba3b52
2 changed files with 79 additions and 11 deletions
+65 -3
View File
@@ -328,8 +328,9 @@ def test_stunned_is_not_helpless() -> None:
def test_helpless_dex_zero_replaces_a_native_dex_penalty() -> None:
# A Dex 8 (-1) helpless target: the -1 penalty gives way to the Dex-0 -5
# (adjustment -5 - (-1) = -4 on top of the flat-footed base).
# A Dex 8 (-1) helpless target: the flat-footed base already excludes
# ALL Dex (bonus and penalty alike), so effective Dex 0 adds exactly -5.
# The old formula added -5 - (-1) = -4, double-counting the Dex penalty.
eng = _engine(
[_combatant("atk"), _combatant("def", ac=14, dex=8)],
[(0, 0), (0, 1)],
@@ -337,7 +338,68 @@ def test_helpless_dex_zero_replaces_a_native_dex_penalty() -> None:
)
_add_condition(eng._states[1], PARALYZED)
result = eng.resolve_attack(eng._states[0], eng._states[1], _melee("atk"))
assert result.ac == 10 # 14 - 4
assert result.ac == 9 # 14 - 5
def _touch(cid: str) -> AttackSpec:
return AttackSpec(
id=f"{cid}-touch",
name="touch",
kind="touch",
attack_bonus=5,
damage=[DamageComponent(formula=parse_dice("1d6"), types=["cold"])],
)
def test_helpless_positive_dex_melee_ac_is_flat_footed_minus5() -> None:
# Dex 16 (+3): flat-footed base drops the +3 already; Dex-0 adds -5.
eng = _engine(
[_combatant("atk"), _combatant("def", ac=16, dex=16)],
[(0, 0), (0, 1)],
[10, 4],
)
_add_condition(eng._states[1], PARALYZED)
result = eng.resolve_attack(eng._states[0], eng._states[1], _melee("atk"))
assert result.ac == 11 # 16 - 5
def test_helpless_positive_dex_touch_ac_strips_dex_then_minus5() -> None:
# Dex 16 (+3): touch base 16 includes the +3; effective Dex 0 strips it
# (16 - 3) and adds -5 -> 8.
eng = _engine(
[_combatant("atk"), _combatant("def", ac=16, dex=16)],
[(0, 0), (0, 1)],
[10, 4],
)
_add_condition(eng._states[1], PARALYZED)
result = eng.resolve_attack(eng._states[0], eng._states[1], _touch("atk"))
assert result.ac == 8
def test_helpless_negative_dex_touch_ac_strips_penalty_then_minus5() -> None:
# Dex 8 (-1): touch base 14 includes the -1; effective Dex 0 replaces it
# (14 + 1) and adds -5 -> 10.
eng = _engine(
[_combatant("atk"), _combatant("def", ac=14, dex=8)],
[(0, 0), (0, 1)],
[10, 4],
)
_add_condition(eng._states[1], PARALYZED)
result = eng.resolve_attack(eng._states[0], eng._states[1], _touch("atk"))
assert result.ac == 10
def test_flat_footed_negative_dex_touch_keeps_penalty_without_minus5() -> None:
# Control: a merely flat-footed (NOT helpless) Dex 8 target loses only a
# positive Dex bonus — the -1 stays on touch AC and no Dex-0 -5 applies.
eng = _engine(
[_combatant("atk"), _combatant("def", ac=14, dex=8)],
[(0, 0), (0, 1)],
[10, 4],
)
_add_condition(eng._states[1], CONDITIONS_BY_NAME["flat_footed"])
result = eng.resolve_attack(eng._states[0], eng._states[1], _touch("atk"))
assert result.ac == 14
# ── B6: nauseated — single move action only, no casting ──────────────────────