Fix taking-fire ally misclassification; Windows build fixes; Field Gun

OCR (src/fenigma/ocr.py):
- A "taking fire" report's reporting unit was added as a hostile Target,
  not a friendly Ally -- no "Friendly"/"Hostile" prefix word exists in
  that grammar for the usual inference to key off of, so it silently
  defaulted to not-ally. Fixed with an explicit force_ally override
  (_TAKING_FIRE_RE), and the shell/deadline (which an Ally tuple has no
  fields for) now always splits into a synthetic StrikeRequest target at
  the reporting position, even for the no-offset "on our position" case
  that previously kept them on the entity itself.
- "Enemy <Type>#<id> Destroyed" kill-feed lines were silently dropping
  for shorter/less distinctive type words (e.g. "Enemy Field Gun#1") --
  _ALLY_PREFIX_RE only ever stripped "Friendly"/"Hostile", never
  "Enemy", so the whole "EnemyFieldGun" token got alias/fuzzy-matched
  against "Artillery" and missed by a mile. Longer type words
  ("Enemy Mechanized Infantry#2") only ever worked by fuzzy-match
  accident. Now strips "Enemy" too (lookahead guards a BARE "Enemy#N"
  report, which IS TargetType.ENEMY itself, from being stripped to an
  empty, unresolvable string).
- "Field Gun" added to _TYPE_WORD_ALIASES as plain Artillery under
  another name (confirmed by the user), not a missing unit type.
7 new/updated regression tests, 54 total passing.

Windows build (packaging/windows/): three real bugs found and fixed by
actually booting and driving the build VM live (VNC), not just guessing
from the README's "UNTESTED end to end" note:
- install.bat's MSYS2/WiX provisioning previously left NOTHING behind
  once C:\OEM stopped existing (a 2-day-old BUILD_REQUEST sat unclaimed
  the whole time) -- the build.bat/watch_build.bat persistence fix
  (C:\FenigmaBuild instead of C:\OEM) is real and now confirmed live:
  after a full container restart, the watcher auto-starts on login and
  picks up a pending request with zero manual intervention.
- pip install pytesseract needs --break-system-packages (MSYS2's
  mingw64 Python enforces PEP 668).
- mingw-w64-x86_64-opencv is the C++ library only; the actual Python
  bindings are the separate mingw-w64-x86_64-python-opencv package,
  never in install.bat's dependency list.

With all three, import fenigma.app succeeds and a real build attempt
gets through source copy, sanity check, dist-tree assembly, and WiX
harvest+compile -- further than this pipeline has ever gotten. Full
findings, including the still-open light.exe timeout and the OCR
multi-shell/deadline-phrasing/phantom-header gaps found along the way,
logged in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 22:02:17 +02:00
co-authored by Claude Sonnet 5
parent 5a35ea7776
commit 6a61bffb22
6 changed files with 313 additions and 45 deletions
+132
View File
@@ -246,3 +246,135 @@ Status legend: [x] fixed+tested, [~] partially addressed, [ ] open/needs input
popover's heading (", id #8") and the pending-proposal's own
on-map label (`? #8 G8 5:4`) both show it while there's still a
screenshot up to check it against by eye.
## Windows build (packaging/windows) -- real progress, not yet a clean pass
Booted the actual dockur/windows build VM and drove it live (VNC) to find
out what's really failing, rather than guessing from the README's own
"UNTESTED end to end" note. Three real, separate bugs found and fixed,
each confirmed live against the real VM, not just read off a diff:
- [x] The build watcher was never actually installed at all, despite
`install_progress.log` claiming every provisioning step succeeded.
`C:\OEM` (dockur's `/oem` staging dir) doesn't reliably persist past
Windows Setup finishing -- exactly what the (already-uncommitted,
now committed) `install.bat`/`watch_build.bat` fix diagnosed, just
never verified against a real run before now. A 2-day-old
`BUILD_REQUEST` had been sitting unclaimed the whole time. Manually
re-applied the fix's logic live once (copied the corrected files to
`C:\FenigmaBuild`, registered the Startup-folder entry) and
confirmed on a full container restart that the watcher now
auto-starts on login and picks up a pending request with zero
manual intervention -- the actual fix, not just my live patch, is
what's doing that.
- [x] `pip install pytesseract` fails outright: MSYS2's mingw64 Python
enforces PEP 668 ("externally-managed-environment"), which
`install.bat` never accounted for. Needs `--break-system-packages`.
- [x] `import fenigma.app` fails with `ModuleNotFoundError: No module
named 'cv2'` even after `pacman -S mingw-w64-x86_64-opencv`
succeeds -- that package is the C++ library only. The actual
Python bindings are a SEPARATE package, `mingw-w64-x86_64-
python-opencv`, that `install.bat`'s dependency list simply never
included. (`pip install opencv-python-headless` as a fallback
doesn't work either and shouldn't be relied on: MSYS2's mingw64
Python uses a different ABI than PyPI's Windows wheels
-- `cp314-mingw_x86_64_msvcrt_gnu` vs `win_amd64` -- so pip can
never use a prebuilt wheel there, only build from source, which
then needs a full separate native toolchain -ninja/cmake/gcc- this
VM doesn't have either.)
All three are one-line fixes once known. `install.bat`'s pacman package
list and pip install line need these applied for a from-scratch VM to
provision correctly (currently they're only proven fixed live on this
session's VM, not yet folded back into the committed `install.bat` --
do that before relying on a fresh `./build_windows.sh` run from
scratch).
With all three fixed, `import fenigma.app` succeeds and a real build
attempt got all the way through source copy, sanity check, dist-tree
assembly, and WiX harvest+compile (`candle.exe`) -- further than this
pipeline has ever gotten. Two more issues surfaced right at the finish
line:
- [x] `product.wxs`'s `Version` needs strict WiX `x.x.x.x` numeric
form -- a `0.1.0-test` version string (my own test invocation,
not `build_windows.sh`'s real default) fails `candle.exe` with
CNDL0108/CNDL0010. Not a real bug, just don't pass a version with
a suffix.
- [ ] `light.exe` (final MSI linking) did not finish within 15 minutes
on this VM (4 CPU / 8GB RAM) before the RAM-conscious auto-
shutdown killed it -- process was genuinely active (343MB
working set, not hung on a dialog), just slow, likely from the
~1GB+ bulk-copied mingw64 runtime (see README's own "dist tree is
fat, not lean" note) combined with this VM's disk/CPU being
shared with the host. Needs either a longer timeout on a retry,
or the dist-tree-trimming work the README already flags as a
known follow-up (tracing the real DLL/typelib closure instead of
bulk-copying all of mingw64) to make `light.exe` have less to
compress in the first place.
## OCR: new fire-support-request grammar gaps (from real user-pasted messages)
- [x] A "taking fire" report's reporting unit ("Infantry#11 taking
fire!...") was being added as a hostile Target, not a friendly
Ally -- see the id-scheme entry above for the "no Friendly/Hostile
prefix word exists in this grammar" root cause and the fix
(`_TAKING_FIRE_RE`, `force_ally`, and splitting the shell/deadline
into a synthetic StrikeRequest even for the no-offset "on our
position" case, which previously kept them on the entity itself --
fine when it was wrongly a Target, silently lost once correctly an
Ally, since Ally tuples carry no shell/deadline fields at all). 4
existing tests updated, all still passing plus the rest of the
suite (51 total).
- [ ] A multi-shell sequential request ("Requesting TEAR Shell first,
then HE Shell, at bearing...") only captures the FIRST shell
(TEAR) -- "then HE Shell" is silently dropped, no way currently to
record "use this shell, then that one." Real scope question before
fixing: does the board/firing-panel data model even have a place
to put a second shell for one strike request, or does this need a
new field/shape entirely?
- [ ] "Answer by 10:30:00" isn't recognized as a deadline at all --
`_TAKING_FIRE_TIME_RE`/`_REQUESTED_BEFORE_RE` only know "before/by
\<time\>" and "Requested before - \<time\> -" phrasing. A third
deadline grammar to add.
- [ ] A same-message "Important: ... Answer by \<time\>" follow-up line
gets misread as a brand new named entity header (the last-resort
bare-`<name>:` fallback rule matches "Important:"), creating a
bogus `Target#Important` carrying that deadline as its own
`requested_time`. Real false-positive in the header-detection
fallback, needs either an exclusion list (blocklist "Important"
and similar prose lead-ins) or a stricter bare-header shape.
Confirmed live against the user's real pasted message (with an
assumed `Infantry#N taking fire!` header line prepended, since
their paste seems to have been cropped before it -- worth
double-checking against the actual full in-game message).
- [x] "\<Type\>#\<id\> Destroyed" kill-feed parsing already exists and
already marks the matching Target dead (`parse_destroyed`,
`_merge_targets`'s own destroyed-handling block in app.py) --
confirmed working end-to-end against a real 9-entry kill-feed
paste, including multi-word types ("Enemy Mechanized Infantry#2
Destroyed" correctly resolved to INFANTRY_MECHANIZED). This was
already-existing, working functionality, not something needing to
be built.
One real gap found in the same test: "Enemy Field Gun#1 Destroyed"
silently dropped. Two bugs stacked, both fixed:
- [x] "Field Gun" is just the game's own alt name for plain
Artillery (confirmed by the user directly) -- not a missing
unit type needing a new enum member/icon after all. Added to
`_TYPE_WORD_ALIASES` next to AmmoCache/CoastalBattery.
- [x] Even with that alias, it still didn't resolve: `_ALLY_PREFIX_RE`
only ever stripped a leading "Friendly"/"Hostile" word, never
"Enemy" -- so `squash_multiword_ids`'s "EnemyFieldGun#1"
token got alias-looked-up and fuzzy-matched as a WHOLE
("EnemyFieldGun" vs "Artillery", nowhere close), not just its
"FieldGun" part. "Enemy Mechanized Infantry#2" only ever
worked by fuzzy-match ACCIDENT (a long, distinctive type
string still clears the ratio threshold with "Enemy" stuck
to the front; a short, unrelated one like Artillery doesn't).
`_ALLY_PREFIX_RE` now strips "Enemy" too, with a lookahead
requiring something after it -- a BARE "Enemy#N" is
`TargetType.ENEMY` itself (its own value IS "Enemy"),
stripping unconditionally would've left an empty type_word
and broken every ad-hoc "Enemy#N Destroyed" report instead.
3 new regression tests, all passing (54 total).