FEnigma/TODO.md
Dominik Roth 1ddb532325 Fix ally/target bugs, OCR fire-support parsing, add debug capture
- 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>
2026-08-11 17:35:37 +02:00

7.0 KiB

Bug Backlog (from user report, 2026-08-11)

Status legend: [x] fixed+tested, [~] partially addressed, [ ] open/needs input

  • Allies and enemies seem to share indices. Investigated: Board.add_target/add_ally already use fully separate id namespaces by design (see models.py's Ally/Target docstrings), confirmed with a new regression test (test_ally_and_target_ids_are_independent_namespaces). What was probably actually seen: an ally and a hostile target of the same type display with the same name ("Tank#1") on the map with no visual "ally" cue beyond icon/side color — related to the next item, which fixes one concrete instance of that (TargetType.ENEMY's "Enemy" label on an Ally). If the symptom persists after that, it's a display/ legibility issue, not an id collision — happy to take a screenshot of what's confusing.

  • Ally type 'ally' is called Enemy on map title. icons._target_type_label (now public icons.target_type_label) already special-cased this for the type picker, but the map's right-click popover heading, "Change type (...)" button, and toast all printed obj.type.value directly instead, so an Ally with the ad-hoc TargetType.ENEMY still showed "Enemy" everywhere except the picker itself. Fixed in app.py (_display_name, and the three spots using it).

  • Reordering firing commands lags UI hard. FiringPanel._reorder() was calling self.on_change() — app.py's full-app refresh (re-solve every target's clue graph, dedupe, redraw the map, THEN rebuild the panel) — on every single drag-drop, even though reordering touches no location/clue/coord state at all. Now calls a local self.refresh() instead.

  • "Always show geo" doesn't reliably work / blast radius should stay shown too. GridCanvas._draw_geo_overlays()'s candidate list was reference_points + targets only — Allies have a show_geo_desc pin in the UI and can carry OCR'd clues too, but were never drawn. Added. _draw_blast_radius() only ever looked at self.selected, ignoring show_geo_desc entirely, so pinning it and then selecting/ deselecting something else made it vanish; now iterates every selected-or-pinned target.

  • Clearing the board doesn't clear allies. Board.clear() cleared everything except self.allies. Fixed, plus the "clear board?" confirm-dialog's early-return guard (which skipped the whole action if only allies were on the board) now checks allies too.

  • Allow right-click on Clear button: clear all enemies/units/flights, keep spotters/RPs/nest. New Board.clear_units() + a right-click popover on the header's Clear button wired to it.

  • On map-reading error: save a screenshot locally to adapt the algo. New debug_capture.pysave_map_read_failure() writes the PNG + the solver's rejection reason under $XDG_DATA_HOME/fenigma/debug_captures/failures/, wired into app.py's _start_map_import.

  • When the user corrects the grid, store screenshot + ground truth too. debug_capture.save_grid_correction(), wired into _accept_grid: fires only when the accepted GridSolution isn't the one auto-solve produced (the user actually dragged a handle in GridFixDialog), saves both solutions under .../debug_captures/corrections/.

  • Many map screenshots seem to get read as text; if nothing relevant is found, also store the image to check whether it was actually a map. debug_capture.save_maybe_map(), wired into _ocr_png: fires when a screenshot (not a plain-text paste) fell through to the OCR/text path and _merge_all found nothing at all. Saved under .../debug_captures/maybe_map/.

  • Unable to parse 3 given chat messages (Infantry "taking fire" fire- support requests). A different grammar from the existing Marine Garrison fire-support request: reversed shell word order ("Requesting X Shell" vs "X Shells requested"), a bare "before/by

  • When the user deletes/replaces the map screenshot, capture whatever units they confirmed as ground truth for it. ScreenshotImport.baseline_targets/baseline_allies (a snapshot of board.targets/board.allies taken when the grid is confirmed, Target/Ally are identity-hashable so these are plain sets of the live objects) let app.py tell "added while this screenshot was up" apart from "was already on the board". Proposal also now records confirmed_type (what the user actually accepted it as, which can differ from the detector's own guess via "Accept as..."). All of it -- every proposal's accept/reject/undecided verdict, plus every target/ally added with no matching proposal at all (a manual add or an OCR-text merge run alongside the screenshot) -- is saved via debug_capture.save_marker_ground_truth() under .../debug_captures/marker_ground_truth/. Wired into all three places a screenshot stops being "the active one": explicit drop, a new screenshot pasted straight over it, and window close.

Needs more scope / your input before I keep going

  • "Accept as" button on proposed targets doesn't work. Read through the whole path (app.py's _open_proposal_menu/ _accept_proposal, map_import.py's Proposal/ScreenshotImport, grid_widget.py's proposal hit-testing) end to end and couldn't find a static defect — map_vision.GridSolution.cell_of already clamps sub_x/sub_y into 0..9 before a Proposal is even built, so the obvious "coord fails to construct, accept silently no-ops" theory doesn't hold up either. I'd need a repro (which button exactly, screenshot of the popover, does anything happen — toast, marker staying put, wrong type applied) to chase this further rather than guess.
  • Enemy type detection needs to be more robust; read the entity id label so dedup is reliable; detect death from the log. All three are real computer-vision/OCR feature work (better marker classification in map_vision.py's classify_marker, a new OCR pass reading each marker's id label off the map screenshot, and a "# Destroyed" log-scan tied into a dedup key that includes that read id) rather than bugs with a small fix. Worth its own pass once there's a batch of the debug_capture failure/maybe_map screenshots above to develop against.