Moorhuhn 1, 2, Winter-Edition, 3, Tennis and CD extras

Local candidates moorhuhn, moorhuhn-2, moorhuhn-winter in test/candidate-corpus/manifest.json (archive.org item moorhuhn_1__2_with_addins); app ids moorhuhn, moorhuhn_2, moorhuhn_winter in lib/apps.js. All three reach gameplay headless and in the browser (2026-09-21).

Moorhuhn (2000, DirectDraw 640x480)

Moorhuhn 2 (v1.1)

Moorhuhn Winter-Edition (2001, Haribo)

Moorhuhn 3 (2001 download edition, German)

Source: Moorhuhn.zip from archive.org moorhuhn_202112. It holds Setup_Moorhuhn3DL_DE.exe (InstallShield); 7z that, then unshield -g "App Executables" on Disk1/data1.cab. The candidate fetcher does both steps (moorhuhn-3). Moorhuhn3.exe + moorhuhn3.dat are the whole game. It uses DirectDraw 640x480x16 and DirectSound, and its audio is an FPU MP3 synthesis filter at 0x43030b, the hottest block in gameplay.

The exe is packed, and the packer is hostile. Four things had to be real:

It also writes C:\WINDOWS\TEMP\gsm3sys32.exe one byte at a time and ShellExecutes it, and probes highscores.txt ~26 times.

Route (batch size 200000): the title is up by ~2400 batches as of 2026-09-28 (it was ~1400 when first written). A click at ~2600 leaves it, another click at ~2850 and Space at ~3050 start the round. The first gameplay frames are a venetian-blind wipe (4px vertical stripes). That is the game's transition, not a blit bug. By ~4300 the round is clean.

node test/run.js --app=moorhuhn_3 --quiet-api --no-close --batch-size=200000 --max-batches=4301 \
  --input=2600:mousedown:320:240,2603:mouseup:320:240,2850:mousedown:320:240,2853:mouseup:320:240,3050:keydown:32,3053:keyup:32 \
  --png=/tmp/mh3.png

Unpacked code is only in memory, so read it with --input=B:dump-mem:0xVA:LEN + node tools/disasm-dump.js <log> --addr=0xVA.

Frame loop (2026-10-10, dumped at batch 4300): 0x413f74 reads timeGetTime, calls the game step 0x4144b0 with [0x487680], reads timeGetTime again, Flips [0x4876b0] (vtbl+0x2c; DDERR_SURFACELOST restores via 0x406ef0 and retries), busy-waits until 10 ms have passed since the first read, then bumps the frame counter [0x4876a4] at 0x413fd9. perf.logicalFrame is 0x4144b0 with verifier 0x413fd9: --count gives 5332 hits on each of the step, the verifier, the loop head and the Flip return over the 4301-batch route, and --present-frames=4300 778 steps for 778 frame ends. The loop runs on every screen; by batch ~4350 the round has ended and the high-score entry is up.

Bonus puzzles (MOORHUHN 3 - BONUS GAMES/)

All three are Jigs@w Puzzle (Tibo Software, 2000-2001) self-extractors:

What it took to run them:

The board is up by about batch 700 of 500000, and dragging a piece works (hover, mousedown, several mousemoves, mouseup):

node test/run.js --app=moorhuhn_3_puzzle_fisch --quiet-api --no-close \
  --batch-size=500000 --max-batches=900 --png=/tmp/fisch.png

All three variants and the frame counter (claude:202b4b39, 2026-10-10). moorhuhn_3_puzzle and _leuchtturm reach their boards the same way, and a piece drag on the CLI (640x480: hover, mousedown, five mousemoves two batches apart, mouseup, from batch 900) moves it; a no-drag control is pixel-identical before/after. setup.exe is byte-identical in the three packages, so one counter serves all of them (perf.logicalFrame in moorhuhn3Puzzle()):

Snapping and completion (claude:202b4b39, 2026-10-10). The piece table: [0x45eda0] = array base, [0x45ed94] = piece count (35 = 5x7 in Moorhuhn 3 and Fisch, 40 = 5x8 in Leuchtturm -- read it, do not assume it), one 0x1b0-byte record per piece. In record p: current board rect at p+0 (l, t, r, b; screen = board + (0, 64) under the banner), outline corners as doubles after it, home (picture-space) top-left at p+0x140, id at p+0x148, held flag at p+0x168, neighbour count at p+0x190 and ids at p+0x194. (Reading a dump by the ff ff ff ff markers misaligns by one record: the rect after a marker belongs to the next piece.)

Best Of Moorhuhn (2001 CD, archive.org/details/best_of_mh)

The ISO holds three InstallShield 5 setups (unshield each data1.cab).

Each of the three is a Delphi Jester wrapper:

Gallinelle XXL (2003 Italian Moorhuhn 1, archive.org/details/gallinelle-xxl)

The image: the zip is a raw MODE2/2352 BIN/CUE. Keep bytes 24..2071 of every sector to get an ISO, then unshield SETUP/DATA1.CAB.

The game (gallinelle): Game/Gallinelle.exe is a different build from our Moorhuhn.exe, but it runs unchanged. The route is the same:

Bonus: Bonus/trainingsarea{1,2}.exe are plain Flash 6 projectors with no Jester wrapper, the Italian twins of the Best Of Training-Areas. They are kept in the fixture but not in the dropdown.

Moorhuhn Tennis (2002, Flash)

Source: Moorhuhn-Tennis.exe from archive.org moorhuhn-tennis, a Wise installer. Its payload is in an overlay 7z cannot open, so install it inside Wine Assembly. Control id 3 exists on a hidden progress dialog as well (its Abbrechen), so wait-dlg-control:3 + dlg-click:3 cancels setup. Click Weiter by position (378,395) every ~400 batches of 10000 instead.

The installed moorhuhn_tennis.exe is only a wrapper. It unpacks swfxxlrt.dll and a <random>TMP\ folder holding MH_Tennis_V14.exe, source.swf and basepath.txt, then CreateProcesses the exe and polls EnumWindows/GetWindowThreadProcessId/Sleep for its window. Headless CreateProcess does not start a child, so it waits forever. MH_Tennis_V14.exe is a stock Macromedia Flash Player 6 projector with the game appended (it seeks to its own tail). It runs directly. lib/apps.js moorhuhn_tennis points at it, and the candidate manifest entry is manual and lists these steps.

Route (batch size 100000): the instruction screen appears by ~300 batches. Hover "weiter" (243,462), press at 1500 and release at 1520; a two-batch click only highlights the button. The chicken follows the mouse.

No game-step counter yet (2026-10-10)

The game is the SWF appended to MH_Tennis_V14.exe (trailer 0xFA123456 + length; 291,139 bytes, Flash 6, 25 fps, 69 frames); source.swf beside it is a one-frame 12 fps loader. The projector ticks a periodic timeSetEvent(23 ms, res 11, 0x4644e0) after timeBeginPeriod(1), plus a 400 ms SetTimer. 0x42cf20 (single caller 0x46450b; clears a slot at +0xd0, calls [vtbl], reads timeGetTime) runs once every second tick: 1706 hits for 3338 ticks of 0x4644e0, ~21.3/s, which is the 25 fps timeline quantised to 46 ms. As perf.logicalFrame it counts 638 steps from batch 0 but zero in the match at the serve, where the movie is stopped and only redraws when the player acts. So it is the timeline advance, not a per-frame game tick; it was not declared. A real step needs the player's per-frame ActionScript dispatch (onEnterFrame/clip events), not yet located. Presents are GDI, so --present-frames sees no frame ends either.

Gallinelle XXL: frame counter and the hit test (2026-10-10)

gallinelle at 100,000-block batches: Space held 330-340, name letters as keypress (420-436) and Enter (444-449) start a round by batch 549 (timer 01:15, eight shells). Each frame is exactly one IDirectDrawSurface_Flip of the primary from one site (return 0x4152f8), after one Lock/Unlock of a compose surface and one Blt into the back buffer: over 549-800, 2,030 presents == 2,030 Flips, 1,422 of them changed. --frame-stats records each Flip twice.

The first aimed CLI shots registered no hit. That was the route, not the emulator. A hover-then-press shot scores 0 -> 25 (scratch/runs/20261010T0735-gallinelle-hit-scored; the earlier misses are in …T0800Z-gallinelle-control-frames and …T0715-gallinelle-aim-check). The input path, as RE'd:

Aiming, for routes. The press uses the hit index from the frame before the press arrives. Move the cursor onto the bird, step at least one batch, then press, never mousemove+mousedown in the same batch. Aim at the art, not the box: a 30x20 bird's opaque pixels are columns ~12-24 and rows ~5-15, so (x+2, y-6) from the object's x/y is a body pixel. A ctl eval that lists live birds is saved with the run (chicken-list.eval.js).

Gallinelle original recovery recipe (2026-10-14)

node tools/fetch-candidate-corpus.js --id=gallinelle-xxl --force now reconstructs the original Game and Bonus directories from the pinned 74,287,372-byte ZIP (SHA1 374449d553941dfd75301b09ba3fa0507c934029). All 33,997 raw sectors are MODE2 Form1 with matching XA subheaders. The converter validates these fields and sync bytes before passing the cooked ISO to archive extraction; Form2 and mixed modes fail explicitly. Cabinet groups require unshield (tested 1.5.1); the older IS5 fallback correctly refuses this newer cabinet. Install with sudo apt-get install unshield if absent.

The four original payload files are pinned in test/candidate-corpus/gallinelle-xxl-files.json; verify with node test/test-gallinelle-recovery.js. Fresh recovery is independently compared with an original sector-slice extraction and unshield group extraction. Original archive and receipts: scratch/runs/20261014-gallinelle-recovery/. No payload patches or new runtime/gameplay/public-distribution claims. Forced recovery replaces the fixture tree; preserve local player files elsewhere first.