- Board.clear() now also drops allies; the "clear board?" guard checks
allies too. New Board.clear_units() + Clear button right-click menu
("clear enemies, units & flights", keeps Nest/spotters/RPs).
- An Ally with the ad-hoc TargetType.ENEMY showed "Enemy" on the map
popover/toast instead of "Ally" (icons.target_type_label already had
the fix for the picker, now reused everywhere else via app.py's
_display_name).
- Firing panel drag-reorder no longer triggers a full app refresh
(solver + dedupe + map redraw) on every drop, just a local rebuild.
- "Always show geo" didn't draw for Allies (missing from the overlay
candidate list); blast radius only respected selection, not the
show_geo_desc pin.
- ocr.py: added a second fire-support-request grammar ("Infantry#N
taking fire ... Requesting X Shell on our position at <coord> before
<time>", plus a bearing/distance-from-position variant), distinct
from the existing Marine Garrison one.
- New debug_capture.py: saves screenshots (+ metadata) the app handled
badly, for later tuning of map_vision/ocr against real failures:
map-read errors, user grid corrections (paired with the auto-detected
grid), screenshots that read as text but may have been a map, and
marker-detection ground truth (every proposal's accept/reject verdict
plus units added with no matching proposal) captured whenever a
screenshot stops being the active one.
- README: Known issues section (map screenshot reading, grid + unit
detection, is unreliable and fails often).
- 14 new tests (tests/test_models.py, tests/test_debug_capture.py, +
additions to tests/test_ocr.py), 38/38 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
"""Regression coverage for Board's bulk-mutation helpers (clear/clear_units)
|
|
and the id namespaces Target/Ally are supposed to keep separate."""
|
|
from fenigma.models import Board, Coord, TargetType
|
|
|
|
|
|
def _coord(x=0, y=0):
|
|
return Coord(X="A", Y=1, x=x, y=y)
|
|
|
|
|
|
def test_clear_drops_allies_too():
|
|
"""Board.clear() used to leave self.allies untouched -- the "clear
|
|
board" action then reported success but a previously-placed ally
|
|
stayed on the map."""
|
|
board = Board()
|
|
board.nest.coord = _coord()
|
|
board.add_spotter(_coord())
|
|
board.add_reference_point(_coord())
|
|
board.add_target(TargetType.TANK, _coord())
|
|
board.add_ally(TargetType.TANK, _coord())
|
|
board.add_scout_flight((1.0, 1.0), 45.0)
|
|
|
|
board.clear()
|
|
|
|
assert board.nest.coord is None
|
|
assert board.spotters == []
|
|
assert board.reference_points == []
|
|
assert board.targets == []
|
|
assert board.allies == []
|
|
assert board.scout_flights == []
|
|
|
|
|
|
def test_clear_units_keeps_recon_infrastructure():
|
|
"""The Clear button's right-click "Clear enemies, units & flights"
|
|
option: drops targets/allies/scout flights but keeps the Nest,
|
|
spotters, and reference points."""
|
|
board = Board()
|
|
board.nest.coord = _coord()
|
|
sp = board.add_spotter(_coord())
|
|
rp = board.add_reference_point(_coord())
|
|
board.add_target(TargetType.TANK, _coord())
|
|
board.add_ally(TargetType.TANK, _coord())
|
|
board.add_scout_flight((1.0, 1.0), 45.0)
|
|
|
|
board.clear_units()
|
|
|
|
assert board.nest.coord is not None
|
|
assert board.spotters == [sp]
|
|
assert board.reference_points == [rp]
|
|
assert board.targets == []
|
|
assert board.allies == []
|
|
assert board.scout_flights == []
|
|
|
|
|
|
def test_ally_and_target_ids_are_independent_namespaces():
|
|
"""An ally Tank#1 and a hostile Target Tank#1 are unrelated -- adding
|
|
one must never be influenced by the other's ids, and auto-assignment
|
|
on each side starts from 'A' independently."""
|
|
board = Board()
|
|
t1 = board.add_target(TargetType.TANK, _coord(), id_="1")
|
|
a1 = board.add_ally(TargetType.TANK, _coord(), id_="1")
|
|
assert t1.id == a1.id == "1"
|
|
assert t1 is not a1
|
|
|
|
t_auto = board.add_target(TargetType.TANK, _coord())
|
|
a_auto = board.add_ally(TargetType.TANK, _coord())
|
|
assert t_auto.id == "A" # first free letter among *targets* only
|
|
assert a_auto.id == "A" # first free letter among *allies* only, unaffected by the target above
|
|
|
|
|
|
def test_find_by_name_prefers_target_over_same_named_ally():
|
|
"""find_by_name() (used to resolve Clue references) checks targets
|
|
before allies -- documented, deliberate priority, not a namespace
|
|
collision: an ally and a same-typed/same-id target are still two
|
|
distinct objects, this only matters when something's Clue names one
|
|
ambiguously by the shared display name."""
|
|
board = Board()
|
|
target = board.add_target(TargetType.TANK, _coord(x=1), id_="1")
|
|
board.add_ally(TargetType.TANK, _coord(x=2), id_="1")
|
|
assert board.find_by_name("Tank#1") is target
|