fix(rules): restrict maneuvers to targets within one size category
PF1e (SRD Trip / Bull Rush / Overrun): "You can only [affect] an opponent who is no more than one size category larger than you." _execute_maneuver now rejects trip, bull_rush and overrun against a target two or more size categories larger: no roll, no provoked AoO, logged rejection. A _SIZE_ORDER ranking (Fine..Colossal, unknown -> Medium) backs the check; other maneuvers keep no size cap. 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:
@@ -248,6 +248,39 @@ def test_overrun_fail_adjacent_attacker_does_not_move() -> None:
|
||||
assert not any("overrun blocked" in line for line in engine._transcript)
|
||||
|
||||
|
||||
def test_maneuver_rejects_target_two_sizes_larger() -> None:
|
||||
# SRD Trip/Bull Rush/Overrun: "You can only [affect] an opponent who is
|
||||
# no more than one size category larger than you." Medium vs Huge: the
|
||||
# attempt is illegal — no d20 consumed, no effect, logged rejection.
|
||||
for maneuver in ("trip", "bull_rush", "overrun"):
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
huge = make_state(_resized(make_combatant("h"), "Huge", 10, 10), "monsters", (1, 2))
|
||||
engine = make_engine([1, 12], [a, huge])
|
||||
engine._execute_maneuver(a, huge, maneuver)
|
||||
assert len(engine._rng._queue) == 2
|
||||
assert not any(c.condition.name == "prone" for c in huge.active_conditions)
|
||||
assert f"{maneuver} vs h rejected (target too large)" in engine._transcript[-1]
|
||||
|
||||
|
||||
def test_maneuver_allows_target_one_size_larger() -> None:
|
||||
# Medium vs Large (one category) stays legal; the roll happens.
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
large = make_state(_resized(make_combatant("l"), "Large", 10, 10), "monsters", (1, 2))
|
||||
engine = make_engine([1, 12], [a, large])
|
||||
engine._execute_maneuver(a, large, "trip")
|
||||
assert not engine._rng._queue
|
||||
assert any(c.condition.name == "prone" for c in large.active_conditions)
|
||||
|
||||
|
||||
def test_unrestricted_maneuver_ignores_size_limit() -> None:
|
||||
# The size cap is trip/bull_rush/overrun only: disarm works on Huge.
|
||||
a = make_state(make_combatant("a", attack_bonus=2), "players", (1, 1))
|
||||
huge = make_state(_resized(make_combatant("h"), "Huge", 10, 10), "monsters", (1, 2))
|
||||
engine = make_engine([1, 12], [a, huge])
|
||||
engine._execute_maneuver(a, huge, "disarm")
|
||||
assert not engine._rng._queue
|
||||
|
||||
|
||||
def test_overrun_knocks_prone_and_moves_through() -> None:
|
||||
# d20=15+2=17 vs CMD 12: margin 5 -> prone AND moved through (CRB
|
||||
# "Overrun": prone only when the attack exceeds CMD by 5 or more).
|
||||
|
||||
Reference in New Issue
Block a user