conformance: block 5-foot step on difficult terrain (SRD Take 5-Foot Step)
A 5-foot step is hampered by difficult terrain: stepping out of OR into a difficult cell is blocked. _step5 (auto-directed) logs 'wait (cannot step)'; _step5_to (explicit destination) rejects with unreachable_destination. Engine-level tests cover destination, auto-path, and origin cases.
This commit is contained in:
@@ -450,3 +450,74 @@ def test_move_with_target_auto_paths_as_before() -> None:
|
||||
assert eng._states[0].last_action_results == [
|
||||
ActionResult(kind="move", ok=True, reason="ok")
|
||||
]
|
||||
|
||||
|
||||
# ── (g) 5-foot step blocked by difficult terrain ────────────────────────────
|
||||
|
||||
|
||||
def _rubble_adjacent_grid() -> Grid:
|
||||
"""8x8 grid where (0, 1) is difficult terrain (rubble, cost 2)."""
|
||||
rows = [".~" + "." * 6] + ["." * 8] * 7
|
||||
legend = {
|
||||
".": TerrainType(type="floor", move_cost=1),
|
||||
"~": TerrainType(type="rubble", move_cost=2),
|
||||
}
|
||||
return Grid.from_spec(MapSpec(name="rubble", terrain=tuple(rows), legend=legend))
|
||||
|
||||
|
||||
def test_step5_to_difficult_destination_is_rejected() -> None:
|
||||
"""SRD Take 5-Foot Step: hampered by difficult terrain blocks the step."""
|
||||
|
||||
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
|
||||
if state.combatant.id == "hero":
|
||||
return (Action(kind="5foot_step", destination=(0, 1)),)
|
||||
return _WAIT
|
||||
|
||||
grid = _rubble_adjacent_grid()
|
||||
eng = _hero_vs_foe(policy, [10, 5], round_cap=1, foe_pos=(0, 4), grid=grid)
|
||||
t = eng.run().transcript
|
||||
assert eng._states[0].pos == (0, 0)
|
||||
assert "round 1 hero: 5foot_step rejected (unreachable_destination)" in t
|
||||
|
||||
|
||||
def test_step5_auto_path_to_difficult_is_blocked() -> None:
|
||||
"""SRD Take 5-Foot Step: auto-directed step into difficult terrain blocked."""
|
||||
|
||||
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
|
||||
if state.combatant.id == "hero":
|
||||
return (Action(kind="5foot_step", target_id="foe"),)
|
||||
return _WAIT
|
||||
|
||||
legend = {
|
||||
".": TerrainType(type="floor", move_cost=1),
|
||||
"~": TerrainType(type="rubble", move_cost=2),
|
||||
"#": TerrainType(type="wall", move_cost=None, blocks_los=True),
|
||||
}
|
||||
# Hero at (0,0), wall at (0,1) blocks east, rubble at (1,0) is the only exit.
|
||||
# Foe at (2,0) -- the greedy walk must go through (1,0) rubble.
|
||||
rows = [".#" + "." * 6, "~" + "." * 7] + ["." * 8] * 6
|
||||
grid = Grid.from_spec(MapSpec(name="funnel", terrain=tuple(rows), legend=legend))
|
||||
eng = _hero_vs_foe(policy, [10, 5], round_cap=1, foe_pos=(2, 0), grid=grid)
|
||||
t = eng.run().transcript
|
||||
assert eng._states[0].pos == (0, 0)
|
||||
assert "round 1 hero: wait (cannot step)" in t
|
||||
|
||||
|
||||
def test_step5_from_difficult_origin_is_rejected() -> None:
|
||||
"""SRD Take 5-Foot Step: origin on difficult terrain also blocks the step."""
|
||||
|
||||
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
|
||||
if state.combatant.id == "hero":
|
||||
return (Action(kind="5foot_step", destination=(1, 1)),)
|
||||
return _WAIT
|
||||
|
||||
legend = {
|
||||
".": TerrainType(type="floor", move_cost=1),
|
||||
"~": TerrainType(type="rubble", move_cost=2),
|
||||
}
|
||||
rows = ["~" + "." * 7] + ["." * 8] * 7
|
||||
grid = Grid.from_spec(MapSpec(name="rubble", terrain=tuple(rows), legend=legend))
|
||||
eng = _hero_vs_foe(policy, [10, 5], round_cap=1, foe_pos=(0, 4), grid=grid)
|
||||
t = eng.run().transcript
|
||||
assert eng._states[0].pos == (0, 0)
|
||||
assert "round 1 hero: 5foot_step rejected (unreachable_destination)" in t
|
||||
|
||||
Reference in New Issue
Block a user