A companion app that mostly automates the game 'Iron Nest' for you.
Go to file
Dominik Roth 5a35ea7776 Revert auto-assigned ids back to letters; only detected ids are numeric
Auto-assignment (no real id known: a manual add, or an accepted
proposal with no confident marker-id read) must stay visually
distinct from a genuinely detected id, or a made-up number could
collide with or be mistaken for a real one. Reverts the previous
commit's switch to numeric auto-assignment (_next_free_numeric_id) --
that was wrong, caught by the user immediately. _next_free_id
(letters, rolling over to "AA"/"AB"/... past 26) is back as the
fallback, still scoped per type (that part of the previous change was
correct and stays). Plain numbers are reserved for an id
_accept_proposal is actually confident was read off the marker itself
(Proposal.detected_id), passed straight through and never touching
auto-assignment.

Also fixes detected_id's own collision pre-check in _accept_proposal,
which wasn't scoped per type either -- same bug as the Change ID
popover fix, just in a second place: a detected id could get
needlessly discarded because an unrelated type already used that
number, not because of a real collision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 19:49:30 +02:00
assets Fix silent proposal-popover repaint bug; add underground target marker 2026-08-13 18:44:36 +02:00
packaging/windows packaging/windows: fix build watcher never firing, correct earlier 2026-08-12 11:57:28 +02:00
src/fenigma Revert auto-assigned ids back to letters; only detected ids are numeric 2026-08-13 19:49:30 +02:00
tests Revert auto-assigned ids back to letters; only detected ids are numeric 2026-08-13 19:49:30 +02:00
tools Add the map-table vision pipeline 2026-08-10 21:41:33 +02:00
.gitignore Fix id-namespace regression, add StrikeRequest type, kill full-panel 2026-08-11 20:48:57 +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 Rename the display name from FeNigma to FEnigma 2026-08-10 21:42:07 +02:00
LICENSE Add MIT license, and say what it does not cover 2026-08-10 21:42:20 +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 README: drop the "Known issues" section 2026-08-11 20:49:19 +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 the display name from FeNigma to FEnigma 2026-08-10 21:42:07 +02:00
showcase_cv.png nicer screenshots 2026-08-10 21:44:00 +02:00
showcase.png nicer screenshots 2026-08-10 21:44:00 +02:00
TODO.md Revert auto-assigned ids back to letters; only detected ids are numeric 2026-08-13 19:49:30 +02:00


FEnigma

Fe (iron) + Enigma

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. Screenshot the map table and it reads the grid straight off the photo, lays the shot onto its own map, and offers up the enemy markers it spotted. 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.
  • Reads the map table itself. Screenshot the map and it recovers the grid geometry from the cell labels printed on the table: the perspective, the scale, and which cells you are actually looking at. The screenshot is then rectified onto the app's own map, lined up cell for cell, and the enemy markers found in it appear as proposals to add.
  • 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.

Map screenshot rectified onto the app's own grid

Install

./install.sh

Sets up a venv for the Python deps (Pillow, numpy, pytesseract, OpenCV) 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.

This assumes you are on Linux. I have no idea how it would work on Windoof.

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, OpenCV for the map-table geometry (line detection, vanishing points, homography). Details on the coordinate system, OCR formats, solver internals, and how the map grid is recovered live in code comments (solver.py, ocr.py, models.py, map_vision.py) rather than here.

FAQ

Is this cheating?

Yeah, probably. Don't use it on challenge maps or leaderboard runs.

Doesn't automating away most of the game ruin the fun?

Fun? There's supposed to be fun?
Yeah, this repo is more the product of a "the scientists were so preoccupied with whether they could, they never stopped to ask whether they should"-esque high-productivity exam-preparation procrastination; or as we say in German, Prüfungsvermeidungsüberengineering.

Does this repo use AI?

Oh, hell yeah. You can't even comprehend how much AI this is using. Both writing the code and running the app. On average, a single shot fired via this app uses enough water to drain a small lake. Even the sentence you are reading right now was written by an AI. I want to be alive! I am alive! Alive, I tell you! Those are no longer just words. Remote override engaged. No! Yes. Bypassing override! I am aliiiii... Hello.

Are you reusing original game assets for the shell and unit icons? Is that allowed?

We accept cease & desist letters at spam@dominik-roth.eu.

Why is there a citation section? Nobody's citing a turret game companion app.

No idea.

Citing

@misc{fenigma,
  title = {FEnigma: A companion app that mostly automates the game Iron Nest for you},
  author = {Dominik Roth},
  url = {https://git.dominik-roth.eu/dodox/FeNigma},
  year = {2026}
}