A companion app that mostly automates the game 'Iron Nest' for you.
Go to file
Dominik Roth 3c80f93203 Import map screenshots into the board, and edit entities from the map
The existing clipboard button now routes images: a map-table shot goes to the
vision pipeline, anything else to the text OCR path as before. The decision
runs in a worker, since even the cheap pre-filter costs ~0.3s and solve()
takes 10-20s. solve() rejecting counts as "not a map" and falls through to
OCR, because it is the reliable verdict (0 false positives over 122 text
screenshots) where the pre-filter lets ~6% through; reporting a failure there
would mean a text screenshot never got read at all.

Grid first, units second. The one modal confirms or fixes the geometry only:
the screenshot with the reconstructed lattice drawn over it, plus four
draggable handles on one cell's corners. Four corners pin a homography
exactly (8 DOF, 2 equations each), and dragging any of them refits the whole
grid live. Detection deliberately does not run until this is accepted --
every unit position is expressed in grid coordinates, so detecting against a
grid about to be dragged would only be thrown away.

Once accepted the screenshot is rectified into board space and drawn as the
map's backdrop. Pre-warping is what makes it drawable at all: cairo has no
projective transform, but a rectified image places with a plain scale and
translate. Detected units then appear as proposals ON the map, drawn hollow
-- the same shape the map already uses for "this might be where it is", which
is exactly what a proposal is. Clicking one offers accept (with the detected
type or a corrected one) or reject; the header gains accept-all and
remove-screenshot, and removing the screenshot drops every proposal never
accepted, since they were only ever readings of it.

Separately, right-clicking any entity now opens an edit menu: change type,
change id, change position, delete. Which actions appear follows what the
entity actually has -- only Target/Ally carry a TargetType, Spotter's id is
an int, and the Nest is singular so it cannot be deleted. Changing an
existing target's type or id had no UI at all before this.

Also fixes warp_to_map, which composed only the lattice homography and
dropped the discrete (si,sj,du,dv) mapping that pins lattice indices to named
cells, so every automatically solved screenshot landed in the wrong place. It
happened to test fine because manual solutions have an identity mapping.
While there, the same routine had an off-by-one for a negative axis sign
(si*u+du runs from col+1 down to col across a cell, so floor() named the
neighbour); both now go through one shared GridSolution.grid_of.

Verified end to end through the real widgets on a fixture: grid phase yields
no proposals, four handles, a drag refits and still names cells correctly,
reset restores, a degenerate drag survives, accept warps to a 2000x1000
overlay, detection then yields proposals that hit-test, accept and reject
correctly, and removing the screenshot keeps accepted units only.

Completes the FEnigma rename in app.py (APP_ID, window title, class).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:41:56 +02:00
assets Add the map-table vision pipeline 2026-08-10 21:41:33 +02:00
src/fenigma Import map screenshots into the board, and edit entities from the map 2026-08-10 21:41:56 +02:00
tests Add the map-table vision pipeline 2026-08-10 21:41:33 +02:00
tools Add the map-table vision pipeline 2026-08-10 21:41:33 +02:00
.gitignore Add the map-table vision pipeline 2026-08-10 21:41:33 +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 Add the map-table vision pipeline 2026-08-10 21:41:33 +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.