- 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>
115 lines
7.0 KiB
Markdown
115 lines
7.0 KiB
Markdown
# Bug Backlog (from user report, 2026-08-11)
|
|
|
|
Status legend: [x] fixed+tested, [~] partially addressed, [ ] open/needs input
|
|
|
|
- [x] 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.
|
|
- [x] 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).
|
|
- [x] 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.
|
|
- [x] "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.
|
|
- [x] 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.
|
|
- [x] 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.
|
|
- [x] On map-reading error: save a screenshot locally to adapt the algo.
|
|
New `debug_capture.py` — `save_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`.
|
|
- [x] 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/`.
|
|
- [x] 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/`.
|
|
- [x] 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 <time>" deadline (no "Requested"/
|
|
dashes), and either a direct "on our position at <coord>" or a
|
|
bearing/distance offset from that same inline position (not a named
|
|
board entity, so resolved directly via
|
|
`solver.point_from_bearing_distance` rather than through a Clue).
|
|
New extractors in `ocr.py`, wired into `parse_intel_blocks`'s
|
|
`flush()`. 3 new regression tests, all passing (`tests/test_ocr.py`).
|
|
|
|
- [x] 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
|
|
"<Type>#<id> 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.
|