fix(rules): correct P2/P3 PF1e conformance bugs (flanking, touch, blinded, disabled, death, maneuvers, DR, sneak)

This commit is contained in:
2026-09-10 21:45:50 +02:00
parent 5afe495c4a
commit 9aee4f6f6b
15 changed files with 934 additions and 134 deletions
+13 -10
View File
@@ -63,6 +63,9 @@ def make_grid() -> Grid:
def make_engine(queue: list[int], states: list[CombatantState]) -> CombatEngine:
# A maneuver provokes an AoO from the (threatening) target BEFORE the CMB
# check (B13 conformance): the scripted queues carry a leading natural 1
# so the provoking attack always misses.
return CombatEngine(ScriptedRng(queue), make_grid(), states)
@@ -73,7 +76,7 @@ def test_reposition_direction_right() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "reposition", direction=(0, 1))
assert b.pos == (1, 3)
@@ -83,7 +86,7 @@ def test_reposition_direction_down() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "reposition", direction=(1, 0))
assert b.pos == (2, 2)
@@ -95,7 +98,7 @@ def test_drag_direction_up() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "drag", direction=(-1, 0))
assert b.pos == (0, 2)
assert a.pos == (1, 2)
@@ -108,7 +111,7 @@ def test_bull_rush_direction_left() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (2, 2))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "bull_rush", direction=(0, -1))
assert b.pos == (1, 1)
@@ -120,7 +123,7 @@ def test_reposition_direction_out_of_bounds_no_move() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (0, 0))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "reposition", direction=(-1, 0))
assert b.pos == (0, 0)
@@ -130,7 +133,7 @@ def test_reposition_direction_occupied_no_move() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
# direction points at the attacker's own cell -> occupied -> no move
engine._execute_maneuver(a, b, "reposition", direction=(0, -1))
assert b.pos == (1, 2)
@@ -141,7 +144,7 @@ def test_bull_rush_direction_out_of_bounds_no_move() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (0, 0))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "bull_rush", direction=(-1, 0))
assert b.pos == (0, 0)
@@ -153,7 +156,7 @@ def test_reposition_none_uses_first_free_neighbor() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "reposition")
# First free neighbor in _NEIGHBOR_OFFSETS order, skipping attacker's cell.
assert b.pos == (0, 1)
@@ -164,7 +167,7 @@ def test_drag_none_uses_beyond_cell() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (1, 1))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "drag")
# Beyond the target from the attacker: (1, 3).
assert b.pos == (1, 3)
@@ -176,7 +179,7 @@ def test_bull_rush_none_uses_away_from_attacker() -> None:
b_spec = make_combatant("b")
a = make_state(a_spec, "players", (2, 2))
b = make_state(b_spec, "monsters", (1, 2))
engine = make_engine([12], [a, b])
engine = make_engine([1, 12], [a, b])
engine._execute_maneuver(a, b, "bull_rush")
# Away from attacker (up, since target is above): (0, 2).
assert b.pos == (0, 2)