Every fix here was verified directly (numeric checks against the view math, rendered screenshots, GTK smoke tests), not just reasoned about, after the previous zoom/square pass shipped with real problems still in it. Map fixes: - Off-viewport entities bled through onto the canvas when zoomed in: _km_to_px() has no inherent bound, an entity actually elsewhere on the map still got projected and drawn if the result happened to land within the canvas's pixel bounds. Clipped all marker/overlay/arrow/ scout-flight drawing to the grid's own visible rectangle. - Gridlines themselves bled into the label margin above/left of the grid: the line-drawing loop deliberately over-generated a couple of lines past the true viewport edge (meant to cover a trailing partial cell, which doesn't need its own line, just the whole-integer lines already bounding it). Switched to ceil/floor bounds that only ever produce in-range lines, verified directly against the view math for a fractional zoom/pan (no clip needed for this half, the bug was generating the wrong lines in the first place, not failing to hide them). - Square-cell letterbox padding stayed pixel-identical at every zoom level (verified: same padding at 1x/2x/5x), which meant the fixed bands ate a bigger and bigger share of an already-zoomed-in view. Once actually zoomed in with square_cells on, the viewport's own aspect now follows the canvas shape instead of staying locked to the full map's 20:10 ratio, so cells come out square with zero letterboxing rather than fixed padding. The whole-map view (zoom=1) is unchanged, that's the one case that has a real reason to keep the fixed 20:10 shape. - Added drag-to-pan, active once actually zoomed in (at 1x the whole map's already on screen, nothing to pan to). Guards against a drag's release also firing as a click-select via a small pixel threshold. Popover cleanup (Nest/Spotters/Reference Points/Targets): - Removed each popover's own 'Load all from screenshot' bulk button and every row's individual 'set this one from screenshot' button: the universal clipboard button in the header already re-parses a fresh screenshot/paste and merges it into everything it recognizes by name, these were redundant category- and item-specific ways to trigger the exact same merge. Deleted the now-dead _set_*_from_screenshot_info() methods along with them. - The trailing 'Add spotter'/'Add RP'/'Add target'/'Add scout flight' rows are now a single full-width button (new _add_row() helper) instead of the full two-button _row() layout, there's nothing to screenshot into for something that doesn't exist yet. Verified: full pytest suite, direct numeric check that no gridline lands outside the grid rectangle at a fractional zoom/pan, a rendered screenshot at 4x zoom confirming off-viewport entities no longer appear and gridlines terminate cleanly at the edges, and a full-app smoke test exercising drag-pan end to end including the post-drag click-suppression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| assets/icons | ||
| docs | ||
| src/fenigma | ||
| tests | ||
| .gitignore | ||
| icon_alt.png | ||
| icon.png | ||
| install.sh | ||
| pytest.ini | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| run.sh | ||
| showcase.png | ||
FeNigma
Fe (iron) + enigma, we solve the geometric puzzles.
A companion app that mostly automates IRON NEST: Heavy Turret Simulator for you. Select the game's typewriter orders (selecting text in-game copies it to your clipboard automatically) and it solves the geo puzzle and the trajectory math, handing you ready-to-fire commands: elevation, azimuth, number of powder charges. Screenshotting works the same way when a selection isn't practical. Select or screenshot the field log the same way and it picks up kills and newly-spotted units automatically. You can also plan strikes and scout flights of your own. Pure screen-reading, no game files touched, no input injected.
What it does
- Reads orders, solves the geometry. Copy (or screenshot) the in-game typewriter text and it parses absolute grid refs and relative bearing/distance descriptions, then resolves everything into map coordinates, chained clues ("Bearing 293 from Alpha") included. The map shows its work: the actual bearing lines/circles behind each resolved position. When a description is genuinely ambiguous (two intersections), both candidates are shown instead of guessing.
- Calculates the shot. Every resolved target gets a live firing card: elevation, azimuth, and minimum powder charge, computed from the Nest.
- Tracks the battle. A second copy/screenshot of the field log marks units destroyed and folds in newly-spotted contacts, merging with what's already known instead of duplicating it.
- Plans strikes. Drop a strike anywhere on the map and pick a shell to preview its blast radius before committing.
- Plans scout flights. Click the map to plot a scout flight's sweep path: it anchors to the large grid square you're pointing at and reads the heading off exactly where in that square you click, previewed live before you commit.
- Watches the clipboard for you. Toggle auto-watch and every new screenshot or copied intel text gets read and merged automatically, no manual fetch between orders.
Install
./install.sh
Sets up a venv for the Python deps (Pillow, numpy, pytesseract) and checks for the system packages that pip can't install: GTK4/libadwaita bindings and tesseract. If either is missing it prints the package names for your distro and stops, install those and re-run.
Run
./run.sh
Uses the venv from install.sh if one exists, otherwise falls back to system python3. GTK apps with this app ID are single-instance, if a run gets killed uncleanly it can leave a zombie registered on D-Bus and silently no-op the next launch. If ./run.sh seems to do nothing, pgrep -af fenigma and kill any stragglers first.
Tests
pip install -r requirements-dev.txt
pytest
Regression coverage for every intel-text format the OCR pipeline understands and the solver's geometry, in tests/. Run this before trusting a change to ocr.py/solver.py, several of the formats have collided with each other in non-obvious ways before.
Stack
GTK4 + libadwaita (PyGObject) for the UI, Tesseract (via pytesseract) for OCR, Pillow/numpy for preprocessing. Details on the coordinate system, OCR formats, and solver internals live in code comments (solver.py, ocr.py, models.py) rather than here.
