# Test harness `tools/lamatest.py` Starts "Ein Königreich für ein Lama" unattended and invisibly (Xvfb :5, sound into a null sink), loads TestLevel chapter 1 (T1L9M001.wad) and takes screenshots. Tested on 2026-11-06 with Wine 6.0.3, llvmpipe. ## Commands ``` lamatest.py run [n] # all in one: install, start, tolevel, tour, stop lamatest.py start # Xvfb :5 + null sink lama_stumm + groove.exe lamatest.py tolevel # menu -> slot with TestLevel -> chapter 1 -> gameplay lamatest.py tour [n] # n camera shots (look around) + n walking shots + llama view lamatest.py shot out.png # full screen 1920x1080 lamatest.py key [s] # hold key for s seconds (default 0.12) lamatest.py keys Up:1.5 a:0.5 x # sequence, name[:hold time], also sleep=2 lamatest.py seq "TOKENS" # --seq tokens (shot:, run:, rj: …) in the running game lamatest.py status | stop | restore-wad ``` ### `run` options (added 2026-11-06) ``` lamatest.py run [n] [--seq "TOKENS"] [--tour] [--keep] ``` * **WAD restore**: `run` remembers the *previously installed* `T1L9M001.wad` (`$LAMATEST_DIR/vorher_T1L9M001.wad`) and restores it after the run – i.e. not `.orig`. With `--keep` the tested WAD stays installed. (`restore-wad` still explicitly restores `.orig`.) * `--seq` replaces the default tour with a key sequence (with `--tour` the tour runs as well): * `Up:1.5`, `a:0.5`, `x` … key[:hold time] as with `keys`; `sleep=1` * `shot:NAME` → `seq_NN_NAME.jpg`, `fp:NAME` → shot in llama view (Return held) * `dialog` → click away dialog/letterbox * `Up+x:0.8` → hold keys simultaneously * `rj:T1:T2` → running jump (hold Up, X after T1 s, Up for another T2 s) * `dj:T` → double X with run-up, `hop:N:T` → hold Up, jump N× every T s * `run:T:x@1.3,x@2.9,shot@5.0,Left@3.0-3.4` → hold Up for T s, following a schedule (s from start) tap X / take a shot (`seq_NN_runT.jpg`) / hold a key from–to; `run/Left:T:…` holds another key instead of Up. Real time: a shot blocks ~0.4 s (delays subsequent events and slows the game down) → no shots shortly before jumps. * Controls are **camera-relative** (Up = in the camera's view direction, Left/Right sideways). A/S rotate the camera, S:0.45 ≈ look 90° to the left. * Measurements (llama): ~1.1 units/s walking, jump 0.35 high, ~1.3 far with run-up – see levelbau.md. Timed runs scatter by ~0.3 units (start delay, frame rate); longer distances need "stops" (a wall/step where the llama comes to a halt). * Camera: after running into a wall or a fall the camera rotates unpredictably → Up then walks in a different direction. A/S rotation (S:0.45) is only roughly 90°. * Complete test world parkour as a script: `levels/testwelt_parkour.seq` (levelbau.md). * `LAMATEST_SHOT=1280` (environment): `shot:`/`fp:` shots at 1280×720, quality 85 instead of 960×540 (preview images). * "EINGEFROREN?" (frozen?) for a `--seq` without movement (e.g. only `sleep=3 shot:x`) is a false alarm – the standing llama does not change the image; with camera rotation/walking you get "OK". * Good viewing angles in the game are hard: the camera hangs low behind the llama; elevated start points (pedestal, parkour checkpoint `pk2` + `A:0.6`) show several areas. Helper script for series: `$CLAUDE_JOB_DIR/tmp/lb_run.sh WAD NAME "SEQ"` (montage `lb_mNAME.jpg`, WAD path absolute!), `rs.sh NAME "SEQ"` (restart with installed WAD + `seq`, montage `i/NAME.jpg`). **Caution**: no `pgrep -f groove.exe` in wait loops – it matches your own shell (the command line contains "groove.exe") and lamatest takes it for a foreign instance. State/PIDs/wine.log: `$LAMATEST_DIR` (default `/tmp/lamatest-$USER`). Game: `$LAMA_WINEPREFIX` (default `~/.wine-lama`), `lama-mod`: `$LAMA_MOD` (see [docs/tools.md](../docs/tools.md#paths)). `run` exit codes: 0 OK, 3 crash, 4 timeout/hang, 5 image frozen. Output in ``: `report.txt`, `wine.log` (stderr, truncated to 2 MB), `00_geladen.png` (full) + `.jpg`, `tour_*.jpg` (960x540, ~30 KB), on failure `zz_absturz.jpg` / `zz_timeout.jpg`. One run ≈ 75 s, ≈ 1.2 MB. ## Safety rules (implemented in the script) - Aborts if a groove.exe with a different DISPLAY is running (the user's own instance). - xdotool only with DISPLAY=:5. - Sound: own null sink, the app is never muted; `stop` unloads the sink. - Save games (`*.ksv` in the game folder) are backed up on `start` and restored on `stop` if changed (newly created ones are deleted). - `stop`: `wineserver -k` only if no foreign groove.exe is running, otherwise only own PIDs. - The test world exit saves progress (chapter 2 unlocked) – restored on `stop` together with the save games. - WAD: the first `run` backs up the original as `mods/testlevel/wads/T1L9M001.wad.orig`; `restore-wad` restores it. The overlay (`lama-mod an`) consists of **symlinks** to the mod files → overwriting `~/.wine-lama/mods/testlevel/wads/T1L9M001.wad` takes effect immediately; `lama-mod an` is only called if the overlay link is missing. ## `tolevel` sequence (measured) | Step | Key | Detection | t (from start) | |---|---|---|---| | Logo → main menu | – | OCR crop 700x300+1150+650, threshold 75 % → only the highlighted item "Spiel starten" is readable | ~6 s | | "Spiel starten" (start game) | Return | – | | | Save slot selection (carousel, starts at slot 2, **no** wrap) | Left ×3, then Right until "TestLevel" | OCR crop 1600x280+160+740 (fuzzy, 2 thresholds) | ~20 s | | Load slot 3 | Return | selection map = >75 % beige pixels | ~25 s | | Activate chapter 1 (cursor from KSV +4/+5) | Return | | | | Camera fly-through (letterbox: top+bottom row black) | Return skips | | +1.5 s | | Gameplay | – | bright, no letterbox, golden HUD top left (or >25 s) 3× in a row | ~29 s total, loading ~4 s | Without skipping, the camera fly-through in the village takes ~20 s. Loading screen = T191.COR ("TestLevel Kapitel 1 … LADEVORGANG"). Fallback: if no menu is detected, Return after 40 s (skips the demo). Caution: Return on a misdetected screen can start a different game – but save games are restored at the end. ## PC controls (keyboard, from "Optionen → Tastatur konfigurieren", verified) | Action | Key | |---|---| | Up / Down (walk forward / backward) | Up / Down | | Left / Right (turn) | Left / Right | | Jump (also advances dialog) | X | | Action | Z | | Power | Space | | Llama view (first person, hold) | Return | | Rotate camera left / right | A / S (hold 0.5 s ≈ 90°) | | Pause menu | Escape | Registry `HKCU\Software\Disney Interactive\Emperors New Groove\1.0`, `"InputDevice Wine Keyboard"` = 14 DIK codes (4 hex digits each): CB Left, CD Right, C8 Up, D0 Down, 1E A, 1F S, 39 Space, 2D X, 1C Return, 19 P, C9 PgUp, D1 PgDn, 2C Z, 18 O. The registry order ≠ menu order; P/PgUp/PgDn/O probably pause/inventory (according to exe strings: PAUSE GAME, CYCLE INVENTORY UP/DOWN, USE AN ITEM, TURN 180, SELECT) – not verified. Other values there: CameraMode, ControlType, ScreenW/H (1920x1142), CDPath=L:\. Pause menu (Escape): graphics/sound options, configure keyboard/joystick, more options, continue, "Beenden und zur Auswahlkarte wechseln" (quit to selection map; confirmation Ja/Nein, default Nein → Up, Return), quit game. Main menu submenus: Escape = back. ## Observations on the village 1 copy - Walking forward in the village immediately starts the Pacha dialog (letterbox with subtitles, ~9× X to click through). `tour` detects this, photographs the dialog once and clicks it away. - The llama can fall off the cliff during the tour (respawn) – irrelevant for visual checks. ## Crash detection Per poll: groove.exe process on :5 gone / window "Groove" gone / window with "Programmfehler", "error", "exception" … (Wine crash dialog). The report contains the line `wine: Unhandled page fault … at address XXXXXXXX` (look up the address in Ghidra). Tested: WAD truncated to 300 KB and WAD with 2000 random bytes → both crash during the loading screen, detected after ~5 s (exit 3). Harmless log noise (gstreamer/MPEG-1 missing, ole, ChangeDisplaySettings, MIDIMAP) is filtered out of the report. ## Dependencies Xvfb, xdotool, ImageMagick (import/convert), tesseract with `deu`, pactl, wine.