rerender on assign/alive/shell, add Windows build tooling
- Board.add_target/add_ally's id auto-assignment used a bare
next(c for c in string.ascii_uppercase if c not in used), which
raises StopIteration once 26 entities of a group exist -- a real
crash confirmed via a live traceback, and a direct regression from
moving that sequence from per-type to per-group. This was the actual
cause of "Accept as"/"Accept all" silently doing nothing. Fixed with
_next_free_id(), which rolls over to two-letter ids instead of
raising.
- New TargetType.STRIKE_REQUEST: the bearing/distance-offset "taking
fire" fire-support request (see the earlier two-entity split) now
creates this instead of reusing STRIKE, so a radioed-in request is
never confused with a strike the player placed themselves. Same
crosshair icon, excluded from type pickers/dedupe like STRIKE.
- The "Accept as..." popover on a detected map marker now uses the
same icon grid the entity-edit "Change type" popover does (was a
plain unfiltered text list of every TargetType, which also wrongly
offered STRIKE/STRIKE_REQUEST as pickable).
- Firing panel: _cycle_assignment/_toggle_alive/_pick_shell no longer
route through app.py's full solver+dedupe+canvas+panel refresh --
none of the three can affect the solver or dedupe, and none change
which cards exist or their order (except _toggle_alive in
hide/sort_later mode). New FiringPanel._rebuild_one() rebuilds just
the one changed card; on_visual_change is a new, lighter callback
(just a map redraw) for the two of these three that actually affect
it. This was a real, confirmed lag source with many units on the
board: every click on any of these was previously rebuilding every
card of every target.
- Map right-click entity menu: added "Mark destroyed"/"Mark alive",
reusing the same cheap-refresh path (new
FiringPanel.refresh_after_alive_change).
- packaging/windows/: a from-scratch (untested against a real boot)
MSYS2 + WiX .msi build pipeline for Windows, driven from Linux via
dockur/windows (KVM-in-container), no Windows machine or GitHub
required. See its own README for status/caveats.
- New/updated tests: id-namespace sharing + the 26-entity overflow
regression (tests/test_models.py), StrikeRequest split
(tests/test_ocr.py), warp_to_map's img_scale param
(tests/test_map_vision_warp.py). 44/44 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reads a screenshot of the in-game map table and recovers where on the board
it is looking. The scene is a flat table under a perspective camera, so a
single homography describes grid<->screen exactly; that is fitted from line
evidence (LSD segments, vanishing-point RANSAC, 1-D lattice fits).
Line evidence alone cannot finish the job: the 1km cells and the 100m
subgrid are locally identical, so it fixes neither scale, axis assignment,
axis direction nor phase. Those come from the cell labels -- and the labels
are never *detected*, they are correlated where the grid says they must be
(9% in from a cell's left edge, 6% down from its top). Blob detection on
aerial-photo terrain finds texture, not glyphs; correlating a known template
at a known place has no such failure mode. The same score then ranks the
geometry hypotheses, since a wrong lattice puts the crop where no label is,
so one number resolves scale, axis assignment, direction, phase and anchor
together.
Markers are found by hostile/friendly colour plus an IoU test against an
ideal inscribed diamond, which cut a red-lit shot from 43 false positives to
2 while preserving every hand-verified count. Unit-type classification is
present but not yet reliable, and returns unknown rather than guessing.
Measured over the fixture set: 7 of 10 solve, each with 100% of its
ground-truth points in the correct cell (85/112 overall), residual spread
0.005-0.033 cells. The other three reject cleanly; none has ever produced a
plausible-but-wrong grid. Across 122 typewriter screenshots solve() accepted
none, which is what makes clipboard routing safe.
Fixtures: 10 map shots with hand-transcribed ground truth, plus 10
typewriter shots spanning 262-5366px for routing and future OCR tests. Map
shots wider than 2400px (the pipeline's own maximum working width) were
downscaled with their coordinates rescaled to match; re-measured afterwards,
the results are identical. Typewriter shots stay at native resolution
because OCR needs the text legible.
Drops docs/map_vision_plan.md and its WIP prototype: the design now lives in
the module docstring, next to the code it describes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pulled from the game's own GameAssets/Assets/Texture2D (gitignored as a
whole, not redistributed wholesale) into a tracked assets/icons/ so
they're actually usable in FeNigma's UI instead of us redrawing
equivalents:
- targets/enemy/, targets/friendly/: one marker per game unit type
('Enemy_*.png' / 'Friendly_*.png', the source files spell the latter
'Frendly_*.png', corrected on copy).
- reference_points/: the four lettered RP markers ('Refrence_Point_*.png'
in the source, typo corrected the same way).
- shells/: one icon per Shell enum member (shells.py), named to match
exactly so they're a direct Shell.name lookup, no mapping table
needed. A few source files don't carry the shell's short code in
their name (Mustard Gas_DemoBlocked -> MSTD, Tear Gas -> TEAR,
Propaganda -> PRPG, Phosgene -> PHGN, Nuclear -> ATMC), documented in
assets/icons/README.md.
- nest/IronNest.png: the Iron Nest/FDC unit icon.
- misc/ScoutPlane.png: fits the scout-flight planning feature.
Also commits the (already-made, previously uncommitted) .gitignore
entry for GameAssets itself, which is what makes pulling individual
icons out into a tracked directory sensible instead of contradictory.
GTK4/libadwaita desktop helper for IRON NEST: Heavy Turret Simulator.
Reads clipboard screenshots of the game's typewriter orders via Tesseract
OCR, parses absolute/relative entity positions, geometrically resolves
relative bearing/distance clues into map coordinates, and provides a
firing-commands sidebar with real ballistics (elevation/azimuth/powder
charge). Screen-reading only — no game files touched, no input injected.
- models.py: Board/Nest/Spotter/ReferencePoint/Target data model
- ocr.py: Tesseract preprocessing + typewriter-text parsing
- solver.py: bearing/distance geometric resolution (4 solvable shapes)
plus position-based dedup for generic contacts later identified more
specifically at the same resolved coord
- ballistics.py / shells.py: elevation/azimuth/charge math, shell types
- grid_widget.py: interactive map canvas (hidden entities and,
optionally, dead targets are excluded from the map view entirely
unless selected)
- firing_panel.py: drag-reorderable firing-command sidebar
- app.py: main window wiring it all together
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>