"""Tests for corner-rule line of effect and cover (los.py). Geometry convention: a line is blocked when its relative interior touches a blocking square (interior, border, or vertex); lines that merely start or end on a blocking square's vertex do not count as passing through it. """ from __future__ import annotations from pf1e_simulator.grid import Grid from pf1e_simulator.los import has_cover, has_line_of_effect from pf1e_simulator.map import MapSpec, TerrainType def make_grid(rows: list[str]) -> Grid: legend = { "#": TerrainType(type="wall", move_cost=None, blocks_los=True), ".": TerrainType(type="floor", move_cost=1), "T": TerrainType(type="rubble", move_cost=2), "C": TerrainType(type="pillar", move_cost=None, cover=True), } spec = MapSpec(name="test", terrain=tuple(rows), legend=legend) return Grid.from_spec(spec) def test_los_open_field() -> None: grid = make_grid(["...", "...", "..."]) assert has_line_of_effect(grid, (0, 0), (2, 2)) is True def test_los_same_square() -> None: grid = make_grid(["."]) assert has_line_of_effect(grid, (0, 0), (0, 0)) is True def test_los_blocked_by_wall_between() -> None: grid = make_grid([".#.", "..."]) assert has_line_of_effect(grid, (0, 0), (0, 2)) is False def test_los_through_doorway_gap() -> None: grid = make_grid(["##.##", "##.##"]) assert has_line_of_effect(grid, (0, 2), (1, 2)) is True def test_los_pinched_vertex_stays_open() -> None: grid = make_grid([".#.", "#.."]) assert has_line_of_effect(grid, (0, 0), (1, 1)) is True def test_los_blocked_past_wall_line() -> None: grid = make_grid(["...#.", "....."]) assert has_line_of_effect(grid, (0, 0), (0, 4)) is False def test_cover_open_field() -> None: grid = make_grid([".....", "....."]) assert has_cover(grid, (0, 0), (0, 4), ranged=True) is False def test_cover_pillar_between() -> None: grid = make_grid([".....", "..C..", "....."]) assert has_cover(grid, (1, 0), (1, 4), ranged=True) is True def test_no_cover_when_pillar_off_axis() -> None: grid = make_grid(["..C..", "....."]) assert has_cover(grid, (1, 0), (1, 4), ranged=True) is False def test_no_cover_adjacent_with_wall_behind_target() -> None: grid = make_grid(["..#"]) assert has_cover(grid, (0, 0), (0, 1), ranged=True) is False def test_cover_diagonal_past_wall_vertex() -> None: grid = make_grid([".#.", "#.."]) assert has_cover(grid, (0, 0), (1, 1), ranged=True) is True def test_cover_melee_uses_same_corner_rule_in_phase0() -> None: grid = make_grid([".#.", "#.."]) assert has_cover(grid, (0, 0), (1, 1), ranged=False) is True open_grid = make_grid(["...", "..."]) assert has_cover(open_grid, (0, 0), (1, 1), ranged=False) is False def test_cover_same_square_is_false() -> None: grid = make_grid(["."]) assert has_cover(grid, (0, 0), (0, 0), ranged=True) is False def test_soft_cover_creature_between_grants_cover() -> None: """A creature between shooter and target grants cover on ranged attacks.""" grid = make_grid([".....", ".....", "....."]) occupied = frozenset({(1, 2)}) assert has_cover(grid, (1, 0), (1, 4), ranged=True, occupied=occupied) is True def test_soft_cover_melee_ignores_creatures() -> None: """Melee attacks ignore creatures for soft cover (CRB: ranged only).""" grid = make_grid([".....", ".....", "....."]) occupied = frozenset({(1, 2)}) assert has_cover(grid, (1, 0), (1, 4), ranged=False, occupied=occupied) is False def test_soft_cover_creature_behind_target_ignored() -> None: """A creature behind the target does not grant cover.""" grid = make_grid(["......", "......"]) occupied = frozenset({(1, 5)}) assert has_cover(grid, (1, 0), (1, 4), ranged=True, occupied=occupied) is False def test_soft_cover_creature_off_axis_ignored() -> None: """A creature off the attack line does not grant cover.""" grid = make_grid([".....", "....."]) occupied = frozenset({(0, 2)}) assert has_cover(grid, (1, 0), (1, 4), ranged=True, occupied=occupied) is False