Rewrite README short, rename project to FeNigma, add icon/showcase
Shorter pitch focused on what the app actually automates for the player: screenshot orders -> geo puzzle + trajectory solved -> ready fire commands; screenshot the field log -> kills/new contacts picked up automatically; strike planning with blast-radius preview. Mentions the map's geometric derivation overlays and ambiguous-intersection handling. Drops the old deep technical dump in favor of pointing at code comments for internals. Adds icon.png (used in the header) and showcase.png (embedded screenshot); icon_alt.png kept as an unused alternate for now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
8bec273b3f
commit
07c89c5be4
175
README.md
175
README.md
@ -1,20 +1,38 @@
|
||||
# ironnest-assist
|
||||
<h1 align="center">
|
||||
<img src='icon.png' width="150px" style="margin-top: 20px;">
|
||||
<br>
|
||||
<b>FeNigma</b>
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
Screen-reading helper for **IRON NEST: Heavy Turret Simulator**. Tracks the
|
||||
Nest, Spotters, Reference Points, and Targets on a map matching the game's
|
||||
grid, settable by hand, by a free-text description, or by OCR-ing a
|
||||
clipboard screenshot of the typewriter order. A solver resolves relative
|
||||
descriptions ("Bearing 293 from Alpha") into absolute coordinates once
|
||||
their dependencies are known. Single-player QoL tool — no game files
|
||||
touched, no input injected.
|
||||
<p align="center"><b>Fe</b> (iron) + enigma — we solve the geo puzzles.</p>
|
||||
|
||||
## Stack
|
||||
A companion app that mostly automates **IRON NEST: Heavy Turret Simulator**
|
||||
for you. Screenshot the game's typewriter orders and it solves the geo
|
||||
puzzle and the trajectory math, handing you ready-to-fire commands —
|
||||
elevation, azimuth, number of powder charges. Screenshot the field log and
|
||||
it picks up kills and newly-spotted units automatically. You can also plan
|
||||
strikes of your own, picking a shell for the blast radius you need. Pure
|
||||
screen-reading — no game files touched, no input injected.
|
||||
|
||||
- **GTK4 + libadwaita** (via PyGObject) — native GNOME look on Linux. GTK4
|
||||
itself also runs on Windows/macOS (Win32/Quartz backends), just without
|
||||
full Adwaita chrome there.
|
||||
- **Pillow / numpy** — image preprocessing for OCR.
|
||||
- **pytesseract + tesseract** — text OCR of the typewriter order.
|
||||

|
||||
|
||||
## What it does
|
||||
|
||||
- **Reads orders, solves the geometry.** Paste/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 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.
|
||||
|
||||
## Run
|
||||
|
||||
@ -27,128 +45,9 @@ 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 ironnest_assist`
|
||||
and kill any stragglers first.
|
||||
|
||||
## Coordinate system
|
||||
## Stack
|
||||
|
||||
Large grid: `X` in `A`–`T` (20 cols), `Y` in `1`–`10` (10 rows, row 1 at
|
||||
the bottom). Sub-grid within a cell: `x`, `y` in `0`–`9`. **One large cell
|
||||
is 1km × 1km** — that scale is what the solver's bearing/distance math
|
||||
runs on. Quick keyboard entry in the coord dialog: type e.g. `C433`
|
||||
(letter + 3 digits) to fill and submit in one go — `0` for the `Y` digit
|
||||
means `10`.
|
||||
|
||||
### Location: coord and/or description
|
||||
|
||||
Every entity's `location` (`models.py`) independently holds:
|
||||
- `coord` — a resolved absolute position.
|
||||
- `desc_raw` + `clues` — a raw free-text description and the `Clue`s
|
||||
parsed out of it (`reference`, `bearing_deg`, `distance_km`), each
|
||||
naming another entity it's relative to.
|
||||
- `potential_coords` — set instead of `coord` when the solver found the
|
||||
position genuinely ambiguous (see below); shown on the map, never
|
||||
chained into further resolution.
|
||||
|
||||
These aren't either/or — setting a coord never erases an existing
|
||||
description (and vice versa), since a coord can arrive via OCR *after* a
|
||||
description was already on file, or a description can be added as extra
|
||||
context for an already-placed entity.
|
||||
|
||||
### Solver (`solver.py`)
|
||||
|
||||
Walks every RP/Target's clues, resolving whatever it can against
|
||||
currently-known positions, repeating until nothing new resolves (handles
|
||||
chains, e.g. `AmmoCache#3` → `AmmoCache#2` → `Alpha`/Spotters). Handles:
|
||||
|
||||
1. One clue with both bearing *and* distance from a resolved reference —
|
||||
always unique.
|
||||
2. Two bearing-only clues from different references — ray/ray
|
||||
intersection, always unique (unless parallel).
|
||||
3. A bearing-only + a distance-only clue from different references —
|
||||
ray/circle intersection. A ray can cross a circle at 0, 1, or 2 points;
|
||||
when there are 2, that's genuinely ambiguous — both candidates go into
|
||||
`potential_coords` instead of picking one, and nothing depends on them
|
||||
further.
|
||||
|
||||
Two distance-only clues (circle/circle, also up to 2 solutions) isn't
|
||||
handled — even less to disambiguate with.
|
||||
|
||||
Runs automatically after every mutation (`MainWindow._refresh()` is the
|
||||
single choke point), so newly-unblocked descriptions resolve immediately.
|
||||
|
||||
### Map overlays
|
||||
|
||||
Every entity row has an eye-icon (hide from map) and a star-icon (always
|
||||
show its bearing/distance overlay, vs. only on hover). Hovering a marker,
|
||||
or pinning it with the star, draws its clues: a yellow line for a
|
||||
bearing-only clue, a white circle (radius = distance) for a distance-only
|
||||
clue, a yellow arrow when a single clue has both. Ambiguous entities draw
|
||||
both `potential_coords` as hollow dashed markers. Targets also have an
|
||||
alive/destroyed toggle (checkmark icon) — dead ones show struck-through in
|
||||
their list and dimmed on the map.
|
||||
|
||||
## OCR
|
||||
|
||||
Two sub-pipelines, planned: **text** (typewriter orders, implemented) and
|
||||
**image** (map icons/markers, not started). Text OCR: screenshot →
|
||||
grayscale → divide by a heavily-blurred copy of itself to flatten the
|
||||
game's light-falloff vignette → threshold → `tesseract --psm 6` → parsing.
|
||||
See `src/ironnest_assist/ocr.py`.
|
||||
|
||||
Two text formats parsed, both fuzzy/typo-tolerant (`difflib` keyword
|
||||
matching, digit/letter OCR-mixup normalization `O`/`0` `I`/`l`/`1` `S`/`5`
|
||||
`B`/`8` etc., and every separator — `#`, `:`, block-terminating `.` —
|
||||
treated as just as corruptible as any other character, never matched
|
||||
literally):
|
||||
|
||||
1. **Absolute grid refs** — `IRON NEST - C4 3:3`, `Spotter#1 - F7 7:1` →
|
||||
`nest_coord`, `spotters`.
|
||||
2. **Field-intelligence blocks** — named entity header (`Target#5`,
|
||||
`AmmoCache#1:`, `Reference Point Alpha:`) followed by one or more
|
||||
`Bearing`/`Distance`/combined clue lines, terminated by a blank line or
|
||||
lone `.` → `reference_points`, `targets`, each as (raw text, parsed
|
||||
clues). The same clue grammar (`ocr.parse_clues_from_text`) also backs
|
||||
the manual "Description" tab in the coord-entry dialog.
|
||||
|
||||
- Header clipboard button (or `Ctrl+P`): universal fetch — merges
|
||||
everything recognized into the board.
|
||||
- A category's "from screenshot" actions extract only that category;
|
||||
toasts an error if the requested item isn't found in the screenshot.
|
||||
- "Load all from screenshot" / a list item's screenshot action both use
|
||||
the same merge: same name/id → update in place, new → add.
|
||||
|
||||
Known limitation: OCR on a *heavily skewed/rotated* screenshot degrades
|
||||
badly — psm 6 assumes a roughly-upright text block, and deskewing isn't
|
||||
implemented. A front-on-ish shot works well. Considered switching OCR
|
||||
engines (vision-LLM structured extraction, PaddleOCR/EasyOCR) instead of
|
||||
continuing to special-case Tesseract's misreads one at a time — staying
|
||||
on Tesseract per your call for now.
|
||||
|
||||
## Ammunition (`shells.py`)
|
||||
|
||||
`Shell` enum from High Command's field reference — description, blast
|
||||
radius in km (`None` where not yet measured), and whether it's a
|
||||
"standard" (unlocked-by-default) type. Feeds the firing-solution
|
||||
calculator later (e.g. AP is required for underground supply caches per
|
||||
the typewriter note).
|
||||
|
||||
## Firing commands panel
|
||||
|
||||
Header button (top-right, `sidebar-show-right-symbolic`) slides in a
|
||||
right-hand sidebar via `Adw.OverlaySplitView` — it shares the window's
|
||||
width with the map (narrows it), rather than overlaying on top. One
|
||||
placeholder card per board target (`firing_panel.py`): shell + quantity,
|
||||
elevation/azimuth readout, confirm/cancel — all dummy values for now,
|
||||
since the actual aiming math isn't known yet (see open questions).
|
||||
|
||||
## Status
|
||||
|
||||
Board data model, map view + overlays, exact/description coordinate
|
||||
input, save/load to JSON, text OCR for both known typewriter formats, the
|
||||
bearing/distance solver, and the firing-commands panel shell are all
|
||||
working. Still open: image OCR sub-pipeline, deskewing, circle/circle
|
||||
ambiguous case, and the actual firing-solution calculator (turret aiming
|
||||
math still unknown — the panel currently just shows placeholder cards).
|
||||
|
||||
## Open questions
|
||||
|
||||
- What inputs the firing-solution math actually needs, beyond target
|
||||
position and shell choice
|
||||
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.
|
||||
|
||||
BIN
icon_alt.png
Normal file
BIN
icon_alt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
showcase.png
Normal file
BIN
showcase.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 226 KiB |
Loading…
Reference in New Issue
Block a user