fix(rules): add AC bonuses and flat-footed Dexterity to CMD

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 does not add its Dexterity bonus to its CMD." _maneuver_cmd now sums the target's eligible AC bonus modifiers (plus the dodge/circumstance runtime bonuses from total defense, fight defensively and aid another) and drops the Dexterity bonus (keeping any penalty) while the target denies its Dexterity to AC. README 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-12 08:49:46 +02:00
parent 47010c6e00
commit 98b61c6cf8
3 changed files with 106 additions and 2 deletions
+31 -1
View File
@@ -303,6 +303,15 @@ _NEIGHBOR_OFFSETS: tuple[tuple[int, int], ...] = (
)
# PF1e (SRD Combat Maneuver Defense): the AC bonus types a creature may add
# to its CMD. Penalties and other bonus types (armor, shield, natural,
# enhancement, competence, untyped) stay out of the CMD.
_CMD_AC_BONUS_TYPES: frozenset[str] = frozenset({
"circumstance", "deflection", "dodge", "insight", "luck", "morale",
"profane", "sacred",
})
def _save_label(result: SaveResult | None) -> str:
if result is None:
return "NO SAVE"
@@ -4051,8 +4060,29 @@ class CombatEngine:
)
def _maneuver_cmd(self, target: CombatantState) -> int:
if target.combatant.cmd:
return target.combatant.cmd
target_cmb = self._maneuver_cmb(target)
return target.combatant.cmd or (10 + target_cmb + target.combatant.abilities.dex_mod)
# 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
# does not add its Dexterity bonus to its CMD." A Dexterity penalty
# is not a bonus, so it still applies when flat-footed.
dex_mod = target.combatant.abilities.dex_mod
if self.has_condition_flag(target, "denies_dex_to_ac"):
dex_mod = min(0, dex_mod)
ac_bonuses = resolve_modifiers(
m
for m in self._stat_modifiers(target, "ac")
if m.bonus_type in _CMD_AC_BONUS_TYPES
)
# defensive_ac_bonus (total defense / fight defensively) is a dodge
# 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
+ target.defensive_ac_bonus + target.aid_ac_bonus
)
def _maneuver_trip(
self, state: CombatantState, target: CombatantState, m: _ManeuverRoll