A companion app that mostly automates the game 'Iron Nest' for you.
Go to file
Dominik Roth e585fc5428 Map: square-cell toggle and scroll-wheel zoom
Refactored GridCanvas's geometry around a single _View namedtuple
(cell_w/cell_h/grid_w/grid_h/pad_x/pad_y/viewport origin/visible
extent) instead of threading cell_w/cell_h/grid_h separately through
every draw and hit-test method, that's what makes the two new features
below tractable without a parameter explosion.

- Square-cell toggle (header button, 'view-grid-symbolic'): forces
  cell_w == cell_h, letterboxing (padding) whichever axis has leftover
  space instead of stretching cells to fill the widget. Off by default,
  recovers the exact previous stretch-to-fill behavior.
- Scroll-wheel zoom: 1x (the whole 20x10 map, the old fixed behavior)
  up to 10x, anchored at the cursor's last known position so the km
  point under it stays under it as the zoom level changes, panned/
  clamped so the viewport never hangs off the grid's edge. Grid lines,
  column/row labels, and every marker only draw for the visible
  viewport, not always the full 20x10 grid.

Also two bugs found and fixed along the way:
- The header's cursor-location readout showed AZ/distance-from-nest
  numbers even when the cursor was off the map entirely:
  bearing_deg_point()/distance_km_point() are happy to compute on any
  raw km point, on- map or not, only the coord label itself checked
  bounds. Now the whole readout is just 'off map' whenever the cursor
  genuinely isn't over the grid.
- That bounds check initially reused solver.point_to_coord()'s own
  tolerance, which deliberately forgives up to 0.5km past an edge
  (rounding slop for noisy OCR'd coordinates), the wrong call for 'is
  the mouse over the map', a cursor visibly off the drawn grid still
  passed it. The cursor readout now uses a strict 0<=col<=COLS/
  0<=row<=ROWS check instead.

Verified with GTK smoke tests: zoom in/out and pan-anchoring math,
square-cell letterboxing padding, hover/hit-testing, the toggle wired
end-to-end through the real header button, and the full app launching
and surviving the exact scenario that crashed it earlier in this same
session (an incomplete mid-refactor commit referenced _on_scroll before
it was defined, caught immediately by re-running the app, fixed by
finishing the refactor properly instead of patching around it).

Also, on the firing card: swapped the shell icon to come after the
powder-charge segments instead of before (per feedback), and gave the
assignment cycle button (L/R/-) the same 'image-button' style class its
icon-only siblings get automatically, it was visibly wider than them
for carrying a text label instead of an icon.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:43:35 +02:00
assets/icons Shell icon picker: fix real sizing bugs, crop icon padding, inline grid for Add Strike 2026-08-09 19:28:41 +02:00
docs docs: map-vision pipeline status + BFS grid-growing plan 2026-08-09 14:49:40 +02:00
src/fenigma Map: square-cell toggle and scroll-wheel zoom 2026-08-09 19:43:35 +02:00
tests Fix explain_unresolved() not excluding toleranced bearings like solve_location() does 2026-08-09 18:28:48 +02:00
.gitignore Add game-extracted UI icons: target markers, RP letters, shell set, nest, scout plane 2026-08-09 18:54:27 +02:00
icon_alt.png Rewrite README short, rename project to FeNigma, add icon/showcase 2026-08-08 20:15:14 +02:00
icon.png Rewrite README short, rename project to FeNigma, add icon/showcase 2026-08-08 20:15:14 +02:00
install.sh Add install.sh, run.sh uses the venv when present 2026-08-08 20:18:15 +02:00
pytest.ini Add a real pytest suite: 21 tests covering every OCR format + solver geometry 2026-08-09 18:20:27 +02:00
README.md Add a real pytest suite: 21 tests covering every OCR format + solver geometry 2026-08-09 18:20:27 +02:00
requirements-dev.txt Add a real pytest suite: 21 tests covering every OCR format + solver geometry 2026-08-09 18:20:27 +02:00
requirements.txt Sweep em-dashes out of code, docstrings, and requirements.txt 2026-08-08 20:27:53 +02:00
run.sh Rename package/app to FeNigma consistently 2026-08-08 20:20:43 +02:00
showcase.png Rewrite README short, rename project to FeNigma, add icon/showcase 2026-08-08 20:15:14 +02:00


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.

Showcase

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.