diff --git a/README.md b/README.md index cae2cde..45f3487 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,38 @@ -# ironnest-assist +
Fe (iron) + enigma — we solve the geo puzzles.
-## 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. diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..a53e484 Binary files /dev/null and b/icon.png differ diff --git a/icon_alt.png b/icon_alt.png new file mode 100644 index 0000000..806b540 Binary files /dev/null and b/icon_alt.png differ diff --git a/showcase.png b/showcase.png new file mode 100644 index 0000000..f6e3335 Binary files /dev/null and b/showcase.png differ