fix(rules): compute CMD from Strength and Dexterity directly
PF1e (SRD Combat Maneuvers): CMD = 10 + BAB + Strength modifier + Dexterity modifier + special size modifier. The Tiny-or-smaller Dex-for-Str substitution is CMB-only, but _maneuver_cmd reused _maneuver_cmb for the base, so Tiny/Diminutive/Fine creatures got 10 + BAB + 2xDex + size and dropped Strength (live bug on the Tiny PC ierlieth). The CMD base is now computed with str_mod directly; CMB keeps its substitution. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -4159,7 +4159,15 @@ class CombatEngine:
|
||||
def _maneuver_cmd(self, target: CombatantState) -> int:
|
||||
if target.combatant.cmd:
|
||||
return target.combatant.cmd
|
||||
target_cmb = self._maneuver_cmb(target)
|
||||
# PF1e (SRD Combat Maneuvers): "CMD = 10 + Base attack bonus +
|
||||
# Strength modifier + Dexterity modifier + special size modifier" —
|
||||
# the Tiny-or-smaller Dex-for-Str substitution is CMB-only, so the
|
||||
# ability component is computed here instead of via _maneuver_cmb.
|
||||
target_base = target.combatant.cmb or (
|
||||
target.combatant.bab
|
||||
+ target.combatant.abilities.str_mod
|
||||
+ self._size_mod(target)
|
||||
)
|
||||
# PF1e (SRD Combat Maneuver Defense): "A creature can also add any
|
||||
# circumstance, deflection, dodge, insight, luck, morale, profane,
|
||||
# and sacred bonuses to AC to its CMD. [...] A flat-footed creature
|
||||
@@ -4177,7 +4185,7 @@ class CombatEngine:
|
||||
# bonus and aid_ac_bonus a circumstance bonus to AC — both types the
|
||||
# SRD lets a creature add to its CMD.
|
||||
return (
|
||||
10 + target_cmb + dex_mod + ac_bonuses
|
||||
10 + target_base + dex_mod + ac_bonuses
|
||||
+ target.defensive_ac_bonus + target.aid_ac_bonus
|
||||
)
|
||||
|
||||
|
||||
@@ -461,6 +461,18 @@ def test_small_or_larger_cmb_uses_strength() -> None:
|
||||
assert engine._maneuver_cmb(medium) == 2 - 1 + 0 # bab + str + size
|
||||
|
||||
|
||||
def test_tiny_or_smaller_cmd_uses_strength_and_dexterity() -> None:
|
||||
# SRD Combat Maneuvers: "CMD = 10 + Base attack bonus + Strength modifier
|
||||
# + Dexterity modifier + special size modifier" — the Tiny-or-smaller
|
||||
# Dex-for-Str substitution is CMB-only. Tiny: str -1, dex +4, bab 2,
|
||||
# size -2 -> 10 + 2 - 1 + 4 - 2 = 13 (not 10 + 2 + 4 - 2 + 4 = 18).
|
||||
engine = make_engine([], [])
|
||||
for size in ("Tiny", "Diminutive", "Fine"):
|
||||
tiny = make_state(_resized(make_combatant("t"), size, 8, 18), "p", (0, 0))
|
||||
expected = 10 + 2 - 1 + 4 + _SIZE_MODIFIERS[size.lower()]
|
||||
assert engine._maneuver_cmd(tiny) == expected
|
||||
|
||||
|
||||
def _buffed(spec: Combatant, mods: tuple[StatModifier, ...]) -> CombatantState:
|
||||
state = make_state(spec, "p", (0, 0))
|
||||
state.active_buffs.append(
|
||||
|
||||
Reference in New Issue
Block a user