feat(combat): expose threatened squares and movement cost fields for LLM policy

Rename _threatened_squares -> threatened_squares and
_enemies_threatening -> enemies_threatening (public read-only API for a
future LLM policy), and add reachable_ft(state, budget) and
movement_cost_ft(state, target) accessors returning movement costs in
feet. Pure exposure: no behavior change, resolution engine untouched,
goldens bit-identical.
This commit is contained in:
2026-09-06 10:36:48 +02:00
parent 9c49f85af8
commit 56c8970d5b
4 changed files with 381 additions and 18 deletions
+5 -5
View File
@@ -121,7 +121,7 @@ def test_medium_5ft_weapon_threatens_adjacent_only() -> None:
fighter = make_combatant("fighter")
state = make_state(fighter, "players", (3, 3))
engine = make_engine([state])
assert engine._threatened_squares(state) == frozenset(_adjacent((3, 3)))
assert engine.threatened_squares(state) == frozenset(_adjacent((3, 3)))
def test_reach_weapon_threatens_ring_not_adjacent() -> None:
@@ -129,7 +129,7 @@ def test_reach_weapon_threatens_ring_not_adjacent() -> None:
spearman = make_combatant("spearman", reach_ft=10)
state = make_state(spearman, "players", (3, 3))
engine = make_engine([state])
threatened = engine._threatened_squares(state)
threatened = engine.threatened_squares(state)
assert threatened == frozenset(_ring((3, 3), 2))
assert (3, 2) not in threatened # adjacent orthogonal
assert (3, 4) not in threatened
@@ -178,7 +178,7 @@ def test_sword_plus_reach_weapon_union() -> None:
)
state = make_state(combatant, "players", (3, 3))
engine = make_engine([state])
assert engine._threatened_squares(state) == frozenset(
assert engine.threatened_squares(state) == frozenset(
_adjacent((3, 3)) | _ring((3, 3), 2)
)
@@ -194,7 +194,7 @@ def test_large_creature_sword_threatens_disk_radius_2() -> None:
for cell in footprint:
expected |= _disk(cell, 2)
expected -= footprint
assert engine._threatened_squares(state) == frozenset(expected)
assert engine.threatened_squares(state) == frozenset(expected)
def test_unknown_size_defaults_to_5ft() -> None:
@@ -202,7 +202,7 @@ def test_unknown_size_defaults_to_5ft() -> None:
weird = make_combatant("weird", size="Weird")
state = make_state(weird, "players", (3, 3))
engine = make_engine([state])
assert engine._threatened_squares(state) == frozenset(_adjacent((3, 3)))
assert engine.threatened_squares(state) == frozenset(_adjacent((3, 3)))
def test_weapon_for_reach_weapon_at_distance_2_not_1() -> None: