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.
|
||
|---|---|---|
| docs | ||
| src/fenigma | ||
| .gitignore | ||
| icon_alt.png | ||
| icon.png | ||
| install.sh | ||
| README.md | ||
| 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.
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.
