fix(rules): use Dexterity for the CMB of Tiny and smaller creatures
PF1e (SRD Combat Maneuvers): "Creatures that are size Tiny or smaller use their Dexterity modifier in place of their Strength modifier to determine their CMB." _maneuver_cmb now swaps Str for Dex when the size is Tiny, Diminutive or Fine. True 0-reach and co-occupancy for Tiny-or-smaller stay unmodelled (5-ft grid abstraction), documented in the README. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+32
-1
@@ -7,7 +7,7 @@ it (effectively disarmed). See the plan task-12.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pf1e_simulator.combat import CombatantState, CombatEngine
|
||||
from pf1e_simulator.combat import _SIZE_MODIFIERS, CombatantState, CombatEngine
|
||||
from pf1e_simulator.conditions import CONDITIONS_BY_NAME
|
||||
from pf1e_simulator.effects import ActiveCondition
|
||||
from pf1e_simulator.grid import Grid
|
||||
@@ -269,3 +269,34 @@ def test_drag_no_space() -> None:
|
||||
assert b.pos == (1, 2)
|
||||
assert a.pos == (1, 1)
|
||||
assert "no space" in engine._transcript[-1]
|
||||
|
||||
|
||||
def _resized(spec: Combatant, size: str, str_score: int, dex_score: int) -> Combatant:
|
||||
return spec.model_copy(
|
||||
update={
|
||||
"size": size,
|
||||
"abilities": spec.abilities.model_copy(
|
||||
update={"str_score": str_score, "dex_score": dex_score}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_tiny_or_smaller_cmb_uses_dexterity() -> None:
|
||||
# SRD Combat Maneuvers: "Creatures that are size Tiny or smaller use
|
||||
# their Dexterity modifier in place of their Strength modifier to
|
||||
# determine their CMB." Tiny: str -1, dex +4, bab 2, size -2 -> 2+4-2.
|
||||
engine = make_engine([], [])
|
||||
for size in ("Tiny", "Diminutive", "Fine"):
|
||||
tiny = make_state(_resized(make_combatant("t"), size, 8, 18), "p", (0, 0))
|
||||
expected = 2 + 4 + _SIZE_MODIFIERS[size.lower()]
|
||||
assert engine._maneuver_cmb(tiny) == expected
|
||||
|
||||
|
||||
def test_small_or_larger_cmb_uses_strength() -> None:
|
||||
# The Dex substitution stops at Tiny: Small and larger use Strength.
|
||||
engine = make_engine([], [])
|
||||
small = make_state(_resized(make_combatant("s"), "Small", 8, 18), "p", (0, 0))
|
||||
medium = make_state(_resized(make_combatant("m"), "Medium", 8, 18), "p", (0, 1))
|
||||
assert engine._maneuver_cmb(small) == 2 - 1 - 1 # bab + str + size
|
||||
assert engine._maneuver_cmb(medium) == 2 - 1 + 0 # bab + str + size
|
||||
|
||||
Reference in New Issue
Block a user