fix(rules): CMD never adds Dex on top of a Tiny Dex-substituted pre-baked CMB
This commit is contained in:
@@ -4782,6 +4782,18 @@ class CombatEngine:
|
|||||||
# Strength modifier + Dexterity modifier + special size modifier" —
|
# Strength modifier + Dexterity modifier + special size modifier" —
|
||||||
# the Tiny-or-smaller Dex-for-Str substitution is CMB-only, so the
|
# the Tiny-or-smaller Dex-for-Str substitution is CMB-only, so the
|
||||||
# ability component is computed here instead of via _maneuver_cmb.
|
# ability component is computed here instead of via _maneuver_cmb.
|
||||||
|
# A pre-baked CMB on a Tiny-or-smaller creature ALREADY carries that
|
||||||
|
# substitution (SRD: they "use their Dexterity modifier in place of
|
||||||
|
# their Strength modifier to determine their CMB"), so it must never
|
||||||
|
# seed the CMD base — the extra Dexterity below would be double-
|
||||||
|
# counted and Strength lost. Use the raw CMD formula for those sizes.
|
||||||
|
if target.combatant.size.lower() in _TINY_OR_SMALLER_SIZES:
|
||||||
|
target_base = (
|
||||||
|
target.combatant.bab
|
||||||
|
+ target.combatant.abilities.str_mod
|
||||||
|
+ self._size_mod(target)
|
||||||
|
)
|
||||||
|
else:
|
||||||
target_base = target.combatant.cmb or (
|
target_base = target.combatant.cmb or (
|
||||||
target.combatant.bab
|
target.combatant.bab
|
||||||
+ target.combatant.abilities.str_mod
|
+ target.combatant.abilities.str_mod
|
||||||
|
|||||||
@@ -622,6 +622,27 @@ def test_tiny_or_smaller_cmd_uses_strength_and_dexterity() -> None:
|
|||||||
assert engine._maneuver_cmd(tiny) == expected
|
assert engine._maneuver_cmd(tiny) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmd_never_double_counts_tiny_dex_substituted_cmb() -> None:
|
||||||
|
# Latent bug guard: a Tiny creature with a PRE-BAKED cmb (already
|
||||||
|
# Dex-substituted per SRD CMB) must not get the Dexterity modifier added
|
||||||
|
# on top of it. Tiny: str -1, dex +4, bab 2, size -2, cmb pre-baked to
|
||||||
|
# 2 + 4 - 2 = 4. Correct CMD = 10 + 2 - 1 + 4 - 2 = 13; the buggy path
|
||||||
|
# (10 + cmb + dex) would give 10 + 4 + 4 = 18.
|
||||||
|
engine = make_engine([], [])
|
||||||
|
tiny = make_state(
|
||||||
|
_resized(make_combatant("t"), "Tiny", 8, 18).model_copy(update={"cmb": 4}),
|
||||||
|
"p",
|
||||||
|
(0, 0),
|
||||||
|
)
|
||||||
|
assert engine._maneuver_cmd(tiny) == 13
|
||||||
|
# A Medium creature's pre-baked cmb (Str-based) keeps the old behavior:
|
||||||
|
# CMD = 10 + cmb + dex = 10 + 8 + 0 = 18 (thrune_agent-like shape).
|
||||||
|
medium = make_state(
|
||||||
|
make_combatant("m").model_copy(update={"cmb": 8}), "p", (0, 1)
|
||||||
|
)
|
||||||
|
assert engine._maneuver_cmd(medium) == 18
|
||||||
|
|
||||||
|
|
||||||
def _buffed(spec: Combatant, mods: tuple[StatModifier, ...]) -> CombatantState:
|
def _buffed(spec: Combatant, mods: tuple[StatModifier, ...]) -> CombatantState:
|
||||||
state = make_state(spec, "p", (0, 0))
|
state = make_state(spec, "p", (0, 0))
|
||||||
state.active_buffs.append(
|
state.active_buffs.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user