Surface why a manual clue edit didn't resolve, instead of silence
Circle-circle intersection (and the other solvable shapes) already worked correctly, verified with a direct test: two distance-only clues resolve to potential_coords whenever the two circles actually cross. What was missing was feedback when they don't, e.g. two distances that put one circle entirely inside the other given the references' real positions looks identical, from the dialog's perspective, to a reference that's just not placed yet. solver.explain_unresolved() distinguishes 'waiting on <ref> for a known position' from 'these two readings are geometrically inconsistent', wired into _apply_and_refresh's toast after a manual Description-tab edit.
This commit is contained in:
parent
b206b382fc
commit
8c73442c03
@ -990,6 +990,12 @@ class MainWindow(Adw.ApplicationWindow):
|
|||||||
def _apply_and_refresh(self, obj, location: Location) -> None:
|
def _apply_and_refresh(self, obj, location: Location) -> None:
|
||||||
_apply_location(obj, location)
|
_apply_location(obj, location)
|
||||||
self._refresh()
|
self._refresh()
|
||||||
|
# A manual clue edit (typically the Description tab) can go in and
|
||||||
|
# come out unresolved with no other feedback, tell the user why
|
||||||
|
# instead of leaving it looking like nothing happened.
|
||||||
|
reason = solver.explain_unresolved(obj.location, self.board)
|
||||||
|
if reason is not None:
|
||||||
|
self.toast(f"{obj.name} not resolved: {reason}.")
|
||||||
|
|
||||||
def _edit_target_position(self, target) -> None:
|
def _edit_target_position(self, target) -> None:
|
||||||
"""Firing card's "Edit pos" button: manual coord overrides whatever
|
"""Firing card's "Edit pos" button: manual coord overrides whatever
|
||||||
|
|||||||
@ -219,6 +219,44 @@ def solve_location(location: Location, board: Board) -> SolveResult:
|
|||||||
return SolveResult()
|
return SolveResult()
|
||||||
|
|
||||||
|
|
||||||
|
def explain_unresolved(location: Location, board: Board) -> str | None:
|
||||||
|
"""Best-effort human explanation for why `location` hasn't resolved,
|
||||||
|
surfaced right after a manual edit so a bad/impossible entry doesn't
|
||||||
|
just silently do nothing. Either some clue's reference isn't itself
|
||||||
|
known yet, or the ones that are known are geometrically inconsistent,
|
||||||
|
e.g. two distance circles that don't actually cross given how far
|
||||||
|
apart their centers really are (the game's typewriter can print
|
||||||
|
distances that don't agree with reality if a spotter's position is
|
||||||
|
off, or a digit got misread). Returns None if there's nothing to
|
||||||
|
explain: already resolved, ambiguous-but-resolved-enough, or no
|
||||||
|
clues at all."""
|
||||||
|
if location.coord is not None or location.potential_coords or not location.clues:
|
||||||
|
return None
|
||||||
|
|
||||||
|
unresolved_refs = sorted({c.reference for c in location.clues if _entity_point(board, c.reference) is None})
|
||||||
|
if unresolved_refs:
|
||||||
|
return "waiting on " + ", ".join(unresolved_refs) + " to have a known position first"
|
||||||
|
|
||||||
|
resolved = [(clue, pt) for clue in location.clues if (pt := _entity_point(board, clue.reference)) is not None]
|
||||||
|
bearings = [(c, p) for c, p in resolved if c.bearing_deg is not None and c.distance_km is None]
|
||||||
|
distances = [(c, p) for c, p in resolved if c.distance_km is not None and c.bearing_deg is None]
|
||||||
|
|
||||||
|
if bearings and distances:
|
||||||
|
(cb, pb), (cd, pd) = bearings[0], distances[0]
|
||||||
|
if not ray_circle_intersections(pb, cb.bearing_deg, pd, cd.distance_km):
|
||||||
|
return (f"the bearing from {cb.reference} never crosses the {cd.distance_km}km "
|
||||||
|
f"circle around {cd.reference}, check those two readings against each other")
|
||||||
|
|
||||||
|
if len(distances) >= 2:
|
||||||
|
(c1, p1), (c2, p2) = distances[0], distances[1]
|
||||||
|
if not circle_circle_intersections(p1, c1.distance_km, p2, c2.distance_km):
|
||||||
|
return (f"the {c1.distance_km}km circle around {c1.reference} and the {c2.distance_km}km "
|
||||||
|
f"circle around {c2.reference} don't cross, too far apart or one nested inside "
|
||||||
|
"the other given those references' actual positions, check the readings/positions")
|
||||||
|
|
||||||
|
return None # e.g. two bearings that are (near-)parallel, or genuinely just needs more info
|
||||||
|
|
||||||
|
|
||||||
def resolve_board(board: Board) -> list[str]:
|
def resolve_board(board: Board) -> list[str]:
|
||||||
"""Resolve every not-yet-resolved RP/Target whose clues can currently
|
"""Resolve every not-yet-resolved RP/Target whose clues can currently
|
||||||
be satisfied, repeating until a fixed point (handles dependency
|
be satisfied, repeating until a fixed point (handles dependency
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user