"""Regression coverage for solver.py's geometric resolution.""" from fenigma import solver from fenigma.models import LARGE_X, Board, Clue, Coord, Location, TargetType def _board_with_spotters(*coords): board = Board() for i, coord in enumerate(coords, start=1): board.add_spotter(coord, i) return board def test_bearing_and_distance_from_one_reference_resolves_directly(): board = _board_with_spotters(Coord("J", 5, 0, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [Clue(reference="Spotter#1", bearing_deg=90.0, distance_km=3.0)]) solver.resolve_board(board) assert target.coord is not None def test_two_bearings_resolve_via_ray_ray_intersection(): board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("L", 4, 3, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", bearing_deg=90.0), Clue(reference="Spotter#2", bearing_deg=180.0), ]) solver.resolve_board(board) assert target.coord is not None def test_two_distances_that_actually_cross_are_ambiguous_not_resolved(): board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("P", 5, 0, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", distance_km=7.33), Clue(reference="Spotter#2", distance_km=3.43), ]) solver.resolve_board(board) assert target.coord is None assert len(target.location.potential_coords) == 2 def test_nested_distance_circles_fall_back_to_compromise_point(): """Two distances that don't actually cross (one circle nested inside the other, given how close the two spotters are) used to just give up entirely. closest_compromise_point() finds a bounded, sane stand-in instead, flagged via Location.note rather than treated as a clean resolution.""" board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("J", 5, 1, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", distance_km=7.33), Clue(reference="Spotter#2", distance_km=3.43), ]) solver.resolve_board(board) assert target.coord is not None assert target.location.note is not None assert "approximate" in target.location.note def test_toleranced_bearing_is_never_used_to_triangulate(): """A compass-word bearing (bearing_tolerance_deg set) names a whole sector, solve_location() must never use it as if it were a precise ray, even when it's the only bearing-shaped clue available.""" board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("L", 4, 3, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", distance_km=3.0), Clue(reference="Spotter#2", bearing_deg=270.0, bearing_tolerance_deg=11.25), ]) solver.resolve_board(board) # only one usable (distance-only) clue remains once the toleranced # bearing is excluded, not enough to resolve anything on its own. assert target.coord is None assert not target.location.potential_coords def test_explain_unresolved_ignores_a_toleranced_bearing_too(): """A real inconsistency: solve_location() already excluded a toleranced bearing from triangulation, but explain_unresolved()'s own bearings list didn't, so it could describe a toleranced-only bearing as if it were usable geometry. Only a distance clue is left once the toleranced bearing is (correctly) excluded, one clue alone is never inconsistent with itself, so there's nothing to explain.""" board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("L", 4, 3, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", distance_km=3.0), Clue(reference="Spotter#2", bearing_deg=270.0, bearing_tolerance_deg=11.25), ]) solver.resolve_board(board) assert solver.explain_unresolved(target.location, board) is None def test_manual_coord_override_clears_a_stale_note(): board = _board_with_spotters(Coord("J", 5, 0, 0), Coord("J", 5, 1, 0)) target = board.add_target(TargetType.TANK, id_="1") target.location = Location.from_desc("x", [ Clue(reference="Spotter#1", distance_km=7.33), Clue(reference="Spotter#2", distance_km=3.43), ]) solver.resolve_board(board) assert target.location.note is not None target.coord = Coord("A", 1, 0, 0) assert target.location.note is None def test_point_to_coord_round_trips_every_sub_cell(): """A real, reproducible bug: Coord.as_fraction() centers a sub-cell at x + 0.5 (so a marker drawn at its own coord's exact pixel position round-trips back to the same coord on click), which put point_to_coord()'s own rounding exactly on a .5 boundary, the worst case for floating point. A tiny representation error from the subtraction it used to do could tip round() to either side, silently returning a coord one sub-cell off from the one actually clicked, this reproduced with zero pixel math involved at all, just feeding as_fraction() straight back into point_to_coord(). Checked exhaustively, not just a couple of samples, since the failure was itself pattern-dependent (only some coords tripped the float rounding the wrong way).""" for X in LARGE_X: for Y in range(1, 11): for x in range(10): for y in range(10): c = Coord(X, Y, x, y) assert solver.point_to_coord(c.as_fraction()) == c