Commit Graph
2 Commits
Author SHA1 Message Date
Patryk GenschandClaude Opus 5 f1b36f0e29 Decode IMG and ANN graphics, and follow the engine's file lookup rules
10 459 images and animations were listed but unviewable. They are now decoded to
PNG without OpenGL, so a script reference like IMGOVERLAY:FILENAME=NAKLADKA.IMG
shows the actual picture.

Decompression comes from :core (CLZW2Compression, CRLECompression) — that is the
hard part and there is no reason to have two copies of it. The header parsing had
to be written here, and not by choice: ImageLoader keeps its parser private, and
AnimoLoader, despite a signature that looks headless-friendly, builds Image
objects whose constructor creates a Texture straight away. Field layout mirrors
those loaders one for one, so catalogue and emulator read the same bytes the same
way. Pixels are RGB565/RGB555 plus a separate alpha byte, composed with ImageIO.

One quirk needed care: ImageLoader maps compression 4 to "none" for IMG files,
but in animation frames the same 4 means real CRLE and AnimoLoader passes it
through. Applying the IMG quirk to ANN turned whole animations into noise.

3531 images and 6928 animations decode (81 515 frames, 29 019 named events);
30 files fail and are recorded with the reason. Previews are thumbnails only —
340 MB of cache instead of decoding everything to disk — and full frames are
rendered from the disc on demand. Animations carry their author: 6513 of them
are signed Piotr Maciejewski.

File lookup now follows the engine instead of guessing. A bare FILENAME means
next to the script; $ is the game root, so $COMMON\X and $WAVS\X resolve there;
WAV files live in wavs/. This matters because a name alone does not identify a
file — Wojna Trojańska ships seventeen different bkg.img, one per scene, and
matching on the name showed the wrong picture for all of them. Resolution is now
98% overall and 97% for WAV with nothing uncertain; the 900 matches that still
fall back to name-only are flagged in the UI rather than passed off as fact.
What stays unresolved is mostly save-state written at runtime.

Two extraction bugs fixed along the way: fields ending in ^N (VARIWST:ONCHANGED^2)
were skipped entirely, hiding 176 real references; and blocking a match at an
underscore made the regex restart one character later, cutting HIST0.ARR out of
+"_HIST0.ARR". Matches must now begin at a token boundary.

Resolution used to run as correlated subqueries over a CTE, re-evaluated per row:
36 s for a script with 745 references. Parameters are now bound directly and
file.basename is a generated, indexed column — 0.19 s.

Docker gains whisper.cpp and ffmpeg (tens of MB); the model is mounted under
/models instead, since it is the part that weighs gigabytes and everyone keeps a
different one. Not verified: the Docker daemon is not running on this machine, so
the image was not built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:40:13 +02:00
Patryk GenschandClaude Opus 5 02988297ec Add web frontend and Docker packaging
Frontend is a single page served from classpath resources with a JSON API
alongside it. Kept separate from the MCP tools because the consumers differ:
a model reads formatted text, a browser needs structures. Only the database
is shared.

The page has three views - catalog with edition details, copies, detected
facts with evidence and a filterable file list; script search with FTS5
snippets and a viewer; and a format breakdown. No build step, no CDN, no
dependencies; light and dark follow the system.

New `serve` command puts the frontend and MCP on one port, one process and
one database connection, which is also what the container runs. `mcp` alone
still works for a headless setup.

Docker is a two-stage build: JDK plus the Rex-EMoolator submodule to compile,
JRE for runtime. installDist rather than build, so check - and with it
verifyCoreVersion - is skipped and git is not needed in the image. The pinned
:core tag is extracted from gradle.properties at build time and passed in by
the entrypoint, otherwise copy.core_version would record "nieznana" and
derived-artifact invalidation would stop working. Properties go through
JAVA_OPTS because Gradle's launcher treats everything after the script name
as application arguments.

Compose mounts the collection read-only and keeps the database and script
cache in a named volume. The port is bound to loopback.

Not verified: the image itself does not build here, the Docker daemon is not
running on this machine. The launcher, JAVA_OPTS handling and resource
packaging were tested against the installDist output directly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:34:51 +02:00