A companion app that mostly automates the game 'Iron Nest' for you.
Go to file
Dominik Roth c273f57435 OCR: multi-word RP names, bare-name targets, FO-report triangulation
Three related fixes/additions, found together while working through a
batch of new intel formats:

1. squash_span_content() only handled the 'Enemy X Y' and 'Type#N'
   shapes, so a plain multi-word bold name ('The Mole', "Dockmaster's
   House") passed through untouched and got silently truncated at the
   first space by every downstream single-token assumption
   (_RP_HEADER_RE, the (\S+) clue-reference capture). Added a generic
   fallback: collapse any multi-word bold span into one alphanumeric
   token, UNLESS any word contains a digit, that's very likely a
   coordinate span ('C9 7:9') instead of a name, and squashing THAT the
   same way corrupted it into garbage ('C979') rather than a name, a
   real regression caught immediately by testing against nest/spotter
   parsing before committing.

2. A bare '<Name>:' header (no 'Reference Point'/'Enemy' keyword, no
   digit id, e.g. 'HMS Rockingham:') was previously dropped entirely,
   nothing recognized it at all. Added _BARE_NAME_HEADER_RE as the
   last-resort header check (colon required, not optional like every
   other header regex, nothing else anchors this match). Resolves to a
   Target (TargetType.UNKNOWN), not a Reference Point, a named thing
   giving its own clues is being spotted, not a fixed landmark.

3. Forward-observer reports ('FO#5 Audio report on HMS Rockingham:
   2.24km From I8 6:9'): each FO's position is a literal one-off
   coordinate, not a name referencing some known entity, and isn't
   meant to be tracked as a real board entity. parse_fo_reports()
   triangulates immediately using a throwaway scratch Board (reusing
   solve_location()'s exact geometry/priority) and keeps only the
   resulting coordinate, discarding every ephemeral FO position
   afterward, nothing leaks into the real board.

Also added a 'convert to Target/Reference Point' action (RP and
Target rows both), since bare-name-header classification is a guess
that can land in the wrong bucket, this fixes it without losing the
position/clues already worked out.

Verified against the exact reported examples plus the full existing
regression sweep (standard blocks, Enemy names, Listening
Post/Coastal Battery, Marine Garrison, nest/spotter parsing) and
through the real GTK merge flow.
2026-08-09 16:51:30 +02:00
docs docs: map-vision pipeline status + BFS grid-growing plan 2026-08-09 14:49:40 +02:00
src/fenigma OCR: multi-word RP names, bare-name targets, FO-report triangulation 2026-08-09 16:51:30 +02:00
.gitignore Initial commit: IRON NEST Assist 2026-08-08 20:01:50 +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
README.md README: clipboard text is the primary input now, document scout flights 2026-08-09 12:33:25 +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.

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.