Work in progress

Write-ups of the problems met along the way and how they were solved, and what changed in each release.

Dev log

  • 2026-10-09

    Full-Precision Textures: Undoing the 4444 Cut Inside the Game's Own Driver (WIP)

    Run pumpitea and the faces on the song select screen and the folds in the BGA look blotchy. It looks like an emulator defect, but the cause is inside the game's executable. PIU draws through OpenGL and links a whole Mesa 3.x driver into the executable to turn that OpenGL into Glide; that driver cuts every color texture to 4 bits (ARGB4444) or 5/6 bits (RGB565) per channel before the hardware sees it. The original 8-bit image stays in the game's memory afterwards. This post records finding that original and adding a quality option that uploads it instead, only when it is verified to be exactly the same texture.

  • 2026-10-05

    Post-Processing Shaders: An Arcade Monitor on Top of the Glide Frame (WIP)

    The original PIU board sent its 640×480 picture to an arcade CRT monitor. rePIU draws that picture at 2x in a window on a modern monitor, and the more exact it is, the further it gets from "the screen of the day". This post records adding post-processing shaders that put an arcade-monitor look over the finished frame at presentation only; the framebuffer the guest sees does not change by a byte.

  • 2026-08-12

    The Four Bytes That Were Never There: A Colour Profile, a Missing Checksum, and the Wrong Suspect

    pumpit8 (Pump It Up: The Rebirth) cleared ROM/CHD mounting, executable loading, JAMMA, and security-chip initialization, then terminated with a 0xC0000005 access violation about 97 seconds in while reading a background animation.

  • 2026-08-06

    The Clock That Ran at Half Speed: From a Note-Jumping Report to a One-Line Default

    The reported symptom was one line: "the notes and the BGA lose sync with the music and jump."

  • 2026-07-28

    Hunting the Missing Difficulty Dots: Four Wrong Answers and Two Real Bugs

    On the MUSIC SELECT screen, the difficulty dots are large crisp red circles on original hardware and a few blue pixels in rePIU. Everything else on that screen — the font, the background, the four discs — looked right. Tracking down that single symptom took five instrumented runs and two wrong conclusions, and ended up fixing three unrelated defects.

  • 2026-07-21

    Glide Render Pipeline: From Black Screen to First Pixels — Work in Progress 3

    The previous post was about how fast execution runs — introducing a native AOT dynamic translator to cut single-step trap overhead.

  • 2026-07-13

    [WIP] rePIU Progress: Native AOT Dynamic Translation and Performance Improvements

    Until now, rePIU has largely focused on basic initialization in Legacy mode and establishing the surrounding HLE (High-Level Emulation) environment. However, relying entirely on single-step traps for every original x86 instruction brings substantial overhead. To overcome this, the recent work branch heavily focused on introducing and refining a Native AOT (Ahead-of-Time) dynamic translator.

  • 2026-07-10

    Preserving DOS4GW Execution: Work in Progress 1

    The core of this progress is a step-by-step foundation for running the original 32-bit x86 DOS/4G code inside a Win32 process without rewriting the game logic in C++. The work started with a non-executing analyzer for PIU.EXE, then grew into LE object mapping, fixup decoding, internal relocation dry-runs, relocated image buffers, Win32 process memory placement, guest stack switching, and observation-driven HLE trap handling.

Release timeline

  1. 2026-10-16

    v0.0.225 LATEST

    Changes since v0.0.224 (issue #76). The game can be played with the keyboard and gamepads on the web page, with the same key layout as on Windows and Linux. Release builds (Windows, Linux) behave as before.

    Full release notes

    Changes since v0.0.224 (issue #76). The game can be played with the keyboard and gamepads on the web page, with the same key layout as on Windows and Linux. Release builds (Windows, Linux) behave as before.

    Resolved issues

    issue Summary Commit
    #76 keyboard and gamepad input on the browser page (the input part of phase 4 unit 2); the issue stays open for the in-game check 3f4c8e8

    Changed

    Keyboard. Until now the web build had no input, so F5 (COIN1) reloaded the page.

    • While the game runs, the page sends keys to the engine, which takes them as a native window's keys, so the layout is the same: coin F5, test F1, service F2, clear F3, P1 Q E S Z C, P2 keypad 7 9 5 1 3, the OSD Tab.
    • Keys go by the key's position (KeyboardEvent.code), not the character, so the same key is the same input whatever the keyboard layout.
    • The browser's default action for game keys is stopped: F5 does not reload and Tab does not move the focus. Ctrl and Meta combinations, F11 (fullscreen) and F12 (developer tools) stay the browser's.
    • When the page loses focus or is hidden, every held key is let go of (as a native window does).
    • The page lists the keys.

    Gamepads. A pad the browser recognises with the standard mapping (most XInput pads) is handed to SDL as a gamepad, so the native pad bindings and chords apply as they are: coin on Back, the OSD on LT+RT+Y, exit on LT+RT+L3+R3. A pad without the standard mapping is not used, and the console says so once.

    Verified

    Configuration Check
    Browser in headless Edge, F5 does not reload the page
    Key name table core probe browser_key_code (ranges, named keys, every key of the default layout, unknown names): Win32, Linux x64 and i386, web
    Win32 direct Release core probe 45/45, pumpitea stops normally after 40 s
    Linux x64 and i386 Release core probe 47/47 and 45/45
    Web (repiu, repiu_browser, repiu_core_probe) build; Node core probe 37/37
    CI the Win32 Debug build and probes

    Known

    • Whether the coin and the OSD react in the game, and whether gamepads work, has not been checked in a browser yet. On this PC headless browsers' file reads had slowed down and the CHD unpack stalled (the build before this change as well). #76 stays open for the user's check.
    • The web build has no sound yet (the next unit).
    • On Win32 a GL error is sometimes recorded at pumpitea's first texture upload. v0.0.224 has it too; it is #78.
  2. 2026-10-16

    v0.0.224

    Changes since v0.0.223 (a follow-up to issue #72). The browser page is easier to use: the ROMs can be picked as one folder, and what the engine is doing after Start shows on the page. Release builds (Windows, Linux) are unchanged apart from extra progress lines on a first run.

    Full release notes

    Changes since v0.0.223 (a follow-up to issue #72). The browser page is easier to use: the ROMs can be picked as one folder, and what the engine is doing after Start shows on the page. Release builds (Windows, Linux) are unchanged apart from extra progress lines on a first run.

    Resolved issues

    issue Summary Commit
    #72 follow-up pick the ROMs as one folder or as the ZIP and the CHD separately; show the CHD unpack's progress on the page 67b6e43

    Changed

    Picking the ROMs. Until now one file field had to pick the ZIP and the CHD together, but the two usually sit in different folders (roms/pumpitea.zip, roms/pumpitea/010209_1821.chd), so they could not be picked at once.

    • One folder: pick a folder holding the game's ZIP and its CHD, such as roms, and the game is found; with several games, one is chosen from a list. A ZIP and a CHD side by side in one folder work too. The folder's other ZIPs go along, so a game that needs its parent ROM set (pumpite.zip and the like) works as well.
    • Separately: a ZIP field and a CHD field.
    • Nothing is uploaded; the browser only reads the files.

    A page that shows its progress. Nothing appeared for over a minute after Start because the engine printed no line while it unpacked the game's files from the CHD (736 files, 409 MB for pumpitea).

    • The engine reports the unpack's start and its progress about every 16 MB, for example pumpitea unpacked 47% (194/736 files, 192/409 MB). Windows and Linux print the same lines on a first run.
    • The page shows the engine's output beside the canvas (below it on a narrow screen), with the elapsed time, the line count and the latest line kept current, and a button that copies the whole log.

    Verified

    Browser: the user ran the game with the new page; in headless Edge, picking by folder and separately, starting, and progress lines within 3 s of starting. The picking rules: a Node test 11/11, and all 16 games found in this PC's real roms/. Win32 direct Release: 26 progress lines from an empty cache, the unpack done in 21 s. Web (repiu, repiu_browser): builds. CI: the Win32 Debug build and probes.

    Known

    • The web build has no keyboard or gamepad input and no sound yet (the next unit), so F5 (COIN1) reloads the page.
  3. 2026-10-16

    v0.0.221

    Changes since v0.0.220 (issue #67). The browser host's second phase. The development emulated engine and the rex86 CPU emulator are built as WebAssembly, and run pumpitea under Node with no screen. Release builds (Windows, Linux) behave as before.

    Full release notes

    Changes since v0.0.220 (issue #67). The browser host's second phase. The development emulated engine and the rex86 CPU emulator are built as WebAssembly, and run pumpitea under Node with no screen. Release builds (Windows, Linux) behave as before.

    Resolved issues

    issue Summary Commit
    #67 Web port 2B phase 2: the Emscripten build of the emulated engine and rex86, run headless under Node 27e894d

    Changed

    The web build runs the game. Until now the web build built only the shared core, which runs no game. It now builds the whole engine on the emulated model, with the game's code run by rex86 inside wasm.

    • Result (pumpitea, Node): about 6.1 MIPS over 30 s, close to native emulated on the same PC. The HLE boundary trace matches native up to the first wall-clock dependence (the image's placement aside). The screen is the Glide dummy and there is no sound (picture and sound come in phases 3 and 4).
    • Memory: the game's addresses sit at the same addresses in wasm memory, with Emscripten's data, stack and heap moved above 0x10000000.
    • Web platform layer: virtual memory (stub → implementation), a synthesised host entry state (the selector values a Windows thread sees), a thread layer without threads, and "none" answers for the diagnostics. The 11 fixed-function GL calls the Glide backend still makes are temporary stubs that stop the run if reached.
    • Running: build with ./scripts/build_web_wasm.sh --build-dir ~/repiu-web and run node ~/repiu-web/repiu.js pumpitea from the repository root.
    • The unused none execution model is deleted.

    Verified

    Web (Node): repiu links, core probe 36/36, pumpitea for 30 s, the boundary trace comparison. Win32 direct and emulated Release: core probe 44/44, pumpitea runs. Linux i386 and x64 (WSL): build and core probe. CI: the Win32 Debug build and probes.

    Known

    • It does not run in a browser yet; next is phase 3, moving the Glide renderer to WebGL2.
    • The launcher cannot be used on the web (its update check uses a thread).
    • The heap-corruption crash in emulated teardown (one user run) has not reproduced and is being looked into (#65).
  4. 2026-10-16

    v0.0.220

    Changes since v0.0.219 (issue #64, and #61 follow-ups). The browser host's first phase. The browser's single Worker has no threads, so the development emulated model now runs the game's code, the screen, the timer, the sound and the input on one thread, taking turns. Release builds (Windows, Linux) behave as before.

    Full release notes

    Changes since v0.0.219 (issue #64, and #61 follow-ups). The browser host's first phase. The browser's single Worker has no threads, so the development emulated model now runs the game's code, the screen, the timer, the sound and the input on one thread, taking turns. Release builds (Windows, Linux) behave as before.

    Resolved issues

    issue Summary Commit
    #64 Web port 2B: the browser host's design and phase 1 (the single-threaded host loop of emulated) 3288e17
    #61 follow-up The three execution models in the rules and architecture documents; the Linux i386 build fixed 3288e17

    Changed

    The single-threaded host loop. rePIU used to split into a thread running the game's code, a main thread for the screen, the timer and the window, and audio threads. On emulated one main thread now runs the game's code on rex86 for a while and, when the next timer tick is due or 2 ms have passed, does the host's work (window events, the screen, the timer, filling the audio), over and over.

    • Design: the browser host comes in four phases: 1) the single-threaded loop (this release), 2) the Emscripten build and a headless run under Node, 3) the Glide renderer on WebGL2, 4) the browser page (Worker, OPFS, AudioWorklet, input).
    • Result (Windows, pumpitea): the user confirmed the logo, the opening video and the music. The HLE boundary trace before and after phase 1 matches up to the first wall-clock dependence.
    • Throughput: the instructions run fell by 29%, but the buffer swaps are equal and the game's progress is equal or ahead; what went is the loops the game runs while waiting (inferred). Instructions per second is therefore not a measure of emulated's progress.
    • Shared code: one turn of the host's work (HostTurn) was taken out of the poll loop, and the three audio outputs and the input script can be run one step at a time. direct and cache repeat them on threads as before.

    #61 follow-ups. The Linux i386 build, which had not compiled since v0.0.219 (_mm_getcsr), is fixed. AGENTS.md, CODING_STYLE.md and ARCHITECTURE.md name three execution models.

    Verified

    Win32 direct Release: core probe 44/44, pumpitea 3,600 frames in 70 s, clean exit. Win32 emulated Release: core probe 44/44, the boundary trace comparison, the input script, window close and timeout, and the user's check (logo, opening, music). Linux i386 and x64 (WSL): build and core probe. CI: the Win32 Debug build and probes.

    Known

    • emulated is still slow; on the measuring PC it did not reach the title.
    • One user run of emulated past the music crashed with heap corruption during teardown. It has not reproduced and is the next task's (#65).
  5. 2026-10-15

    v0.0.223

    Changes since v0.0.222 (issue #72). The first unit of the browser host's fourth phase. The development web build puts the game's picture on a browser page for the first time. Release builds (Windows, Linux) behave as before.

    Full release notes

    Changes since v0.0.222 (issue #72). The first unit of the browser host's fourth phase. The development web build puts the game's picture on a browser page for the first time. Release builds (Windows, Linux) behave as before.

    Resolved issues

    issue Summary Commit
    #72 Web port 2B phase 4: the browser page, the engine in a Worker drawing to a WebGL2 canvas 59f2445

    Changed

    pumpitea runs in a browser. Pick the ROM ZIP and the CHD on the page, and the engine runs in the page's Worker and draws to a WebGL2 canvas.

    • Result (pumpitea, Chrome): the title about 4 minutes after start, demo play at about 400 s. The first 55 s or so go to unpacking 428 MB of game files from the CHD into memory. No sound or input yet.
    • Files: the CHD is never loaded whole; only the parts asked for are read. ROMs are not hosted; the user picks them.
    • Picture: the window and GL context work sits behind one boundary, so Windows and Linux use an SDL window and the browser the page's canvas.
    • Running: the engine runs on one thread and hands the browser its turn once a frame.
    • How to run: build with ./scripts/build_web_wasm.sh --build-dir ~/repiu-web --target repiu_browser, serve that directory with python3 -m http.server, and open it in a browser.

    Checked

    Configuration Check
    Browser (Chrome) title and demo play on screen, no Worker errors
    Win32 Debug and Release render probe scenes pixel for pixel, post-process, letterbox, fullscreen, core probe
    Win32 emulated, Linux x64 and i386, web under Node build, core probe, run
    CI Win32 Debug build and probes

    Known

    • No sound or input (next unit). The game files are unpacked again every time (persistent storage is the unit after).
    • Far slower than real time; playing properly needs wider translation in the CPU emulator, a separate piece of work.
  6. 2026-10-15

    v0.0.222

    Changes since v0.0.221 (issue #69). The browser host's third phase. The Glide renderer was rewritten to use only what OpenGL 3.3 core and WebGL2 (OpenGL ES 3.0) share. The Windows and Linux picture is as before, and a GPU driver with OpenGL 3.3 is now required (nearly every GPU since 2010).

    Full release notes

    Changes since v0.0.221 (issue #69). The browser host's third phase. The Glide renderer was rewritten to use only what OpenGL 3.3 core and WebGL2 (OpenGL ES 3.0) share. The Windows and Linux picture is as before, and a GPU driver with OpenGL 3.3 is now required (nearly every GPU since 2010).

    Resolved issues

    issue Summary Commit
    #69 Web port 2B phase 3: the Glide renderer on the GLES 3 / WebGL2 subset, checked on native GL 3.3 core 85071ca

    Changed

    The renderer now has a shape that can run in a browser. Until now it used old OpenGL's fixed function (immediate mode, the matrix stack, the alpha test and so on), which WebGL does not have.

    • Drawing: immediate mode became a vertex buffer, and the projection, point size and alpha test moved into the shader. The color combine and fog calculations are unchanged.
    • Screen buffers: the game draws into an offscreen pair of front and back buffers, and only finished pictures reach the window at the buffer swap. A browser clearing the screen after showing it, or showing a half-drawn frame, cannot touch the game's picture.
    • Post-processing (crt, scanline, user shaders): reads the picture directly instead of copying the screen. User shader files are converted to the newer GLSL automatically and work as they are.
    • OpenGL context: Windows and Linux ask for OpenGL 3.3 core. Without it the game runs with no picture and standard error says why.

    Checked

    Configuration Check
    Render probe zero differing pixels against four reference scenes captured on the renderer before the port (Debug and Release)
    Win32 Release pumpitea and pumpit8 pictures compared before and after, played by the user; vsync-OFF throughput as before (about 1,100 fps)
    Linux x64 and i386 (WSL) build, core probe, the title on 4.6 / 4.5 Core Profile
    Web (Node) links without the fixed-function stubs, core probe, pumpitea for 30 s
    CI Win32 Debug build and probes

    Known

    • It does not run in a browser yet. Next is phase 4, the browser page.
  7. 2026-10-11

    v0.0.218

    Changes since v0.0.217 (issue #58). In the PIU10 games the arrows no longer step back during play or fall behind the music.

    Full release notes

    Changes since v0.0.217 (issue #58). In the PIU10 games the arrows no longer step back during play or fall behind the music.

    Resolved issues

    issue Summary Commit
    #58 Arrows stall, step back or jump during play: the song clock's MP3 position correction 27677cb

    Changed

    The MP3 position is answered at tick time. The PIU10 games (pumpipx3 and others) advance a song clock on timer ticks, count the song position by the changes of the MP3 chip's frame-sync bit, and every 256 frames (about 6.7 s) set the song clock to that position; the arrows follow the song clock. The original timer runs evenly every 4 ms, but in rePIU the ticks arrive late and bunched (four at once per frame under Windows vsync). The game then read the bit at one instant several times, two changes cancelled, and the song position fell 52 ms behind each time: at the next correction the arrows stepped back and stayed late for the rest of the song.

    A read inside the game's timer handler is now answered with the bit at the time its tick was due, and one outside with the bit at the latest tick the game received, the way the input keys already are.

    Measured (Windows, pumpipx3, 300 s) Arrow move at a correction
    Before, with a screen capture running −194.6 to +5.4 ms
    Now, the same all +1.1 / −2.9 ms (within one tick)
    Now, eight songs played by hand 46 of 47 at +1.1 / −2.9 ms (the race behind the other one is fixed too)

    REPIU_PIU10_FRAME_SYNC_REPLAY=0 restores the previous behavior.

    Diagnostics. REPIU_PIU10_SONG_CLOCK_LOG=1 prints how far the arrows moved at every correction, and the final report gives PIU10 song clock … and the per-frame tick histogram PIU10 song frame ticks … (moves of 8 ms or more are printed even when it is off). REPIU_TIMER_LATENCY_TRACE=<path> records where ticks are held.

    Verified

    • Win32 Release: builds, the core probe (including the new piu10_song_clock_census and piu10_frame_sync_history), the JAMMA timeline probe, automated plays and a hand-played session.
    • Linux x64 Release (WSL): build and the core probe.

    Known

    • Not played on Linux hardware or a Steam Deck (on WSL the MP3 does not advance).
    • Ticks still arrive bunched once a frame, and when the game stalls for hundreds of milliseconds dropped ticks can move the arrows ahead; both continue in #59.
  8. 2026-10-10

    v0.0.217

    Changes since v0.0.216 (issue #55). A gamepad opens and drives the OSD, so in-game settings such as fullscreen, aspect, shader and textures can be changed on a Steam Deck with no keyboard.

    Full release notes

    Changes since v0.0.216 (issue #55). A gamepad opens and drives the OSD, so in-game settings such as fullscreen, aspect, shader and textures can be changed on a Steam Deck with no keyboard.

    Resolved issues

    issue Summary Commit
    #55 Open and drive the in-game OSD with a gamepad (LT+RT+Y) 33eba35

    Changed

    Gamepad OSD. Pressing LT + RT + Y together on one pad opens and closes the OSD as Tab does. While it is open the pad drives it: the D-pad moves between items, A toggles a checkbox or opens and picks from a menu (the shader, for one), B closes an open menu or otherwise the OSD, and LT + RT + Y closes it too. Meanwhile pad input does not reach the game, and game inputs held as it opens are released; a button still held as it closes (such as the B that closed it, the down-right panel by default) stays away from the game until released. The keyboard still reaches the game while the OSD is open. Steam Deck steps are in the launcher update guide.

    Verified

    • Linux x64 and i386 Release: builds and the core probe, including the new host_pad_input_osd_chord.
    • Win32: the CI Debug build and probes.
    • A real pad (Xbox Series X, Linux x64): opened and closed twice with LT+RT+Y, keep-aspect and the shader changed with the pad, no pad input reached the game while it was open, and the closing B did not leak into the game.

    Known

    • Not checked on a real Steam Deck.
    • An OSD opened with Tab follows the same rule, so the pad cannot play while it is open (the keyboard can).
  9. 2026-10-10

    v0.0.216

    Changes since v0.0.215 (issue #52). A gamepad alone can now quit the game and the launcher, a chord for the Steam Deck, which has no keyboard or close button. If you run v0.0.215, this is the first release its launcher's Update button can install.

    Full release notes

    Changes since v0.0.215 (issue #52). A gamepad alone can now quit the game and the launcher, a chord for the Steam Deck, which has no keyboard or close button. If you run v0.0.215, this is the first release its launcher's Update button can install.

    Resolved issues

    issue Summary Commit
    #52 Gamepad exit chord LT+RT+L3+R3 held for one second 8d0b378

    Changed

    Gamepad exit chord. Holding LT + RT + left stick click + right stick click together on one pad for one second quits: in a game it ends the game as closing the window does and returns to the launcher (an argument run ends the process); in the launcher it closes the launcher and ends rePIU (back to Steam on a Steam Deck). Releasing sooner does nothing. The right stick click is bound to CLEAR by default, so the game sees CLEAR while the chord is held. The launcher's hint line names the chord, and the Steam Deck steps are in the launcher update guide.

    Verified

    • Linux x64 and i386 Release: builds and the core probe, including the new host_pad_input_exit_chord.
    • Win32: the CI Debug build and probes.
    • A real pad (Xbox Series X, Linux x64): a one-second hold in a game ended it normally and returned to the launcher; the user confirmed the short press and the launcher quit on screen.
    • The v0.0.215 release run passed every job on its first attempt, with no Node.js 20 warning and every Linux x64 host_thread_interrupt claim true (#50, #12).

    Known

    • Not checked on a real Steam Deck.
    • The game sees CLEAR during the chord (the decided behavior).
  10. 2026-10-10

    v0.0.215

    Changes since v0.0.214 (issues #48, #50, a #12 follow-up). The launcher announces a new release and updates with one button, for setups like the Steam Deck where downloading and unpacking a release by hand is tedious; from this version on, the next release can be taken from the launcher.

    Full release notes

    Changes since v0.0.214 (issues #48, #50, a #12 follow-up). The launcher announces a new release and updates with one button, for setups like the Steam Deck where downloading and unpacking a release by hand is tedious; from this version on, the next release can be taken from the launcher.

    Resolved issues

    issue Summary Commit
    #48 Launcher update check and in-place install 8a1cc84
    #50 Move GitHub Actions off the Node.js 20 runtimes 8a1cc84
    #12 (follow-up, still open) Diagnosing the intermittent host_thread_interrupt failure in the Linux x64 release CI 8a1cc84

    Changed

    Launcher updates. When the launcher (repiu with no arguments) opens, it checks GitHub's latest release in the background. A newer one shows "rePIU v… is available." with an Update button at the top, reachable by gamepad, which downloads this build's archive, unpacks it only when it matches the SHA-256 GitHub reports, replaces only the archive's files, and restarts the launcher; roms\, cfg\ and nvram\ stay, and a failure puts the old files back. Win32 downloads through WinHTTP and Linux through the system curl; Linux turns the same process into the new executable (so Steam sees it still running) and Win32 runs the new launcher as a child. The button appears only in a folder unpacked from a release archive (a VERSION next to the executable equal to the running version); a build tree only gets the notice. [Launcher] check_updates = 0 in cfg\repiu.ini or REPIU_UPDATE_CHECK=0 turns it off. Installing on a Steam Deck, troubleshooting and a test procedure are in the launcher update guide.

    CI. The workflows' actions moved to their Node 24 versions (checkout@v7, cache@v6, upload-artifact@v7, download-artifact@v8). The host_thread_interrupt probe that intermittently stopped the Linux x64 release job prints its seven claims separately, and its target alternates between two functions so a biased interrupt position alone cannot fail it.

    Verified

    • Linux x64 and i386 Release: builds and the core probe (including launcher_update's 13 checks), locally and on GitHub's runners.
    • Win32: the CI Debug build and probes, with no warning from the new files.
    • A real update on Linux x64, from a test folder pretending v0.0.212 to v0.0.213: download, verify, 10 files replaced, restart; old files cleaned at the next start; no install button in a build tree.
    • v0.0.213's real tar.gz and zip unpacked by the new code byte-identically to Python's.

    Known

    • The update feature ships in this version, so an actual update from the launcher first happens with the next release.
    • A real Win32 replacement and restart, and a real Steam Deck, were not checked.
    • #12 stays open: one pass does not settle an intermittent failure, and a later failure will name its claim.
  11. 2026-10-10

    v0.0.214

    Changes since v0.0.213 (issue #45, a #34 follow-up). Fullscreen and the aspect ratio can be switched from the in-game OSD, and the choice lasts to the next run. In the launcher, a gamepad's A starts a game.

    Full release notes

    Changes since v0.0.213 (issue #45, a #34 follow-up). Fullscreen and the aspect ratio can be switched from the in-game OSD, and the choice lasts to the next run. In the launcher, a gamepad's A starts a game.

    Resolved issues

    issue Summary Commit
    #45 Fullscreen and keep-aspect options in the OSD and launcher, stored in cfg\repiu.ini f21ac2e
    #34 (follow-up) A gamepad's A starts a ROM set from the launcher list f21ac2e

    Changed

    Fullscreen and keep aspect ratio. The top of the Tab OSD now has "Fullscreen" and "Keep aspect ratio". Keep aspect is on by default, keeping today's 4:3 picture with black bars; off, it ignores the ratio and fills the window or screen, windowed mode included. A change from the OSD, Alt+Enter or a double click is stored at once as [Video] fullscreen and keep_aspect in cfg\repiu.ini; the two checkboxes at the top of the launcher's Options use the same keys; REPIU_GLIDE_FULLSCREEN and REPIU_GLIDE_KEEP_ASPECT win over the stored values for that run. The next run starts the way it was left. The launcher window follows both values too: it opens fullscreen, switches with Alt+Enter, and with keep aspect on is drawn centred with black around it. The launcher's Options now read Fullscreen, Keep aspect ratio, vsync, Full-precision textures, Screen shader, Sound gain.

    Starting from the launcher with a gamepad. On the ROM set list, choose with the D-pad and press A to start, as Enter does.

    Verified

    • Linux x64 and i386 Release: builds; the core probe, including the letterbox probe's stretch case and the launcher settings' two keys.
    • Win32: a CI Debug build of every target, repiu_glide_issue_probe and repiu_aot_probe --timer-safe-point; the core and launcher probes and a run were not done on Win32.
    • Real runs (Linux x64, x11), confirmed on screen by the user: one store per OSD switch (14/14), the stored state restored at start, leaving fullscreen on close not stored, the launcher's checkboxes, window and Options order, and starting with the pad's A.

    Known

    • No game was run on the i386 build (build and core probe only); the changed code is architecture-neutral.
    • Switching under the Wayland video driver was not checked (every run used x11).
    • Whether an OSD change in a game started from the launcher shows in the launcher it returns to was not checked separately.
  12. 2026-10-10

    v0.0.213

    Changes since v0.0.212 (issue #37). Textures are drawn with the game's original 8-bit color, so faces, clothes and backgrounds whose gradients broke into bands are smooth.

    Full release notes

    Changes since v0.0.212 (issue #37). Textures are drawn with the game's original 8-bit color, so faces, clothes and backgrounds whose gradients broke into bands are smooth.

    Resolved issues

    issue Summary Commit
    #37 Full-precision textures bypassing the embedded Mesa driver's 4444 cut f31b7cc

    Changed

    Full-precision textures (on by default). The PIU executables carry a Mesa 3.x driver that turns OpenGL into Glide, and it cuts every color texture to 4 bits (ARGB4444) or 5/6 bits (RGB565) per channel before the hardware sees it. rePIU finds the original 8-bit image the driver still holds and uses it instead only when cutting it again equals the game's data byte for byte; any texture not confirmed is drawn as before. The memory and behavior the game sees do not change.

    The arcade board showed the 4444 picture too, so this option goes beyond the original; turn it off for the arcade look. Switch it with "Full-precision textures (8-bit)" in the in-game Tab OSD (that run, at once), "Full-precision textures" in the launcher's Options ([Video] texture_full_precision in cfg\repiu.ini), or REPIU_GLIDE_TEXTURE_FULL_PRECISION=0 (that run, over the launcher's value).

    Comparison screenshots and how it works are in the dev log.

    Verified

    • Win32 Release: build, the new mesa_fx_texture_source probe, the launcher probe and the core probe.
    • Linux x64 Release: build and the core probe.
    • Real game: every upload replaced by its original in pumpitea (61/61) and pumpit8 (130/130) with no fallbacks, and the OSD toggle works.

    Byte signatures in the executables show all 19 ROM sets carry the same Mesa build.

    Known

    • ROM sets other than pumpitea and pumpit8 have not been run; a texture that fails the check is drawn as before.
    • Each full-precision texture keeps two extra copies in host memory (about 32 MB at most); GPU memory is unchanged.
    • The Linux i386 build was not checked.
  13. 2026-10-09

    v0.0.212

    Changes since v0.0.211 (issue #34). Gamepads and joysticks, USB dance pads included, now drive the game and the launcher.

    Full release notes

    Changes since v0.0.211 (issue #34). Gamepads and joysticks, USB dance pads included, now drive the game and the launcher.

    Resolved issues

    issue Summary Commit
    #34 Gamepad and joystick input for the game and the launcher 6e9cba3

    Changed

    A standard gamepad works with no configuration: the first one connected is P1 and the second P2. LB and RB are the upper-left and upper-right panels, D-pad down and left the lower-left one, B and D-pad right the lower-right one, A and Start the center, Back inserts a coin, and the right stick click is CLEAR. TEST and SERVICE have no gamepad default, so a pad cannot open the operator menus by accident (they stay on F1 and F2).

    To change the layout or use another device, mix pad names with key names in the [Input] values of cfg/<rom-set>.ini; devices without a standard mapping (USB dance pads and the like) are bound by button or hat number, as in P1_CENTER = S, Pad1_A, Joy1_Button5 or P1_UP_LEFT = Q, Joy1_Hat1Up. Devices are numbered in connection order (Pad1–Pad4, Joy1–Joy8) and announce themselves on stderr as [repiu-pad] Pad1 connected: <name>. An input held by both a key and a pad stays held until both release, and losing window focus releases pads too. The full list of names is in §6.1 of the config file guide.

    The launcher works with a gamepad too: move with the D-pad, choose with A.

    Existing config files: an input line you wrote yourself replaces the gamepad default as well; lines left commented out follow the new defaults. Newly generated config files list the gamepad names in their comments.

    Verified

    • Win32 Release: build, the new host_pad_input probe, 94 config probe checks, the full repiu_aot_probe chain and the full core probe.
    • Linux x64 Release: build and the full core probe.
    • Real hardware: the user checked the game, the launcher and the default layout on a standard gamepad.

    Known

    • Analog stick directions and joystick axes are not supported.
    • Dance-pad joysticks (Joy<N>_ names) were not tried on a real device.
    • The Linux i386 build was not checked.
  14. 2026-10-09

    v0.0.211

    Changes since v0.0.210 (issues #30, #8, #31). The deferred guarded segment kill switches are gone, and two fixes that had been lost with an unmerged branch — the Win32 Release probe fix and the getch HLE — are back.

    Full release notes

    Changes since v0.0.210 (issues #30, #8, #31). The deferred guarded segment kill switches are gone, and two fixes that had been lost with an unmerged branch — the Win32 Release probe fix and the getch HLE — are back.

    Resolved issues

    issue Summary Commit
    #30 Retire the guarded segment load/pop/read kill switches (environment toggle cleanup, deferred item) cf3c73b
    #8 stack_bridge_contract fails in Win32 Release builds of repiu_core_probe cf3c73b
    #31 INT 21h AH=07h/08h console input without echo (getch) cf3c73b

    Changed

    A fatal path waiting on getch now takes a key. The game's fatal handler waits with Watcom's getch (INT 21h AH=08h), and without that service reaching the path died on a nameless fault. AH=07h/08h now read the BIOS keyboard buffer and, with nothing queued, let the guest ask again. This was Task 764 on 2026-10-01, missing because its branch was never merged.

    Three variables are no longer read: REPIU_AOT_GUARDED_SEGMENT_LOAD, _POP and _READ; the paths are always on for the dynamic backend. They were kept as an escape hatch after v0.0.206 changed the slot internals, and v0.0.207 to v0.0.210 showed no regression.

    The Win32 Release core probe passes in full. The stack_bridge_contract refusal check relied on whatever the compiler left in EAX and failed only in Release; the engine was fine (Task 765's fix, missing for the same reason).

    New probe: repiu_aot_probe --dos-console-input (also in the core probe).

    Development rules: AGENTS.md now says work continues on the current branch when it is not main, and how a pull request that resolves several issues closes them.

    Verified

    • Win32 Release: build; the full core probe including stack_bridge; the full repiu_aot_probe chain (exit 0); both Glide probes.
    • Linux x64 Release: build; all core probes pass.
    • Linux i386 Release: all core probes pass.

    Known

    • No game run: the guarded segment defaults are unchanged, and getch is reached only on a fatal path (checked on pumpipx3 back in Task 764).
  15. 2026-10-09

    v0.0.210

    Changes since v0.0.209 (issue #25). Nine one-off diagnostics added for investigations that are already answered are gone. Every deleted block ran only with its variable set, so a run with nothing set behaves as v0.0.209 did.

    Full release notes

    Changes since v0.0.209 (issue #25). Nine one-off diagnostics added for investigations that are already answered are gone. Every deleted block ran only with its variable set, so a run with nothing set behaves as v0.0.209 did.

    Resolved issues

    issue Summary Commit
    #25 Delete one-off diagnostics for answered questions (environment toggle cleanup follow-up) b24ca84

    Changed

    Nine variables are no longer read; setting them has no effect: REPIU_GLIDE_CALL_AUDIT, REPIU_GLIDE_TEX_CENSUS, REPIU_GLIDE_DRAW_CENSUS, REPIU_GLIDE_TRI_CENSUS, REPIU_GLIDE_FRAME_DUMP, REPIU_DUMP_LFB_BMP, REPIU_GLIDE_VERTEX_DEPTH_CENSUS, REPIU_LOWMEM_TRACE and REPIU_AOT_PROBE_GUEST. Per-ordinal call counts and first arguments remain in the final log's Glide call trace lines, texture format and size counts in the default texture census, and texture dumps in REPIU_GLIDE_TEX_DUMP (TGA).

    Final log: the Glide vertex depth census … and AOT runtime cache probe … lines, printed only with those variables set, are gone; default output is unchanged.

    Performance: REPIU_DUMP_LFB_BMP called getenv on every LFB present; that call is gone.

    Kept: the candidates REPIU_AOT_DBT_CALL_TRACE, REPIU_AOT_RETIRED_TRAP_PROFILE, REPIU_GLIDE_SETTER_CENSUS and REPIU_GLIDE_SETTER_PHASE still work, since they measure a live mechanism or a guide or script uses them.

    Verified

    • Win32 Release: build; the full repiu_aot_probe chain (exit 0); both Glide probes.
    • Linux x64 Release: build; all core probes pass.

    Known

    • No game run; the deleted code is not on the default execution path.
    • The Linux i386 build was not checked.
    • The Win32 core probe's stack_bridge failure predates this release (#8).
  16. 2026-10-09

    v0.0.209

    Changes since v0.0.208 (issue #24). Three kill switches hidden behind diagnostic names and two default-off DOS/DPMI experiments are gone. A run with nothing set behaves as v0.0.208 did.

    Full release notes

    Changes since v0.0.208 (issue #24). Three kill switches hidden behind diagnostic names and two default-off DOS/DPMI experiments are gone. A run with nothing set behaves as v0.0.208 did.

    Resolved issues

    issue Summary Commit
    #24 Retire the kill switches hidden behind diagnostic names and two DOS/DPMI experiments (environment toggle cleanup, group 3) 5444d28

    Changed

    Six variables are no longer read; setting them has no effect. The features stay always on and only the off switch is gone for REPIU_GLIDE_DRAW_ENTRY_POINTS (drawing the point, AA and polygon entry points), REPIU_TIMER_TICK_BACKLOG (keeping owed timer ticks) and REPIU_JAMMA_SNAPSHOT (the input snapshot); the snapshot interval REPIU_JAMMA_SNAPSHOT_US still works, and 0 queries on every read. The experiments themselves are gone for REPIU_DOS4GW_MEMORY_PATH_PROBE (a PharLap marker on the DOS version reply) and REPIU_DPMI_1E7F_PROBE_SUCCESS/REPIU_DPMI_1E7F_TRACE (a fake success for INT 31h AX=1E7Fh, a call that came from stack corruption fixed long ago).

    Diagnostic output changed format; every removed value was always zero in a default run. The final log's tick line becomes timer tick delivery due/injected/dropped/deferred/max-backlog/remaining (the backlog-enabled and coalesced columns go) and the gate line becomes timer tick in-gate due: N; the CD audio position census file loses its ticks_coalesced and ticks_coalesced_in_gate columns — update any script of your own that reads these formats.

    Probe: the repiu_aot_probe expectation left stale by v0.0.208's removal of an execution time bucket is fixed, so the probe chain runs to the end on Win32.

    Scripts and docs: the backlog A/B script task366 is deleted and task414 follows the new log line; the frontier's toggle table shows the current state, and README's texture dump entry names the real variable, REPIU_GLIDE_TEX_DUMP.

    Verified

    • Win32 Release: build; the full repiu_aot_probe chain (exit 0); the timer tick probe; both Glide probes; two alternating 60 s pumpit2a pairs against the v0.0.207 release (zero faults, 3,202 / 3,203 frames in the second pair).
    • Linux x64 Release: build; all 39 core probes pass.

    Known

    • The Linux i386 build was not checked.
    • The Win32 core probe's stack_bridge failure predates this release (#8).
    • In the first pumpit2a pair the new build fell about a second behind after two hitches; the second pair matched down to the per-second fps, so it was judged run-to-run variation.
  17. 2026-10-07

    v0.0.207

    Changes since v0.0.206 (issue #20). The kill-switch environment variables of 15 long-promoted features are gone. Every feature stays on, and a run with nothing set behaves as v0.0.206 did.

    Full release notes

    Changes since v0.0.206 (issue #20). The kill-switch environment variables of 15 long-promoted features are gone. Every feature stays on, and a run with nothing set behaves as v0.0.206 did.

    Resolved issues

    issue Summary Commit
    #20 Retire the kill switches of long-promoted features (environment toggle cleanup, group 1) 8f84450

    Changed

    Fifteen variables are no longer read; setting them has no effect and the features are always on: REPIU_AOT_DBT_PORT_IO_DISPATCH, REPIU_AOT_DBT_RETURN_MISS_DISPATCH, REPIU_AOT_DBT_DIRECT_EDGE_DISPATCH, REPIU_AOT_DBT_TIMER_SAFE_POINTS, REPIU_AOT_INLINE_CACHE_PATCH_INLINE, REPIU_AOT_DIRECT_RETURN_TABLE (the table size REPIU_AOT_DIRECT_RETURN_TABLE_BITS still applies), REPIU_GLIDE_SETTER_ELIDE, REPIU_GLIDE_SETTER_ELIDE_TEXTURE, REPIU_GLIDE_SETTER_ELIDE_BATCH3, REPIU_GLIDE_SETTER_ELIDE_BATCH4, REPIU_GLIDE_DRAW_BATCH, REPIU_GLIDE_HOST_WAIT, REPIU_GLIDE_GATE_PUMP, REPIU_PORT_IO_DELAY_LOOP and REPIU_NATIVE_LINEAR_SPAN_REJECT_CACHE.

    Two summary lines changed format: AOT inline cache patch direct/worker: N/0 is now AOT inline cache patches: N (the worker round-trip patch path is gone), and the Glide setter elision … line drops its three always-true batch flags.

    One behavior change: on the legacy backend (REPIU_EXECUTION_BACKEND=legacy, kept for diagnosis) the reject cache can no longer be turned on with REPIU_NATIVE_LINEAR_SPAN_REJECT_CACHE=1. The default dynamic backend is unchanged.

    Docs: the full survey is in docs/analysis/environment-toggle-inventory.md; the deferred items (removing the abandoned opt-in experiments, and the REPIU_AOT_GUARDED_SEGMENT_LOAD/POP/READ switches) are in docs/TODO.md with resume conditions; the A/B procedures in three guides, including glide-setter-elision-testing.md, are marked as concluded records — setter elision can no longer be switched off with an environment variable, so please report any visual problem as an issue with the log.

    Diagnostics: repiu_aot_probe gains standalone --inline-cache, --native-linear-span, --glide-draw-batch and --glide-setter-state-cache flags.

    Verified

    Win32 x86 Release: build; six probes (the four above plus --selector-guard and --segment-restore); a 16-romset 30 s smoke alternating the v0.0.206 build and this one per game (zero exceptions on both sides, matching frames on 15 romsets); a 90 s pumpitea pair (post-logo gap 2.78 s / 2.79 s, identical port I/O delay-loop statistics).

    Known

    • One pumpitp2 30 s smoke run ended with zero frames (no exception, guest in a wait loop). An interleaved retest ran clean three times on the new build and twice on the old one, and the same thing happened once during the v0.0.206 checks, so it is taken as an intermittent issue unrelated to this change.
    • Linux was not built; only platform-neutral engine code changed.
  18. 2026-10-07

    v0.0.206

    Changes since v0.0.205 (issue #18). pumpitea's loading gap after the ANDAMIRO logo drops from ~29 s to ~1.5–2.7 s — shorter than the pre-regression baseline (v0.0.180, 5.0 s) — and the second stall around 47 s shrinks from 2.5–6.5 s to 1.4–2.1 s.

    Full release notes

    Changes since v0.0.205 (issue #18). pumpitea's loading gap after the ANDAMIRO logo drops from ~29 s to ~1.5–2.7 s — shorter than the pre-regression baseline (v0.0.180, 5.0 s) — and the second stall around 47 s shrinks from 2.5–6.5 s to 1.4–2.1 s.

    Resolved: #18 (whole-cache segment re-patch on every ES change) — commits 612251c, 804df40, 7a5c88c, c6426c2, 39c0620.

    New: shadow-authoritative i386 guarded segment slots (a dedicated probe proved a Win32 VEH resume never installs a guest selector into a physical segment register; the load, pop and memory-source-load slots now compare against the shadow and a per-register accepted pair and write the shadow natively, the read slot reads it unguarded, and timer injection enters the INT8 handler through its cache block); host-direct dispatch for hot INT 21h services (AH=2Ch/42h; AH=3Fh read is excluded because its guest-memory writes followed by the dispatcher's direct cache jump skip the reentry funnel's retirement checks and broke eight titles), promoted to default-on following the Port-I/O precedent (REPIU_AOT_DBT_SUPERBLOCK=0 restores INT3-only boundaries); new repiu_aot_probe --selector-guard and --segment-restore flags; and the full record of the three rejected re-patch-cheapening variants in docs/analysis/pumpitea-loading-segment-flip.md.

    Verified: Win32 x86 Release build, both probes, interleaved 90 s pumpitea measurements (gap 1.5–2.7 s, second stall 1.4–2.1 s, zero exceptions), and a 16-romset 30 s smoke matrix default-on.

    Known: the fast/slow mode selector remains unidentified (EEPROM contents and pure warm-up excluded); an intermittent pumpit3a guest AV during loading (~1 in 4, EBX=0) is suspected to be a pre-existing race; the far-strcmp fallbacks (2,530 per 90 s, ~0.1 s) remain; the promotion and new slots are measured on Win32 only, with long-mode slots unchanged.

  19. 2026-10-05

    v0.0.205

    Changes since the previous release, v0.0.204 (issue #15). The in-game Tab OSD now spans the top of the screen and opens with the name, version, build date and Target Profile, and OSD and launcher text grows with the window.

    Full release notes

    Changes since the previous release, v0.0.204 (issue #15). The in-game Tab OSD now spans the top of the screen and opens with the name, version, build date and Target Profile, and OSD and launcher text grows with the window.

    Resolved issues

    Issue Summary Commit
    #15 The OSD in re2DJ's layout; OSD and launcher text that scales with the window d44a1e5

    New

    OSD layout (#15). The same layout as re2DJ's OSD.

    • Across the full width at the top of the screen, as tall as its content.
    • First line: rePIU v0.0.205 (Win/x86 Release) - Build … (written as in the window title). Second line: the running ROM set, as in Target Profile : pumpit1. v0.0.204's Renderer section, the LFB toggle and the screen shader menu follow.

    Text that scales with the window (#15).

    • OSD text and spacing grow with the window height: 0.75x in a 1x window (640×480), 1.5x in the default 2x window, 3.375x in 4K fullscreen. Before, the text stayed at 13 px however large the window, and so looked ever smaller.
    • The launcher shown without arguments can now be resized and maximised, and its text, table and buttons grow with it. The default size looks as before, and the vertical scroll bar it always showed is gone.

    Font. The OSD and the launcher both draw with ProggyForever, Dear ImGui's scalable default font, as re2DJ does. Dear ImGui moves from v1.92.1 to v1.92.9 for it.

    Verified

    Configuration Check
    Win32 x86 Release build, repiu_core_probe and repiu_aot_probe --imgui-ui-scale, pumpit1 OSD captures (1x, 2x, 4K fullscreen), launcher captures (default 960×640, maximised 3840×2054)
    Linux x64 Debug (WSL) build; the whole repiu_core_probe passes
    Win32 x86 Debug (CI) build and probes

    Known

    • The new OSD layout was not checked on screen on Linux (it is the same shared code, and v0.0.204's OSD was checked on screen on Linux).
    • At the launcher's default size, long titles reach the Status column's edge (as before); enlarging the window clears it.
    • Earlier known issues remain: #7 i386 on Wayland (NVIDIA) failing to create its window, #8 the Win32 Release core probe's stack_bridge_contract, and #12 the intermittent Linux x64 host_thread_interrupt failure in the release CI.
  20. 2026-10-05

    v0.0.204

    Changes since the previous release, v0.0.203 (issue #5). The in-game Tab OSD now opens with the OpenGL renderer drawing the picture, and warns when it is a software renderer.

    Full release notes

    Changes since the previous release, v0.0.203 (issue #5). The in-game Tab OSD now opens with the OpenGL renderer drawing the picture, and warns when it is a software renderer.

    Resolved issues

    Issue Summary Commit
    #5 The OpenGL renderer in the in-game OSD 7edcfa2

    New

    A Renderer section in the OSD (#5). The top of the OSD opened with Tab shows:

    • The renderer (for example NVIDIA GeForce RTX 4090/PCIe/SSE2), its vendor and the OpenGL version.
    • The SDL video driver: windows on Windows, x11 or wayland on Linux, noting when the engine chose Mesa's D3D12 driver on WSL.
    • A software renderer such as llvmpipe, softpipe, swrast, GDI Generic (Windows without a GPU driver), WARP or SwiftShader is shown in red with "Software rendering: no 3D acceleration". If the game runs unusually slowly, look here first.

    It only displays; no GL call is added. The log gains one line, GL vendor/version/video driver/software: …, next to the existing GL renderer: line.

    Verified

    Configuration Check
    Win32 x86 Release build, repiu_core_probe and repiu_aot_probe --gl-renderer-identity, the OSD captured in pumpit1 (RTX 4090, windows)
    Linux x64 Debug (WSL) build, the whole repiu_core_probe passes, the warning captured in pumpit1 with GALLIUM_DRIVER=llvmpipe (x11)
    Win32 x86 Debug (CI) build and probes

    Known

    • The software check is a name list; a software renderer not on it shows its name without the warning.
    • This feature was not run on Linux i386 or under Wayland (the code is the same).
    • v0.0.203's known issues remain: i386 on Wayland (NVIDIA) failing to create its window (#7) and the Win32 Release core probe's stack_bridge_contract (#8). The intermittent Linux x64 host_thread_interrupt failure in the release CI (#12) is being tracked.
  21. 2026-10-05

    v0.0.203

    Changes since the previous release, v0.0.200 (Tasks 771–773, issues #4 and #6). The v0.0.201 release build failed and was never published, and v0.0.202 changed only documentation rules, so both are described here. Linux i386 is much faster (pumpit1 from about 1,990 to 3,190 frames in 60 s), and minimising the window no longer slows the game's time. The screen shader can be chosen on the command line.

    Full release notes

    Changes since the previous release, v0.0.200 (Tasks 771–773, issues #4 and #6). The v0.0.201 release build failed and was never published, and v0.0.202 changed only documentation rules, so both are described here. Linux i386 is much faster (pumpit1 from about 1,990 to 3,190 frames in 60 s), and minimising the window no longer slows the game's time. The screen shader can be chosen on the command line.

    Resolved issues

    Issue Summary Commit
    #6 Direct model (i386): timer ticks dropped while a hidden window blocks the vsync swap 6574861
    #4 Tasks as GitHub issues, and main merges through squash-merged pull requests dcc73e5

    Work from before the switch to issues: Task 771 77117bf (v0.0.201), Tasks 772–773 6574861 (part of v0.0.203).

    New

    Glide calls linked on Linux i386 (Task 773). On Linux i386 the code cache sits near 0xE8000000, so the call rel32 that links a game's Glide call straight to its gate was never written, and every Glide call took a boundary path costing about 160 µs. A 32-bit instruction pointer wraps at 2^32, so the wrapped displacement is now written. Windows, whose cache sits low, never had this problem.

    • pumpit1, 60 s: about 1,990 → 3,190 frames (vsync off: 2,100 → 53,197); guest thread CPU 99.4% → 16.3%.
    • pumpit8, 60 s: 439 → 3,288 frames.

    Game time kept while the window is minimised (#6). A hidden window has its vsync swaps slowed by the compositor to about one a second, and Linux i386 dropped the timer ticks that piled up meanwhile, so the game's time fell behind (more than 4,200 in 12 s minimised). Once a swap wait passes 50 ms, ticks are now delivered during the wait. Behaviour and frames with a visible window are unchanged.

    • Ticks dropped in 12 s minimised: pumpit1 4,456 → 21, pumpitea 4,451 → 54.
    • Windows does not block the swap of a minimised window, so it never dropped ticks in bulk. The same path is on there, with the same behaviour.

    Screen shader on the command line (v0.0.201, Task 771). repiu pumpit1 --post-shader crt (or --post-shader=crt). It wins over REPIU_POST_SHADER and the launcher setting; a missing value is an error.

    New switches (default behaviour when unset)

    • --post-shader <id> — the screen shader for that run.
    • REPIU_GLIDE_SWAP_WAIT_TICKS=0 — turns off tick delivery during the swap wait (the earlier behaviour).
    • REPIU_GLIDE_SWAP_WAIT_TICK_HOLD_MS — how long a wait lasts before delivery starts. Defaults to 50 ms on the direct model (Win32, Linux i386) and 0 ms on Linux x64.

    Verified

    Configuration Check
    Linux i386 Release (Ubuntu 26.04, RTX 4090, x11) core probe, normal runs and 12 s minimised runs of pumpit1, pumpit8 and pumpitea, alternating with the switch off
    Linux x64 Debug (same machine) build, core probe, no regression in pumpit1, three --post-shader cases, fullscreen round trips (Wayland and x11), the v0.0.200 archives
    Win32 x86 Release (Windows 11) build, the two new probes (core and AOT), normal runs and 12 s minimised runs of pumpit1, pumpit8 and pumpitea, a 0 ms hold stress run (3,804 injections, same frames)
    Win32 x86 Debug (CI) build and probes

    Known

    • Linux i386 on Wayland (NVIDIA) cannot create its window (#7). With the 32-bit Wayland libraries installed, SDL picks Wayland and fails to create the EGL surface. Run with SDL_VIDEO_DRIVER=x11 for now.
    • The Win32 Release core probe's stack_bridge_contract=false looks like a problem in the probe and is tracked in #8.
    • Glide calls the game makes indirectly (about 1.5%) still take the boundary path.
    • v0.0.200's known issues (one frame right after a Linux window resize, leaving fullscreen on WSLg Wayland) remain.
  22. 2026-10-04

    v0.0.200

    Changes since the previous release, v0.0.197 (Tasks 764–770). v0.0.198 and v0.0.199 were tagged without release notes, so they are described here as well. Screen post-processing shaders (crt, scanline) and a fullscreen toggle are in, and the original 4:3 picture is kept whatever shape the window takes. Vertical sync is on by default, and the GitHub release carries Linux i386 and x64 archives.

    Full release notes

    Changes since the previous release, v0.0.197 (Tasks 764–770). v0.0.198 and v0.0.199 were tagged without release notes, so they are described here as well. Screen post-processing shaders (crt, scanline) and a fullscreen toggle are in, and the original 4:3 picture is kept whatever shape the window takes. Vertical sync is on by default, and the GitHub release carries Linux i386 and x64 archives.

    New

    Screen post-processing shaders (Task 768). One presentation-only pass runs just before the buffer swap; the framebuffer the guest reads is unchanged.

    • Built-in crt (curvature, beam-shaped scanlines, a mask and a vignette) and scanline. .glsl files in the libretro single-pass layout dropped into shaders\ join the list.
    • Chosen in the launcher's Options "Screen shader" (stored in cfg\repiu.ini), with REPIU_POST_SHADER=crt (that run, over the launcher's value), or in game from the Tab OSD (that run only, with Reload and parameter sliders).
    • The default is none, which adds no GL call.
    • The brightness profile of a line is aligned to output pixels so scanlines show in the default 2x window. The crt curvature defaults to 0.01.
    • Format and checks: the post-processing shader guide. Dev log: the WIP post.

    Fullscreen toggle and aspect ratio (Task 769). Double-click the game window or press Alt+Enter to switch between borderless fullscreen and windowed mode; the display resolution is left alone. Whether the window is resized or fullscreen, the picture keeps its original ratio with black bars around it, and the bars never reach what the guest reads back. Alt+1 to Alt+4 pick a 1x to 4x window in windowed mode. Before, there was no fullscreen and a stretched window stretched the picture with it.

    Vertical sync on by default (v0.0.198, Task 766). Without REPIU_GLIDE_SWAP_INTERVAL the engine never requested a swap interval, so SDL's Wayland backend drew without vsync. It now always requests one once a GL window exists (the variable's value when valid, 1 otherwise). REPIU_GLIDE_SWAP_INTERVAL=0 or unchecking "Wait for vertical sync" in the launcher turns it off.

    Linux release archives (v0.0.199, Task 767). The release workflow builds Linux i386 and x64 Release on ubuntu-22.04 next to Win32 and attaches them as rePIU-v<version>-linux-<arch>.tar.gz. The C++ runtime is linked statically, so they need only libGL and glibc 2.35 or newer.

    Screenshots (Task 770). Screens from 16 ROM sets are in docs/screenshots/, shown in the README and on the project site's introduction; the shader comparison shots are in docs/screenshots/shaders/.

    Site and credits (Tasks 764–765). The site gained a Pump It Up Exceed arcade layer, and LICENSE (BSD 3-Clause), CREDITS.md and a site Credits page are in.

    New switches (default behaviour when unset)

    • REPIU_POST_SHADER — none (default), crt, scanline, or a file name in shaders\.
    • [Video] post_shader in cfg\repiu.ini — stored by the launcher.

    Verified

    Configuration Check
    Win32 x86 Debug build, repiu_aot_probe --post-shader, --glide-letterbox and --launcher, repiu_glide_render_probe --opengl-post-shader and --opengl-lfb (Intel Arc), pumpit1 15 s with none, crt and scanline
    Win32 x86 Release build, 16 ROM sets captured for 90 s each (the screenshots), pumpit8 with an input script captured under none, crt and scanline
    Linux x64 Debug (WSL) build, the three probes above (ad hoc build), the GL probe under x11 and Wayland × d3d12 and llvmpipe, pumpit1 30 s under x11 with four fullscreen round trips by real X input

    Known

    • On Linux, the one frame right after a window resize can be drawn with the new viewport into the old-size buffer, because Mesa resizes the buffer only at the next swap; the next frame is right. For the same reason the letterbox check of repiu_glide_render_probe --opengl-post-shader fails on Linux (a problem in the check).
    • Leaving fullscreen on WSLg Wayland is slow and erratic (13 to 368 swaps, once not within 600). The engine requests it correctly; under x11 and on Windows it leaves at once. A real Wayland desktop was not checked.
    • repiu_aot_probe and repiu_glide_render_probe are not built on Linux.
    • The Linux i386 build and the web build were not checked this time.
    • v0.0.197's known issues (the Win32 Release core probe's stack_bridge_contract=false, among others) remain.
  23. 2026-09-30

    v0.0.197

    Changes since the previous release, v0.0.195 (Tasks 761–762). v0.0.196 was tagged without a release note, so it is described here as well. pumpit8 on Win32 no longer stops after loading its first song and pumpipx3 no longer dies at start, and the Glide LFB high-precision presentation and the first in-game OSD are in.

    Full release notes

    Changes since the previous release, v0.0.195 (Tasks 761–762). v0.0.196 was tagged without a release note, so it is described here as well. pumpit8 on Win32 no longer stops after loading its first song and pumpipx3 no longer dies at start, and the Glide LFB high-precision presentation and the first in-game OSD are in.

    Fixed (v0.0.197, Task 762)

    pumpit8 on Win32 stopped after loading its first song. From v0.0.191 to v0.0.196 it stopped at the same place: having read the song, the game waits for the timer tick counter to reach 1200 (five seconds), and no tick reached that wait loop.

    • Cause. Win32 (and Linux i386) run the guest's iret as it is, so the engine never saw a timer handler return. The code a handler returned to ran in place unsupervised, and once it entered a loop without a trap there was no place to inject the next tick. Up to v0.0.190 the trace flag (TF) left in the injected frame happened to keep the code supervised; v0.0.191 (Task 736) removed it and the path was cut. Restoring that rule made the next run nest handlers until the stack had dropped 320 KiB, so it was not restored.
    • Fix. An injected timer frame returns to the return pad, a reserved page that faults when executed. From that fault the engine restores the real return address and execution state, sends code that runs in place back into the code cache and, now that the return is seen, switches on for Win32 the nesting, turn and chain rules (Task 751) proven on Linux x64.
    • Result (Win32 Release, input script). pumpit8 for 100 seconds: 5,004 frames and the song playing at 60 fps with 22,655 of 22,757 ticks delivered, against 2,086 frames, a stop and 9,573 of 22,771 in v0.0.196; the song's file opens 11 seconds earlier. pumpitea, pumpit2a and pumpitpc for 60 seconds: 99.5% or more of the ticks delivered.

    pumpipx3 on Win32 died with Fatal error: unable to find entry point in DLL. It went away with the same change: three 30-second runs completed, and with the switch off two runs died as before. The change did not aim at it, and why the tick injection path decides that failure is not explained.

    Loading looked slow. With the dropped ticks now delivered the game's clock runs at its own speed; pumpit8 opens its song's file 11 seconds earlier under the same input.

    New (v0.0.196, Task 761)

    • Glide LFB high-precision presentation. 24/32-bit sources of grLfbWriteRegion keep their precision to the screen instead of being quantized to 565; the 565 buffer the guest reads is unchanged. On by default; REPIU_GLIDE_LFB_HIGH_PRECISION=0|off|false turns it off.
    • In-game OSD. Tab opens and closes it and never reaches game input. Its one item is the high-precision toggle, which takes effect at the next LFB write.
    • The PTX header census and the per-version asset generations are recorded (pumpitea is generation 2, PNG and PTX).

    New switches and diagnostics (default behaviour when unset)

    • REPIU_TIMER_RETURN_PAD — 0 writes the real return address into the injected frame as v0.0.196 did (and pumpit8 stops again). The final report's timer return pad pushed/returned/supervised/overflow/unmatched/abandoned/depth-max/depth-at-end line says how many returns were taken.
    • REPIU_GLIDE_LFB_HIGH_PRECISION — above.
    • Core probe timer_return_pad, repiu_aot_probe --timer-return-pad.

    Verified

    Configuration Check
    Win32 x86 Release pumpit8 100 s ×2, pumpitea, pumpit2a and pumpitpc 60 s, pumpipx3 30 s ×3, control runs with the switch off
    Win32 x86 Debug build, every core probe passes, aot probes (--timer-return-pad, --pic-timer-in-service, --jamma-input-timeline, --shutdown-recovery-policy)
    Linux x64 Release and Debug build, every core probe passes, pumpit8 50 s (no change in behaviour)
    Linux i386 (WSL) build, every core probe passes, pumpit8 70 s without a fault

    The picture and the sound were checked by numbers only. No survey of every ROM set.

    Known

    • The core probe of a Win32 Release build fails with stack_bridge_contract=false. v0.0.195's Release build fails the same way, so it is not new in this release. Debug builds pass.
    • Why pumpipx3 came back is not explained (above).
    • Linux i386 under WSL runs at 4 to 13 fps, as before.
    • The web build was not checked.
    • v0.0.194's known issues (the music slowing down under WSL, among others) remain.
  24. 2026-09-28

    v0.0.195

    Changes since the previous release, v0.0.194 (Tasks 756–760). A release that puts the source directories' rule in order; the games behave as they did. Code that depends on the OS is gathered in the platform layer, and the engine's difference by CPU architecture in an execution model. Two defects found while verifying are fixed with it (the Linux i386 build could not open its window on WSL, and the Win32 core probe aborted part way).

    Full release notes

    Changes since the previous release, v0.0.194 (Tasks 756–760). A release that puts the source directories' rule in order; the games behave as they did. Code that depends on the OS is gathered in the platform layer, and the engine's difference by CPU architecture in an execution model. Two defects found while verifying are fixed with it (the Linux i386 build could not open its window on WSL, and the Win32 core probe aborted part way).

    Structure

    v0.0.194 v0.0.195
    Platform and architecture branches outside the platform layer (src, include) 162 102
    Of those, in the engine, the runtime and headers 93 33
    #if asking about the architecture in the engine over 60 9 #error checks of model and diagnostic files

    The rule (Tasks 757–759)

    • The engine, runtime, HLE and tool directories have no platform or architecture subdirectories.
    • Code that depends on the OS is in src/platform/<OS>/[<arch>/], and the platform layer uses none of the engine's types. Inside the platform layer, code splits by architecture into x86/ and x64/.
    • The engine's difference by architecture is the execution model. direct runs the guest's bytes as they are (Win32, Linux i386), cache runs the translated long-mode code cache (Linux x64), and none runs no guest (the web build). Shared code asks through include/repiu/runtime/execution_model.h, and implementations that differ by model sit next to their shared file as <name>_direct.cpp and <name>_cache.cpp.
    • A diagnostic built on one host, and a guard around a whole probe, are not turned into directories.

    What moved

    • The Linux platform layer is split into shared, x86/ and x64/, and the Linux x64 public headers are under include/repiu/platform/linux/x64/ (Task 757).
    • The platform layer's host_thread, host_time, host_error_stream and build_identity, and the compiler shim headers, are split by OS (Task 758, phase 1).
    • The five MSVC inline-assembly thunks and the stack switch moved from the engine to src/platform/win32/.
    • The crash report, the host's own detail of a fault report, the choice of WSL's driver, symbol names, the trace dump, the telemetry's host reads and stopping a thread became functions of the platform layer.
    • The execution trampoline's 742 lines for x86-64 (the return resolver, the entry into the cache, the return traces) moved to execution_trampoline_cache.cpp, and its x86 entries to execution_trampoline_direct.cpp.

    Fixed

    The Linux i386 build could not open its window on WSL (Task 760) — v0.0.194's choice of the D3D12 driver looked for the driver file in a list that did not tell architectures apart, so an i386 process saw the x86-64 driver and set GALLIUM_DRIVER=d3d12. The choice is now made only when the driver of the process's own architecture exists.

    The Win32 core probe ended part way (Task 758) — the dos_console_device probe threw a filesystem_error on the CON: path, the core probe ended with exit code 3, and no probe after it ran.

    Also

    • A GitHub Pages project site, in Korean and English (Task 756).

    Checked

    Configuration Check
    Linux x64 Release and Debug, Linux i386 built; core probe result lines the same as before the directory work
    Win32 x86 Debug built; results of the core probe, 18 aot probe modes and the glide issue probe the same as before the directory work
    Runs (short) pumpit8, pumpitpc and pumpit1 on Linux x64, pumpit1 on Linux i386, pumpit8 on Win32: no faults

    Known

    • The web build was not checked. The machine this release was made on has no tools for it.
    • Win32 code remains in the engine: the Win32 thread procedure (SEH) of the execution trampoline, the exception entry and the shutdown recovery's vectored handlers. Moving them means changing how Win32 receives faults, so it is left as a task of its own.
    • The 69 platform branches of the tools and probes are as they were; about 30 are guards around a probe that runs on Win32 only.
    • Two functions nothing calls (PushX87Float, CaptureSuspendedThreadSnapshot) were kept.
    • v0.0.194's known items (the music slowing on WSL among them) stand.
  25. 2026-09-27

    v0.0.194

    Changes since the previous release, v0.0.193 (Tasks 751–754). On Linux x64 all 16 ROM sets that have a disc image run, and pumpit8's arrows no longer part from the music and go back. The engine keeps the guest's cli, draws with the GPU on WSL, ends an input record at its handler's return, and runs the timer ticks on the clock the audio follows. The arrows were confirmed by the user in play on WSLg (wayland).

    Full release notes

    Changes since the previous release, v0.0.193 (Tasks 751–754). On Linux x64 all 16 ROM sets that have a disc image run, and pumpit8's arrows no longer part from the music and go back. The engine keeps the guest's cli, draws with the GPU on WSL, ends an input record at its handler's return, and runs the timer ticks on the clock the audio follows. The arrows were confirmed by the user in play on WSLg (wayland).

    In numbers (WSL Linux x64 Release)

    v0.0.193 v0.0.194
    ROM sets running their 60 s (of the 16 with a disc image) 15 (pumpitpc died at 5 s) 16
    Tick storms right after start (18,000 ticks a second, no frames) 2 of 6 starts 0 of 32
    pumpit8's selection screens, fps 36–42 60
    Median fps over the last 20 s, pumpit8 and pumpitp2 39 60
    Presses the guest read in pumpit8 (the input script holds 139) 1,389–2,406 139
    Tick rate in pumpit8's play (on the audio's clock; 240 expected) 241–264 239.5–240.7

    Fixed

    The guest's cli is kept (Task 751) — user mode cannot clear IF, so timer ticks were injected even in the middle of a section the game wrapped in cli…sti. In pumpitpc the handler changed a board register in the middle of a transaction with the security chip (CAT702) and the boot check failed. A hold raised by cli now defers injection until sti or the iret of an injected frame (with a 100 ms valve). The rules of injection were set in order too: a chain right after a handler's return is at most 3 and at periods of 1 ms or more, nesting is one level deep, and the interrupted code gets a turn as long as the last handler took. The tick storm right after start is gone with these rules.

    aam and aad are emulated (Task 751) — long mode does not have them. They were why pumpitpc died at its first score display.

    The pacer no longer holds a late frame one period longer (Task 752) — its resynchronisation test compared against the frame's own deadline, so a frame 1 ms late became a 33 ms frame. Only a frame more than a period late resynchronises now, and a late frame passes without a sleep.

    The GPU draws on WSL (Task 752) — WSL offers the GPU only through Mesa's D3D12 driver, and Mesa picks llvmpipe by itself. With /dev/dxg and d3d12_dri.so present and no driver chosen by the user, the engine sets GALLIUM_DRIVER=d3d12.

    An input record ends at its handler's return (Task 753) — the input replay frame pushed when a tick is injected was retired by a stack test alone, so the frame of a tick injected at a stack level the guest does not come back to stayed to the end, and reads outside a handler were answered with the keys of an old time (a SERVICE press read 60 times a second). A frame now ends at the handler's iret, and Win32, which cannot see the return, retires it after more than 64 later injections.

    Timer ticks follow the audio's clock (Task 754) — the tick schedule alone used SDL's clock (CLOCK_MONOTONIC_RAW on Linux) while the audio and the rest used CLOCK_MONOTONIC. While WSL2 slowed CLOCK_MONOTONIC by 1–7%, the guest's time ran up to 9% ahead of the music, so the arrows ran ahead and came back. The tick schedule and the input timeline now follow steady_clock, and the engine says so when the two clocks part.

    New switches and diagnostics (default behaviour when unset)

    Variable / output What it is
    REPIU_GUEST_CLI_HOLD=0 turns the guest cli hold off
    guest cli hold cli/sti/blocked/expired/max-hold-us/held-at-end (final report) the hold's counts and its longest
    timer IRQ0 after-return …, timer IRQ0 nesting …, timer IRQ0 turn … (final report) deferrals by injection rule
    REPIU_PIU10_CAT702_TRACE=1 [repiu-cat702] #N bits= in= out= per CAT702 transaction
    [repiu-linexe] get-proc miss name=… a Glide name lookup that failed
    REPIU_WSL_D3D12=0 leaves Mesa's choice alone on WSL
    [repiu-glide] GL renderer: …, Glide GL renderer/wsl-d3d12-chosen (final report) what draws
    REPIU_GLIDE_LONG_FRAME_LOG=2 every frame as sleep, guest and present
    REPIU_JAMMA_REPLAY_FRAME_END=0 input frames are not ended by return (stack test and age only)
    REPIU_JAMMA_TIMELINE_TRACE=1 the ESP and interrupted EIP of each input frame retired by age
    JAMMA timeline frames ended-by-return/retired-stale/latest-state-reads (final report) how input frames were retired
    REPIU_EVENT_CLOCK=sdl the tick schedule follows SDL's clock as before
    [repiu-clock] …, host clock raw-against-steady … (final report) the two clocks' divergence (windows of 1 s, 0.5% limit)
    scripts/survey_romsets.sh <binary> <tag> [seconds] [profile…] a 60 s run per ROM set, summarised in build/survey/<tag>/summary.txt
    scripts/input_scripts/pumpit8_play.txt, survey_generic.txt input that drives pumpit8 into a song; the survey's common input

    Known

    • On WSL the music slows and comes back. This machine's WSL slows CLOCK_MONOTONIC by 1–7% against real time (measured against the Windows performance counter; what slows it is not established), and audio is consumed by that clock. It remained after WSL was restarted. It is taken to be outside the engine, and checking on real hardware (an actual Linux machine) is the follow-up. Arrows and music agree with each other.
    • Fps and tick rates measured on WSL are measured with CLOCK_MONOTONIC and read 1–9% high while that clock is being slowed. Read the final report's host clock … line first.
    • pumpitp3 and pumpipx3 on Win32 die intermittently with Fatal error: unable to find entry point in DLL. Their CAT702 transactions are correct and the cause is not established.
    • Six profiles (pumpit2, pumpit3, pumpite, pumpitp3a, pumpipx3a, pumpipx3b) have no disc image (CHD) and cannot be run.
    • INT 21h AH=08h (getch) is absent. It is reached only behind a game's error screen.
    • Win32 cannot see the guest's iret, so the nesting and turn rules are off there and input frames are retired by age (reads at an old time can remain for about 0.27 s). Tasks 752–754 were checked on Win32 by build and probes only.
    • The teardown segfault after the budget remains. In pumpit8's song selection the guest stalls half a second per preview load. Some frames in play present in 23–43 ms (the host's display side, not investigated).
    • The scripted runs of Tasks 748–752 ran under Task 753's defect (the guest received more input than the script held).
  26. 2026-09-27

    v0.0.193

    Changes since v0.0.192 (Tasks 745–750). On Linux x64 the arrows and the BGA no longer hitch with vsync on. When the driver refuses vsync the engine paces the swaps at the refresh rate, the MP3 playback clock no longer follows the device's pull jitter, and the timer ISR runs while a swap is waited out. A run started with arguments applies cfg/repiu.ini too. Confirmed by the user playing on WSLg (wayland, real vsync).

    Full release notes

    Changes since v0.0.192 (Tasks 745–750). On Linux x64 the arrows and the BGA no longer hitch with vsync on. When the driver refuses vsync the engine paces the swaps at the refresh rate, the MP3 playback clock no longer follows the device's pull jitter, and the timer ISR runs while a swap is waited out. A run started with arguments applies cfg/repiu.ini too. Confirmed by the user playing on WSLg (wayland, real vsync).

    In numbers (pumpitea inside a song, 62 s plays with in-song input, WSL Linux x64 Release)

    v0.0.192 v0.0.193
    fps at swap_interval = 1 (llvmpipe refuses vsync) 58–98 59.9–60.1
    MP3 frame-sync toggle interval (should be 26.1 ms) 16–35 ms 24–27 ms (98%)
    Guest's delay in seeing a toggle, under a 60 Hz wait uniform 0–16 ms 0–4 ms
    Guest's song position step, under a 60 Hz wait 16 ms / 33 ms bimodal centred on 25 ms (21–31)
    Ticks dropped in a paced 14 s attract 936 62
    Frames over 25 ms (inside the song) — 0

    Free-running with vsync off already observed within 0–7 ms and still does; what changed is that a run with vsync on now matches it.

    Fixed

    Swap pacing when the driver refuses vsync (Task 745) — WSLg's llvmpipe GL refuses SDL_GL_SetSwapInterval(1) ("That operation is not supported"). When refused, the engine waits after grBufferSwap until a deadline derived from the display's refresh rate. Deadlines accumulate, so the average rate is exact, and a deadline more than a period behind is resynchronised. The final report's two Glide swap pacing … lines give the state, the period, the refusal and the jitter (late, resyncs, oversleep).

    Argument runs read cfg/repiu.ini (Task 746) — a run that skips the launcher, such as repiu pumpitea, reads and applies the same file first; an environment variable of the same meaning still wins. The log's Launcher settings read from … line says where each value came from.

    Tick injection after an HLE reentry (Task 747) — Linux x64 handled the ISR's iret, out and cli without attempting an injection afterwards, so after a long gate the owed ticks drained two a frame and the backlog (64) overflowed. The legacy chain's injection call is now there too, draining right after iret without nesting.

    The MP3 playback clock free-runs (Task 749) — the previous clock restarted its line at the previous step every time the device took 768 frames, so a few ms of pull jitter became 12 ms jumps and stalls. It now advances from its own previous value at the PCM rate and slews, with a four second time constant (±2%), to the pulled count less half a device buffer. It still never passes the pulled count and never runs backwards.

    Timer ticks while a swap is waited out (Task 750, Linux x64) — no guest code ran inside the grBufferSwap gate, so the timer ISR stopped too. The gate now hands the present to the host thread and waits, injecting an owed tick as "an interrupt that arrived just before the call that reached the gate": the ISR's iret runs the call again and returns to the gate, which returns to its caller only when the present is done, as the real machine takes IRQ0 during its vblank wait. The code that arms ticks moved from the host poll loop into a call the guest's wait loop makes too.

    New switches and diagnostics (default behaviour when unset)

    Variable / output Meaning
    Glide swap pacing active/refresh-hz/period-us/paced-swaps/slept-ms/refusal (final report) whether pacing is active, and the refusal
    Glide swap pacing late-swaps/late-max-us/resyncs/oversleep>1ms/oversleep-max-us (final report) the pacer's jitter
    Glide swap wait ticks swaps/injections (final report) swaps waited out with ticks delivered, and the injections
    REPIU_GLIDE_SWAP_WAIT_TICKS=0 turns the ticks during the wait off (a Linux x64 feature, on by default)
    REPIU_GLIDE_SWAP_WAIT_LOG=1 names the first few waits that could not deliver a tick
    REPIU_PIU10_MP3_POSITION_TRACE=1 [repiu-mp3-pos] toggle|seen … per toggle and per guest read that first sees the new value
    REPIU_GLIDE_LONG_FRAME_LOG=1 (a larger value is a µs threshold) splits a frame over one and a half periods into sleep, guest and present
    REPIU_INPUT_SCRIPT=<file> a scripted keyboard pushing <ms> <key name> [hold ms] lines as SDL key events; see scripts/input_scripts/pumpitea_play.txt and pumpitea_play_steps.txt
    REPIU_DOS_ASSET_TRACE=all lifts the asset trace's caps (200 opens, 120 reads, 120 seeks)

    Known

    • The wayland driver takes real vsync. WSLg keeps its socket in /mnt/wslg/runtime-dir, so run with XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir SDL_VIDEO_DRIVER=wayland. The earlier note that the wayland driver opens no window was this path.
    • Starting under wayland, 2 of 6 runs had the tick injection climb to 18,000 a second from 4 s and overflow the guest stack at 13 s. It has not appeared in recent runs and is not investigated.
    • Ticks during the wait are Linux x64 only. Forced on under Win32 the run stalls after the first injection, without a fault, so it is not offered there; Win32's vsync waits in the same gate and is expected to have the same observation delay.
    • Win32 pumpitea drops 380 ticks in 15 s, vsync on or off. Unrelated to this release and not investigated.
    • A play without input empties its gauge about 14 s into the song and enters the fail sequence, whose BGA loads (HEYMAN.DAT and others) make 80–340 ms frames. Use an input script when measuring, so they are not mistaken for in-song hitches.
    • A game whose call site is not call rel32 gets no ticks during the wait and waits as before.
  27. 2026-09-27

    v0.0.192

    Changes since v0.0.191 (Tasks 741–744). On Linux x64 one trap costs 41% less and pumpitea's breakpoint exceptions fell from about 38,000 to about 30,000 a second. A census of which guest sites trap is always on, and a latent defect where a ret into an untranslated block died as an anonymous SIGTRAP is fixed. This release changes the Linux x64 engine internals only; game behaviour is unchanged (the Windows build either has no affected code or copies the original bytes as before).

    Full release notes

    Changes since v0.0.191 (Tasks 741–744). On Linux x64 one trap costs 41% less and pumpitea's breakpoint exceptions fell from about 38,000 to about 30,000 a second. A census of which guest sites trap is always on, and a latent defect where a ret into an untranslated block died as an anonymous SIGTRAP is fixed. This release changes the Linux x64 engine internals only; game behaviour is unchanged (the Windows build either has no affected code or copies the original bytes as before).

    In numbers (pumpitea attract, 27–30 s, WSL Linux x64 Release)

    v0.0.191 v0.0.192
    VEH cost of one breakpoint exception 15.2k cycles 8.9k cycles
    VEH share of wall 14.3% 6.7–7.3%
    Breakpoint exceptions 1.02–1.22 M 0.88–0.97 M
    memcpy helper push es, mov eax,ds 53,914 × 2 0
    jmp cs:[table] (0x010F659E) 27,594–36,659 0
    pumpit2a 25 s breakpoints 149,911 108,557

    Frame rate is unchanged: the remaining traps sit inside the game's own wait loops (Watcom delay()'s INT 21h AH=2Ch calibration spin and polling, lseek), so a cheaper trap only spins more inside the same wait. The gain is CPU occupancy.

    Fixed

    "The ISR input scan's trap load" was a misdiagnosis (Task 741): the new breakpoint site census (always on; breakpoint site #N host/guest/cache/count/exits, the 32 busiest, in the final report) named the real top sites: three INT 21h AH=2Ch calls of Watcom delay() at 61% (a 450,000-call calibration spin in the first two seconds plus 6,600 a second), lseek at 10%, a memcpy helper's segment-write pair at 10%, the ISR about 1%. The count is the game's own wait loop, so the cost of one trap was cut: every HLE reentry decoded the same guest address three ways with Zydis (segment write, span safety up to 64 instructions, long-mode identity); a per-address memo (1,024 slots, invalidated by a generation that moves on every guest code write, an eight-byte fingerprint and the code mode) replaces that. REPIU_AOT_REENTRY_MEMO=0 turns it off. The per-call getenv and the localtime conversion inside the DOS service are cached too.

    Segment reads and pushes without a trap (Task 742): mov r16/r32,sreg and a 32-bit push es/ds/fs/gs are emitted as long-mode slots. The value is the guest-visible selector the engine keeps in the shadow, so the slots read the shadow with no guard (new kind kGuardedSegmentPush; i386 images copy the bytes). The earlier code emitted the i386 read slot into long-mode images, pushing RFLAGS onto the host stack and comparing the host DS of 0 RIP-relatively, so it always fell back to INT3; it is no longer emitted in long mode.

    Returning to an untranslated target (Task 743): when a ret came back to a block whose dynamic translation had been rejected, the return thunk's resolver allowed the legacy resume only for a byte-identical first instruction in long mode and died at the thunk's int3 for anything else (a pop edx, commonly). It now uses the indirect call's condition (readable guest code). The dead ends that remain (no translation and no long-mode identity) end with [repiu-x64-untranslatable] stage= eip= bytes= …, and [repiu-fault] unhandled … carries the last VEH exit site (last_exit_site=, last_exit_eip=).

    Jump tables normalized by xor edx,edx; mov dl,bl (Task 744): the planner's low-byte guard (cmp bl,imm; ja) knew only and r32,0xFF as its normalization, so this Watcom switch form trapped every time through its CS: prefix. The guard now survives xor/sub r32,r32 and completes on mov r8,r8' (or movzx r32,r8).

    New diagnostics (inert when unset)

    Variable / output What it does
    breakpoint site #N host/guest/cache/count/exits (final report, always) the 32 host/guest addresses raising the most breakpoint exceptions, with the VEH exit that ended them
    AOT reentry memo hit/miss segment/span/long-mode (final report, always) the reentry memo's hit rate
    REPIU_AOT_REENTRY_MEMO=0 turns the reentry memo off, decoding every time (for A/B)
    REPIU_AOT_DYNAMIC_REJECT=<guest address> or read makes the worker reject those dynamic images, reproducing a translation failure
    [repiu-x64-untranslatable] … address and bytes where execution stops at an untranslated, non-identical instruction (up to eight)
    [repiu-fault] unhandled … last_exit_site= last_exit_eip= the refusing VEH exit site (VehExitSite code) and EIP
    [repiu-aot-unsafe] line=N which check ended a dynamic image append as an unsafe failure

    Known

    • An instruction with no translation and no long-mode identity (an absolute cmp, say) still cannot run on Linux x64 (no interpreter); unreachable while dynamic translation works, and now named.
    • The remaining Linux x64 traps are all the game's own: delay() AH=2Ch, lseek, the ISR's batched in, EOI and iret. The tick rate wandering 234–244 a second during a song and the 40–160 ms tick gaps during scene loads were not investigated.
    • REPIU_GUEST_POSITION_CENSUS captures nothing on Linux. REPIU_DOS_INT_TRACE's stderr writes slow the wait loops and distort call counts; do not use it to count traps.
    • v0.0.191's known items (the Windows legacy backend's pumpitea crash, the VS 2026 v145 dos4gw_hello crash, the CD-preview silence report) remain.
    • Linux figures were measured under WSLg.

    Verified by the whole Linux x64 core probe (new cases: the reentry memo, three long-mode segment read/push slots, three jump-table guards), the whole Windows x86 core probe, an automated WSL reproduction of pumpitea from coin to song select to play (60 s, no faults), a pumpit2a 25 s regression, and 4 of 4 runs of the fixed build without a fault.

  28. 2026-09-26

    v0.0.191

    Changes since v0.0.190 (Tasks 735–740). pumpitea now starts and plays on both Linux x64 and Windows. On Linux x64 the arrows no longer stall and jump during play and the music no longer carries noise (confirmed by the user). The window title shows platform, architecture and build configuration, and the Linux build scripts work as they are, with no --target.

    Full release notes

    Changes since v0.0.190 (Tasks 735–740). pumpitea now starts and plays on both Linux x64 and Windows. On Linux x64 the arrows no longer stall and jump during play and the music no longer carries noise (confirmed by the user). The window title shows platform, architecture and build configuration, and the Linux build scripts work as they are, with no --target.

    Where pumpitea stands

    v0.0.190 v0.0.191
    Linux x64 start rejected by the AOT image's decode check starts → title → song select → play
    Linux x64 at 23 s segfault from nested timer ISRs runs past 90 s
    Windows start 0xC0000005 at 8 s reaches the title at 90 s
    Arrows during play stall and jump smooth
    Music during play occasional noise none

    Fixed

    pumpitea did not start on Linux x64 (Task 735): the AOT image's decode check rejected one entry, guest 82 68 10 01 (sub byte [eax+0x10],1). Opcode 82 is the alias of the 80 group-1 operations and raises #UD in long mode; the long-mode path now judges and emits 82 as its 80 spelling. The build-failure message now names the stage, the first failing entry and the emitted bytes.

    pumpitea's nested timer ISRs (Task 736): with the PIT programmed at 51.9 kHz, ticks were injected again at the back-edge after the timer ISR's cli (IF does not reflect cli in user mode), nesting the ISR until the stack overflowed. The PIC's IRQ0 in-service bit is modelled: no injection before the EOI (0x20/0x60 to port 0x20), and an owed tick is delivered at the guest's sti after it (never two sti deliveries in a row, TF cleared from the injected frame). REPIU_PIC_TIMER_IN_SERVICE=0 restores the earlier behaviour.

    Windows pumpitea start-up crash (Task 737): three layers. (1) The i386 indirect CALL fallback kept the push as the x64 rule says (x64 continues in the callee), pushing the return address twice; on i386 the legacy path re-executes the CALL, so the push is undone, and the miss tail's CALL also gets +8. (2) i386 kCopy copied the three stub bytes of 16-bit code objects verbatim, rejecting every dynamic image; 16-bit records are now an INT3 HLE boundary. (3) The delay-loop batcher rejected the ISR input scan's cmp; jge exit; jmp back shape; the inverted form is accepted (19,001 batches in 90 s on Windows).

    Arrow stutter and noise during Linux x64 play (Task 740): both the MP3 frame-sync toggles and the decoder's refills followed the steps in which the SDL device pulls PCM (its buffer size). Several frame boundaries inside one step toggled in the same call (a guest polling at 240 Hz reads an even number as no change), and the decoder refilled a whole step at once, so the guest's demand and frame counter moved in the same steps. A playback clock interpolated at the PCM rate between device steps now drives both the toggles and the decode gate. With a forced 2048-frame device buffer, collapsed toggles went from four to six a second to none, and on the user's host both the stutter and the noise are gone.

    Changed

    • Window title (Task 738): rePIU v0.0.191 (Win/x86 Debug) - Build … - Glide 2 OpenGL [dynamic] - FPS : …, with the platform/architecture and build configuration such as Linux/x64 Release. repiu::platform::BuildIdentityLabel(), CMake REPIU_BUILD_CONFIG.
    • Linux build scripts (Task 739): scripts/build_linux_x64.sh and build_linux_i386.sh build everything with no --target. repiu_exe links GL PUBLIC on Linux, which removes the link errors of repiu_instruction_census and the i386 core probe. The i386 script gained --build-dir and the configuration-mismatch guard. --headless only suppresses the configure error when the X11/Wayland development packages are missing; the desktop drivers are built regardless.
    • The final report gained PIU10 MP3 received/dropped/decoded/starvation/toggles/multi/pcm-empty/ pcm-low-water-bytes/device-buffer-frames and timer IRQ0 in-service … lines.

    New diagnostics (inert when unset)

    Variable / tool What it does
    REPIU_PIU10_MP3_CENSUS_MS=<ms> one MP3 pipeline census line per interval (received, decoded, queue, clock lag, demand, toggles, collapsed toggles, starvation, empty queue, ticks)
    REPIU_PIC_TIMER_IN_SERVICE=0 turns the IRQ0 in-service model off, restoring the earlier injection
    SDL_AUDIO_DEVICE_SAMPLE_FRAMES=<frames> (SDL hint) forces the device buffer, to reproduce a host with a larger one
    scripts/task740_mp3_census_capture.sh one run with the census above and a per-second summary

    Known

    • The Windows legacy backend's pumpitea crash (0x040FD010) was not re-checked, nor was Windows pumpitea's progress to song select and play.
    • pumpitea's ISR input scan runs 200 in instructions per tick through traps (4.05 million in 88 s on Linux). The results are right; the cost is high.
    • The tick rate wanders between 234 and 244 a second during a song, and there are 40–160 ms tick gaps during scene loads. Not investigated.
    • From v0.0.190's list, the VS 2026 (v145) dos4gw_hello crash, the Linux x64 loading stalls and the CD-preview silence report remain as they were.
    • Linux figures were measured under WSLg.

    Verified by Linux x64 core probe 33/33, Windows x86 core probe 31/31, Linux i386 core probe 31/31, an automated WSL reproduction of pumpitea from coin to song select to play (60–90 s, no faults), a 90-second Windows pumpitea run, pumpit2a regression runs, and the user's own play.

  29. 2026-09-26

    v0.0.190

    Changes since v0.0.189 (Tasks 718–734). On Linux x64, pumpit2a now gets past the song-select screen, chooses a song and starts playing it. The render-phase game clock runs by default, and Glide calls dispatch directly instead of trapping. On Windows, an intermittent shutdown crash in pumpit1 legacy runs is fixed.

    Full release notes

    Changes since v0.0.189 (Tasks 718–734). On Linux x64, pumpit2a now gets past the song-select screen, chooses a song and starts playing it. The render-phase game clock runs by default, and Glide calls dispatch directly instead of trapping. On Windows, an intermittent shutdown crash in pumpit1 legacy runs is fixed.

    Where Linux x64 stands

    v0.0.189 v0.0.190
    Game clock in the render phase runs only with the opt-in runs by default
    Glide calls all through a ud2 trap (2.42 million in 180 s) direct dispatch (no traps)
    Swaps in a 180-second run 10,344 15,832
    Song-select screen freezes passes → song choice → step data loaded → song plays

    Fixed

    pumpit2a song-select freeze (Task 734): the Linux x64 return thunk did not restore EFLAGS after a guest ret or indirect call/jump, so every ret through it came back with CF=0. The Watcom C runtime's sin/cos helper reports its result in CF, so the game retried fsin forever. The thunk now restores the saved flags right before its jmp. The defect may have affected any guest code that reads flags after a ret.

    Linux x64 render-phase clock (Task 718): safe-point tick injection is on by default. Turn it off with REPIU_LINUX_X64_SAFE_POINT_INJECTION=0; the variable used to be tested for presence, so =0 meant on.

    Linux x64 Glide direct dispatch (Task 720): the direct-dispatch thunk existed only as i386 assembly, so every Glide call on Linux x64 went through a ud2 signal. RepiuLinuxX64GlideGateThunk now calls the export directly, saving and restoring the guest's x87 state. The 1–3.6 s stalls while loading are unchanged, which also established that Glide traps are not their cause.

    Shutdown recovery

    • The x64 shutdown recovery decision reads the full RIP (Task 730). It used to read only the low 32 bits, so a host-library address whose low half fell in the guest range could be "recovered" from a host frame.
    • Windows: pumpit1 legacy runs crashed with 0xC0000005 at shutdown about half the time; fixed (Task 733). The cause was an exception the kernel was already delivering when SetThreadContext ran, now handled by a redirect guard installed only for the shutdown window. After the fix, 40 consecutive runs ended cleanly.

    Changed — logs and source layout

    • The shared loader moved from src/host/win32/main.cpp to src/host/loader/main.cpp (Task 731, byte-identical).
    • Loader log lines no longer start with Win32 (Task 732): [loader] Win32 minimal execution timed out is now [loader] minimal execution timed out. The 230 uses in the repository's scripts and guides were updated with it. External log parsers need their patterns updated.
    • scripts/test_all.ps1's stale pumpit1 assertions were brought up to current behavior (Task 733).

    Performance experiment (off by default)

    The framebuffer readback each Glide LFB write lock did can be replaced by one copy per color buffer, exchanged at the swap like a 3dfx page flip (Tasks 728–729). In a 30-second run seeds fell from 304 to 2 and readback cost from about 6.5 billion cycles to 46.9 million, but that stretch's fps barely moved (27.7 → 28.0), so it stays off. Turn it on with REPIU_GLIDE_LFB_STAGING_REUSE.

    New diagnostics (inert when unset)

    Variable / tool What it does
    REPIU_GLIDE_PIXEL_DIAG_INTERVAL_MS samples the back buffer by time (4×4 brightness grid), to line the two hosts up on the same scenes
    REPIU_LINUX_X64_NATIVE_SAMPLE, REPIU_LINUX_X64_NATIVE_SAMPLE_TRACE Linux x64 guest-thread position samples, and a per-sample RIP / guest EIP log
    REPIU_GLIDE_LFB_TIME_PROFILE, REPIU_GLIDE_LFB_WRITE_CENSUS LFB lock cost by phase, and write extents
    REPIU_LINUX_X64_LFB_STORE_CENSUS, ..._SOURCE_CENSUS the guest instructions that store to the LFB, and where they come from
    REPIU_GLIDE_LFB_STAGING_SHADOW_CENSUS, REPIU_GLIDE_LFB_LOCK_INTERVAL_CENSUS locks whose readback could be skipped, and what happens between locks
    REPIU_LINUX_X64_MEMORY_WRITE_TRACE Linux x64 guest memory write tracing
    REPIU_SHUTDOWN_RECOVERY_TRACE Windows: logs the exceptions that arrive right after the shutdown redirect
    [repiu-shutdown] line host_ip=, decision=, aliased=, and the Windows redirect guard's intervention counts
    scripts/task734_songselect_stall_capture.sh records an interactive state such as the song-select freeze with frame rate, position samples and a cache map

    Known

    • build_win32_x86.ps1 picks the newest installed Visual Studio, and a binary built with VS 2026 (toolset v145) crashes at the start of dos4gw_hello (3 of 3); the VS 2022 (v143) binary is fine. test_all.ps1 therefore could not be run end to end.
    • A user report that the CD preview music is inaudible did not reproduce on the development machine, where the WSLg output carries sound during CD play.
    • The 1–3.6 s stalls while loading on Linux x64 remain (not caused by Glide).
    • The repiu_instruction_census tool does not link on Linux x64 (unresolved GL symbols).
    • Linux figures were measured under WSLg.

    Verified by Linux x64 core probe 31/31 (including the new guest_return_preserves_flags), Windows x86 core probe 29/29, an automated WSL reproduction of pumpit2a from song select to song playback, and 40 consecutive clean pumpit1 legacy shutdowns on Windows.

  30. 2026-09-18

    v0.0.189

    Changes since v0.0.188. On Linux x64, pumpit2a now draws textured frames, and with an opt-in setting the game clock runs and the scenes advance. Windows behaviour is unchanged apart from two shared fixes listed separately below.

    Full release notes

    Changes since v0.0.188. On Linux x64, pumpit2a now draws textured frames, and with an opt-in setting the game clock runs and the scenes advance. Windows behaviour is unchanged apart from two shared fixes listed separately below.

    Where Linux x64 stands

    v0.0.188 v0.0.189
    Glide init → render loop reached reached
    Texture upload skipped the same 96 gates as Win32
    Floating point all NaN after a signal return correct
    Game clock in the render phase stops default: stops / opt-in: runs
    90-second run, opt-in on — no faults, 3,913 frames, scenes advance

    The setting that keeps the clock running is REPIU_LINUX_X64_SAFE_POINT_INJECTION. It is on whenever the variable exists, whatever its value (=0 turns it on). The next release is expected to make it the default.

    Fixed

    Re-entry and interrupts (Tasks 702–705)

    • Execution re-enters AOT code after Glide gates and timer-chain boundaries. Before, a PUSH or CALL after the boundary ran on the host stack and vanished from the guest's.
    • INT 8 injection frames carry the guest selector, and the ISR's IRETD is handled by HLE.
    • A thread interrupt that changed nothing no longer overwrites host R15 (guest ESP) with a 32-bit value.

    CPU state and emission (Tasks 707, 710, 713, 716)

    • x87 tag word: every signal return marked all FPU registers non-empty, so everything after it was NaN.
    • High-byte lowering: the first byte of the lowering for mov dh,[esp] and the like overwrote DL instead of saving it, corrupting EDX; glEnable(GL_TEXTURE_2D) then failed and textures were skipped altogether.
    • Timer safe points (1,067 sites) are planted on the long-mode path.
    • CS: override data accesses (mov al, cs:[edx+table]) are emitted as copies; before, they were HLE boundaries nothing on x64 handled.

    Segment folds (Tasks 711, 712, 717)

    • An override naming the loader's initial stack selector (0x0034) or data selector (0x0024) now folds base 0 in any segment register, because the guest's implicit accesses are flat.
      • SS: Watcom lseek wrote outside the stack and PIU.BIN was read 1.1 million times.
      • ES: sprintf("%d") copied texture pixels as a number and overwrote the stack and heap.

    Glide LFB (Task 708): the LFB buffer address was a host pointer truncated to 32 bits; the buffer now sits in the shared below-4 GiB reservation (ReserveLowAddressMemory) where the guest can address it.

    Shared fixes that also apply to Windows

    • SS fold (Task 712): Windows now reads PIU.BIN correctly for the first time.
    • Data selector fold (Task 717): clock, safe-point and segment figures of a 30-second run stayed within noise.
    • The Windows x86 build no longer compiles the Linux-only telemetry source (Task 706).

    New diagnostics (inert when unset)

    Variable / output What it does
    REPIU_LIVE_GUEST_PEEK=<offset>[,n] logs up to 16 guest dwords each second
    REPIU_LIVE_GUEST_SCAN=<offset>,<bytes>,<dword> counts a dword in a range and lists its runs
    REPIU_LINUX_X64_DATA_WATCH=<address> Linux hardware write watchpoint, logged and resumed
    [repiu-fault-stack] 64 guest stack dwords after an unhandled Linux fault
    Final execution report printed on the refused-recovery exit path as well (Task 709)

    Known

    • With injection off (the default) the game clock stops once rendering starts, around 17 s.
    • The two hosts return different values when the guest reads a segment register (Windows 0x2B, Linux 0x0024). The folds treat them alike; the values still differ.
    • Linux figures were measured under WSLg, not on a native ext4 Ubuntu install.

    Verified by Linux x64 core probe 30/30, Windows x86 core probe 28/28, 30-second runs compared on both hosts, and a 90-second Linux run.

  31. 2026-08-28

    v0.0.171

    Changes since v0.0.170. Source layout only; behaviour is unchanged.

    Full release notes

    Changes since v0.0.170. Source layout only; behaviour is unchanged.

    The execution engine moved to src/engine/

    AGENTS.md puts platform-specific code under src/platform/{win32,linux,web}/. The platform-neutral execution engine was living under src/platform/win32/ and Linux was compiling it. Counted, 102 of the 119 files there had no Win32 dependency at all.

    Before After
    src/platform/win32/ 119 6 (the backends with a Linux counterpart)
    src/engine/ — 113
    include/repiu/platform/win32/ 46 1
    include/repiu/engine/ — 46

    Header paths and the namespace change — a compatibility break

    include "repiu/platform/win32/foo.h"   ->  include "repiu/engine/foo.h"
    repiu::platform::win32::Bar            ->  repiu::engine::Bar
    

    Anything including those headers needs updating. The backends (repiu::platform) are unchanged.

    The dependency now runs one way

    execution_trampoline.cpp used to reach the backend with #include "win32_thread_api.h", reading a backend's private source tree. That header is public now, so the engine sees a backend only through include/repiu/platform/win32/.

    Known

    • No behaviour change. Confirmed by Linux and Windows builds, the probe, and the sample regression.
    • The wasm configuration is unverified -- Emscripten is not installed in this environment.
    • File and symbol names still carrying Win32 are untouched (aot_code_cache_win32.cpp has no Win32 code in it), as is src/host/win32/main.cpp. Both are separate tasks.
  32. 2026-08-28

    v0.0.170

    Changes since v0.0.169. The Linux performance axis is narrowed to one trap per cache reentry. Nothing is fixed yet.

    Full release notes

    Changes since v0.0.169. The Linux performance axis is narrowed to one trap per cache reentry. Nothing is fixed yet.

    The chain, narrowed

    Windows Linux
    Reentries a frame 160.6 ~154 (0.90x)
    Breakpoints per reentry 0.0432 1.010

    Linux reenters less often. What differs is whether leaving costs a trap, and that 23x matches the 24.2x breakpoint excess measured earlier. The path Windows uses to avoid it is Glide direct dispatch, at 95.1% of its reentries, and 87% of Linux's reentries take it too -- yet a success still costs a trap.

    Four lines of live instrumentation

    Enabled with REPIU_EXECUTION_TIME_PROFILE=1 and REPIU_LIVE_PROFILE_INTERVAL_MS.

    [repiu-live-profile]  veh / glide / port-io / dos / unaccounted shares
    [repiu-live-veh]      deliveries, cycles per delivery, kernel gap, three classes
    [repiu-live-aot]      cache entry / boundary / reentry, five boundary reasons
    [repiu-live-gdd]      direct dispatch patched / resolved / relinked / entry / success
    

    Linux never reaches the loader summary, so without these nothing here can be read.

    Known

    • No performance has been fixed.
    • Next question: why that INT3 stops being hit on Windows.
    • Four inferences were wrong while making this version. All took a counter's meaning from its name instead of from where it increments; the frontier carries the table.
  33. 2026-08-28

    v0.0.169

    Changes since v0.0.168. Stages 1 and 2 of the web (WebAssembly) port land, and Stages 3 through 5 are recorded as on hold.

    Full release notes

    Changes since v0.0.168. Stages 1 and 2 of the web (WebAssembly) port land, and Stages 3 through 5 are recorded as on hold.

    The platform-neutral core builds as wasm32

    Not one line of core source was changed.

    Item Value
    repiu_exe objects (wasm32) 64
    Core sources modified 0
    Undefined symbols of the engine's own none
    repiu_core_probe (wasm32) 9 of 9 passed, with the 6 excluded ones named
    Windows regression 15 of 15, unchanged

    The dependencies (SDL3, libchdr, miniz, Zydis) build under Emscripten as they are.

    The interpreter's size is measured

    pumpipx3's PIU.EXE, by recursive descent from the entry point.

    Item Value
    Instructions 74,333 (17,367 blocks)
    Distinct mnemonics 120
    Distinct (mnemonic, operand signature) 320
    Top 17 mnemonics, cumulative 87.89%

    An interpreter is sized by 320, not 120 -- one MOV is 19 forms. But with the top 17 at 87.89% it can be built incrementally.

    It does not run in a browser yet

    Both present backends (legacy and dynamic) stand on native x86, which has no wasm form. Stage 3, a platform-neutral interpreter, is where that goes, and it is on hold -- the Linux performance axis comes first.

    The resume conditions are in the web port frontier. In short, two things must be settled first: the x87 representation (80-bit or not) and assuming Worker execution. Changing either afterwards means writing the interpreter twice.

  34. 2026-08-27

    v0.0.168

    Changes since v0.0.167, which corrects one of that release's conclusions.

    Full release notes

    Changes since v0.0.167, which corrects one of that release's conclusions.

    Signal delivery was not the culprit

    v0.0.167 recorded 68% of Linux's gap as "signal delivery". Splitting that 68% into its two factors shows the name was wrong.

    Windows Linux Factor
    Deliveries per frame 18.8 256.3 13.6x
    Cycles per delivery (handler body) 104,002 341,557 3.3x
    Kernel round-trip floor 21,756 9,632 0.44x
    Kernel round trip, single-step mean 28,185 16,466 0.58x

    Linux's signal delivery is more than twice as cheap as Windows' exception dispatch. What costs is not delivery but taking 13.6x more boundaries a frame and a handler body 3.3x more expensive each time.

    One more report line

    [repiu-live-veh] #8 veh_count=617579 per_frame=263 cycles_per_veh=344198 gap_min=9472 gap_ss_mean=16427 gap_ss_count=22082
    

    Enabled by REPIU_EXECUTION_TIME_PROFILE=1 with REPIU_LIVE_PROFILE_INTERVAL_MS. Nothing new is counted -- these values already accumulated and simply were not printed.

    Known

    • No performance has been fixed yet. This narrows the cause.
    • What the excess boundaries are is still unknown. Not single steps (3.6% of Linux's deliveries against Windows' 24.4%).
    • The per-frame and per-second normalisations say different things. Per second Linux takes 40% fewer faults -- because frames are 24x rarer.
  35. 2026-08-27

    v0.0.167

    Changes since v0.0.166. We now know why Linux is slow.

    Full release notes

    Changes since v0.0.166. We now know why Linux is slow.

    The answer: signal delivery

    Both hosts are the same machine, so TSC cycles compare directly. Per frame, in millions of cycles:

    Bucket Windows Linux Factor Share of the gap
    The fault handler 2.065 88.072 42.6x 68.3%
    Glide 1.618 26.480 16.4x 19.7%
    Unaccounted 2.159 17.214 8.0x 12.0%
    DOS services 0.006 2.438 433.9x 1.9%

    68% of Linux's gap is the fault handler, and on Linux that is signal delivery. It reproduces over three runs, against a Windows control of 35.35%.

    The Glide gate was ruled out first: putting the host round trips and the gate crossings back moved the frame rate not at all, while the same knob costs Windows 27.1%.

    A run reports on itself while it is going

    REPIU_LIVE_PROFILE_INTERVAL_MS=1000 gives one line at the interval you name.

    [repiu-live-profile] #5 frames=1706 window_frames=275 cycles_per_frame=133327684 veh=59.82% glide=22.86% dos=2.90% unaccounted=17.30%
    

    Use it with REPIU_EXECUTION_TIME_PROFILE=1. It was needed because a Linux run that reaches rendering never reaches the loader summary -- its guest thread does not stop, so the shutdown arm ends at _Exit.

    It immediately showed what a cumulative average cannot: Linux's cost per frame triples during a run (83M to 272M cycles). It is not uniformly slow; it gets worse as the scene proceeds.

    Known

    • No performance has been fixed yet. This goes as far as finding the axis.
    • Whether signal delivery is a count problem or a price problem has not been separated. That is the next task.
    • The probe suite has never been validated in Release (recorded in v0.0.166).
  36. 2026-08-27

    v0.0.165

    Changes since v0.0.164. Stage 3 of the Linux port: the game runs on Linux and puts a picture on the screen. v0.0.163 reached the launcher, which at the time answered a chosen ROM set by saying the execution engine was not on Linux yet.

    Full release notes

    Changes since v0.0.164. Stage 3 of the Linux port: the game runs on Linux and puts a picture on the screen. v0.0.163 reached the launcher, which at the time answered a chosen ROM set by saying the execution engine was not on Linux yet.

    The game runs on Linux

    • The default dynamic AOT backend works on Linux. Its 62 Win32 memory calls moved onto the 3b virtual-memory layer, and the one thing that did not exist yet -- an instruction-cache flush -- was built. Cache placement, dynamic execution, inline patching and page retirement all run.
    • The legacy backend single-steps every instruction and never finished the start-up asset decode. This was the item the screen was waiting on.
    • On WSLg pumpit1: first buffer swap at about 45.1 seconds, 69,263 of 307,200 non-black pixels at about 51.7 seconds, and swaps continuing after that. A person has confirmed an actual game screen appears.

    Shutdown works

    • A run asked to stop stops by itself. Before this, SDL turned SIGTERM into a quit event and pthread_join waited forever, so the run asked to stop hung instead.
    • The shutdown block's four Win32-only calls converged onto one InterruptHostThread. A successful recovery ends the run normally; a refused one goes down without waiting.
    • And it ends without a core dump. The refused arm had been removing a fault handler the guest thread still needed. That arm now does no cleanup and ends immediately. Across six 60-second-budget runs: two SIGTRAPs became zero.

    Known

    • It is very slow. The Linux frame rate has never been measured, and that is the next task.
    • What is drawn has never been compared frame by frame against Windows.
  37. 2026-08-24

    v0.0.163

    Changes since v0.0.162. Stage 2 of the Linux port: the launcher runs on Linux as a 32-bit binary. The execution engine is still Windows-only, so no game runs.

    Full release notes

    Changes since v0.0.162. Stage 2 of the Linux port: the launcher runs on Linux as a 32-bit binary. The execution engine is still Windows-only, so no game runs.

    The Linux launcher

    repiu_launcher builds as an i386 ELF and runs under WSLg, sharing the ROM set list and options screen with Windows as the same code. It reads the real roms/ directory and separates the sets that can run from the ones that cannot. Choosing one reports that the execution engine does not exist on Linux yet and exits, rather than pretending to start something and failing later with a missing file. --headless still builds the core and its probes without the desktop packages.

    What this stage established

    The 32-bit SDL3 desktop stack stands up: X11 video, OpenGL rendering, and ALSA audio. That covers not only the launcher but everything the game window and sound will need, and since the execution engine uses SDL for exactly those, the whole port depended on it.

    Two obstacles were resolved along the way. SDL's configure stopped on the XSCRNSAVER and XTEST extensions, which inhibit the screen saver and simulate input and which this project never uses; they were switched off rather than installed, because every extension left on is one more 32-bit package to find. And OpenGL had to be linked by plain name rather than through find_package(OpenGL)'s imported target, which can bind the 64-bit libGL.so and fail an -m32 link on architecture.

    Known limitations

    No game runs on Linux; the execution engine port is the next stage. The Linux launcher does not copy the Windows return loop or child process, both of which exist because a GPU driver claims the guest's low address space there — whether the same applies on Linux can only be judged once an engine exists. The Wayland backend was not detected, which does not matter because WSLg also provides an X server.

  38. 2026-08-24

    v0.0.162

    Changes since v0.0.161. This is the first stage of the Linux port: the platform-neutral core and its probes build as i386 on Linux and pass there. Windows behavior is unchanged, and no game runs on Linux yet.

    Full release notes

    Changes since v0.0.161. This is the first stage of the Linux port: the platform-neutral core and its probes build as i386 on Linux and pass there. Windows behavior is unchanged, and no game runs on Linux yet.

    Measurement decided the plan

    Of 192 sources, 53 are the platform-neutral core and 76 are the Win32 layer. The core includes no Win32 header, uses no MSVC-specific construct, assumes no 32-bit pointer, and compiles under GCC 13 with C++20 without a single error — it moved to Linux unmodified. The Win32 layer is a different size of problem: 270 CONTEXT uses, 47 VirtualProtect calls, and 28 fs:[...] assembly sites, which is the scale of the execution-engine port still ahead.

    Build structure

    The Win32 sources and Windows-only executables moved behind if(WIN32) inside the existing targets, so Windows output and linkage are untouched. A new repiu_core_probe gathers the nine platform-independent probes and builds on both systems; the two runs produce byte-identical 46-line output, which is the point — not that it passed, but that the same code behaves the same in both environments. scripts/build_linux_i386.sh checks the 32-bit toolchain before configuring and names the packages to install when it is missing. The architecture is i386 because the guest's 32-bit code runs natively in the host process, the same reason the Win32 host is a 32-bit process.

    Fixed

    nvram_path_probe used MSVC's _putenv; POSIX spells this setenv and unsetenv and removes a variable differently. Nothing of the kind appeared in the 53 core sources.

    Known limitations

    The execution engine, loader, and launcher remain Windows-only, so no game runs on Linux. SDL3 refuses to configure without X11 or Wayland development packages, so this stage — which opens no window — takes SDL's documented headless option; the stage that brings the launcher over will need the real desktop packages. repiu_chd_cd_probe and repiu_glide_issue_probe were left unguarded because they reference no Win32 symbol, but their Linux builds have not been checked.

  39. 2026-08-22

    v0.0.161

    Changes since v0.0.160. This release changes how rePIU is started: with no arguments it opens a launcher, and a finished game returns to the list.

    Full release notes

    Changes since v0.0.160. This release changes how rePIU is started: with no arguments it opens a launcher, and a finished game returns to the list.

    The launcher

    Running repiu.exe with no arguments lists every ROM set in the built-in catalog and starts the one selected; when that game ends the launcher comes back, so another title can be chosen without restarting rePIU, and Quit ends the session. ROM sets that cannot run are not hidden: they are dimmed and carry their reason — a missing roms\<id>.zip, a zip without the required PIU10 entries, a missing roms\<id>\, no CHD, or more than one CHD — so it is obvious why a disc is unavailable. Availability uses the same rules as the mount but extracts nothing, because verifying twenty-two discs would outlast anyone's patience and the real judgement already happens at launch.

    Arrows move, Enter starts, Escape quits, and unavailable rows are skipped by navigation. The cursor opens on the ROM set played last. Vertical sync and sound gain are edited there and stored in cfg\repiu.ini; an environment variable of the same meaning always wins, so measurement scripts and diagnostic procedures keep their precedence.

    Existing invocations are unchanged

    repiu.exe <rom set> and repiu.exe <path to PIU.EXE> behave exactly as before and end the process when the game ends — the return loop exists only for an argument-free standalone run. REPIU_LAUNCHER=0 skips the launcher for automation that runs the binary bare, and a host that cannot create a window or GL context falls back to the previous default target rather than failing to start.

    Why not a native GUI

    The launcher is drawn with Dear ImGui (MIT) inside the SDL3 window the host already creates. Every cross-platform native toolkit that would serve Linux — Qt, GTK, wxWidgets, FLTK — is LGPL-family and conflicts with the project's ban on copyleft dependencies, and avoiding them would mean building the same screen separately against Win32 and X11/Wayland. Two further reasons agree: cabinets have no keyboard or mouse, so the UI must eventually be operable inside the game window, and the same layer becomes the in-game OSD later. Availability and settings depend only on std::filesystem and the existing INI parser, so they are probe-verified, and the screen touches only SDL3 and OpenGL.

    Verification

    Debug and Release builds of repiu and repiu_aot_probe succeeded. repiu_aot_probe --launcher passed five checks: the six availability states, catalog coverage, settings round trip with malformed values, environment precedence in every combination, and child command-line quoting with a path containing spaces. For regression, repiu.exe pumpit8 exits cleanly with no launcher involvement. A user confirmed listing, launching, returning to the launcher, and keyboard operation.

    Known limitations

    Window size and fullscreen are not available yet, because window creation follows the guest's logical resolution and no consumer exists for an override. Driving the launcher from the cabinet panels is not possible yet either: panel input arrives over PIUIO ports while the guest runs, which is a different path, so keyboard, mouse, and gamepad are the current means. Diagnostic toggles such as the execution backend or the direct-return table are deliberately not exposed and remain environment variables.

  40. 2026-08-22

    v0.0.160

    Changes since v0.0.159. This release is about execution performance: a translated RET now resolves its own destination in most cases instead of crossing to the host, which raised frames on the same scene by 59.4%.

    Full release notes

    Changes since v0.0.159. This release is about execution performance: a translated RET now resolves its own destination in most cases instead of crossing to the host, which raised frames on the same scene by 59.4%.

    Measuring return cost before removing it

    Task 482 added REPIU_AOT_RETURN_STAGE_PROFILE, an opt-in instrument that splits the return handler into five mutually exclusive stages plus the residual of the same window. It immediately corrected the number the project had been steering by: the old kAotReturn bucket measured only the inside of HandleAotReturnTransfer and missed the adapter entirely, so return handling was about 27% of guest-run, not the 16.5-17.5% previously recorded — a factor of 1.39, reproduced across three runs. No stage dominated (resolve 31.7%, patch 27.5%, entry 25.1%, read 8.3%, continuation 7.3%), and 99.4% of policy observations were megamorphic bypasses.

    Megamorphic direct-return table, on by default

    Task 499 probes a shared memo table ahead of the return miss path; a hit returns with the original RET's own stack effect and no host transition, and a miss falls through into the existing path unchanged. A pumpit8 A/B of three 60-second pairs, with no instruments and only the toggle changing, measured 37,385 against 59,586 frames, +59.38% (623 against 993 fps) with non-overlapping groups, triangles per frame within 2.84%, and identical grDrawPoint totals in all six runs. Host return round trips fell from about fifty million per run to about 2,900, taking return handling from 25.63% of guest-run to 0.13%. REPIU_AOT_DIRECT_RETURN_TABLE=0 restores the exact cache bytes of a build without the feature, and REPIU_AOT_DIRECT_RETURN_TABLE_BITS sizes the table from 8 to 18 bits, defaulting to 15.

    Correctness adds no new invariant: a table hit is semantically identical to a four-entry PIC hit, insertion takes only resolutions the host validated, and invalidation clears the table exactly where PIC guards are reset, at a point where the guest thread is already stopped.

    What the Glide gate actually costs

    The same measurement work decomposed the Glide gate (23.7-24.7% of guest-run) across three runs. Its largest component is not rendering but crossings that never reach the host thread, 45-47% of the bucket and 10.7-11.6% of guest-run: 70.8-71.1% of gate entries are calls that setter elision discards, and elision removed only the host round trip, leaving about 1,790 cycles of crossing per call — a cost that had never been measured.

    Verification

    Debug and Release builds of all three binaries succeeded; the eight --return-stage-profile checks and five --direct-return-table checks passed, the latter including one that executes the emitted bytes and confirms a hit reproduces the original RET's EIP and ESP with eax and ecx preserved, for both C3 and C2 iw; the complete pumpit8 probe exited zero on both configurations; and table invalidation behaved correctly across 36 live page retirements with zero return fallbacks and zero index scans in every run.

    Known limitations

    The promotion rests on an unattended pumpit8 attract scene and deserves one gameplay-scene confirmation; REPIU_AOT_DIRECT_RETURN_TABLE=0 reverts instantly if a regression appears. A first pumpitea run crashed once on a null dereference after 4.2 seconds and did not reproduce; the mount silently skipping 89 external-extent files is the leading suspect and the investigation is recorded with its resumption steps in the pumpite CHD mount analysis. The next performance axis is Glide: moving the elision test ahead of the crossing, worth about 11% of guest-run, and host wake latency, about 6%.

  41. 2026-08-20

    v0.0.159

    Changes since v0.0.156. This release reworks how the host keyboard reaches the game: window key input is delivered to the BIOS INT 16h path, PIUIO panel and cabinet button mappings are configurable per ROM set, and the EEPROM is stored separately per ROM set.

    Full release notes

    Changes since v0.0.156. This release reworks how the host keyboard reaches the game: window key input is delivered to the BIOS INT 16h path, PIUIO panel and cabinet button mappings are configurable per ROM set, and the EEPROM is stored separately per ROM set.

    Window keyboard BIOS input

    • SDL key down/up events from the game window now reach the INT 16h BIOS path the original game polls every frame. The existing JAMMA input path keeps working alongside it.
    • The platform-neutral hle::BiosKeyboard owns the same 15-entry FIFO a real BIOS data area has, the legacy/enhanced AX pairs, and shift/lock state. The SDL host thread is the producer and the guest thread the consumer, so a short mutex critical section guards it.
    • The Win32 SDL adapter translates US 101-key scan codes, ASCII, Shift/Caps/Ctrl/Alt combinations, and the function, navigation, and numeric keypad groups. Key repeat is enqueued as a DOS typematic keystroke, and losing focus releases held modifiers while retaining lock toggles.
    • AH=00h/10h consume the FIFO head, AH=01h/11h peek without consuming and report presence through ZF, and AH=02h/12h return the real shift flags.

    Per-ROM-set configuration files

    • The [Input] section of cfg/<rom-set-id>.ini remaps the host keys for the PIUIO panels and cabinet buttons per ROM set. Names follow the physical panel positions: P1_UP_LEFT, P1_DOWN_RIGHT, TEST, SERVICE, CLEAR, COIN1, and the rest of the fourteen.
    • A missing file is created on first run as a commented-out template whose comments carry the values currently in effect, so changing a binding means deleting the leading ; on that line. An existing file is never overwritten.
    • Several keys can drive one input (P2_UP_LEFT = Keypad7, Home), and combinations such as Ctrl+F1 are supported. A binding with modifiers must match exactly, while one without ignores modifier state unless another binding contends over the same key. NumLock, CapsLock, and ScrollLock never affect any binding.
    • An empty value turns the input off: written as TEST =, no key drives TEST at all.
    • A ROM set's file layers over its parent's per key, so shared settings placed in cfg/pumpitup.ini apply to every title.
    • A typo warns rather than stopping the run, and the loader log names the file and line.
    • Usage is documented in docs/guides/romset-config-files.md.

    Input mapping consolidation

    • The same key mapping previously lived in three places in three vocabularies — ScanJammaPort8 and CaptureCurrentJammaPressedMask in Win32 virtual keys, the SDL event path in SDL keycodes — so changing one silently desynchronized the others. They now share one binding table.
    • SDL_Keycode is the canonical key type and SDL_Keymod the modifier mask. The SDL event path compares directly with no translation; only the polling path uses a virtual key table.
    • Port, bit, logical input, and log name were merged into one table. That corrected a mismatch where the log called bit 0x02 P1-UpRight while the code enumerator was kP1Down; the enumerator order is unchanged because the timeline uses it as a bit position.
    • The host key query count is unchanged under the default configuration. With no combination in use, modifiers are never read, so the port I/O cost Task 403 measured stands.

    Per-ROM-set NVRAM

    • The 93C46 EEPROM image is stored at nvram/<rom-set-id>/eeprom.dat the way MAME does. All 22 ROM sets previously shared one eeprom.dat in the working directory, so cabinet settings saved under one title were what another read back.
    • An existing eeprom.dat is copied into the ROM set's directory once, carrying accumulated cabinet settings forward. The original is not deleted.
    • The REPIU_EEPROM_PATH full-path override keeps its meaning, so the per-run EEPROM isolation used by the benchmark scripts and measurement guides still works.
    • cfg/ and nvram/ were added to .gitignore.

    Verification

    • Win32 x86 Debug builds of repiu_exe, repiu, and repiu_aot_probe succeeded.
    • repiu_aot_probe --romset-config: checks=94 failures=0, covering INI parsing, alias lists and turning an input off, name table health, combination parsing and its rejected shapes, modifier matching with the contention rule and lock-bit exclusion, layering, and generated file round trip and no-overwrite. The regression guard transcribes the pre-change hardcoded mapping directly and compares virtual key for virtual key.
    • repiu_aot_probe --nvram-path: checks=14 failures=0, covering override precedence, directory creation, separation between ROM sets, and carry-forward with matching contents and a preserved original.
    • repiu_aot_probe --bios-keyboard-input passed with translated=true,fifo=true,modifiers=true,overflow=1.
    • The complete repiu_aot_probe build/runtime_mounts/pumpit1/PIU/PIU.EXE assertion set exited zero.
    • The user confirmed config file generation and key mapping on a real pumpipx3 run.
    • Only the existing C4819 code-page warnings remain.

    Known limitations

    • COIN2 and the cabinet input port 0x02AB are unsupported; their I/O port bits are not identified.
    • Combinations are meant for cabinet buttons. Binding one to a stage panel is not recommended, because the window event path fixes the modifier test at the instant the base key goes down.
    • No sections beyond [Input] exist yet. Unrecognized sections are silently ignored so a file written for a later version does not stop an earlier build.
    • Per-ROM-set NVRAM has not yet been confirmed at runtime in a user environment: file creation after a run and carry-forward of existing settings remain unverified.
  42. 2026-08-17

    v0.0.156

    Changes since v0.0.155. This release removes the paletted-texture refresh bottleneck observed on the pumpitp3 title screen while preserving the Glide semantics that make a new palette affect existing P_8/AP_88 textures.

    Full release notes

    Changes since v0.0.155. This release removes the paletted-texture refresh bottleneck observed on the pumpitp3 title screen while preserving the Glide semantics that make a new palette affect existing P_8/AP_88 textures.

    Lazy paletted-texture refresh

    • Removed the eager path that CPU-decoded every retained P_8/AP_88 texture to RGBA and re-uploaded the entire set through glTexImage2D on every palette download.
    • The Win32 OpenGL backend advances a generation only when palette bytes actually change. A byte-identical download skips both generation advance and texture work.
    • Each indexed texture records its applied palette generation. A textured draw decodes retained indices and calls glTexSubImage2D only when the currently sampled texture is stale.
    • Palette changes remain visible before the next sample, retaining Task 483's independent texture/palette lifetime, opaque P_8 alpha, and AP_88 texel-alpha contracts.

    Performance telemetry

    • The ending texture census now reports total, changed, and identical palette downloads.
    • It also reports lazy-refresh successes/failures, source/RGBA bytes, CPU decode time, and OpenGL upload-call time.
    • The deferred Task 483 palette-performance TODO is complete in this implementation and its documentation.

    Verification

    • Win32 x86 Debug/Release builds of repiu_aot_probe and repiu succeeded.
    • The complete pumpitp3/PIU/PIU.EXE AOT probe exited zero in both configurations.
    • glide_texture_table_stack_probe=pass, glide_texture_census_palette_refresh_profiled=true, and glide_texture_census_all=true were confirmed.
    • The user confirmed the performance improvement on the real pumpitp3 title screen.
    • Only the existing C4819 code-page and LNK4217 warnings remain.
  43. 2026-08-15

    v0.0.155

    Changes since v0.0.154. This release restores DOS file-create dispatch on the dynamic traced execution path and scales Glide point size with the viewport when the 640×480 logical display is enlarged.

    Full release notes

    Changes since v0.0.154. This release restores DOS file-create dispatch on the dynamic traced execution path and scales Glide point size with the viewport when the 640×480 logical display is enlarged.

    Dynamic DOS file creation

    • Dynamic traced INT 21h/AH=3Ch requests now delegate to the common DOS HLE handler. The existing VFS remains the single owner of file-create semantics, return registers, and EIP advancement.
    • Closing a file now releases the write-stream cache as well as the read stream, allowing scratch directories to be cleaned up after created files are closed.
    • A probe using a real CD 21 instruction and guest path covers the integration boundary from traced dispatch through file creation and cleanup.

    Viewport-scaled Glide point size

    • Glide point size uses the smaller horizontal or vertical ratio between the original 640×480 logical display and the drawable viewport. At 1280×960, a former 1×1 dot occupies 2×2 physical pixels.
    • Selecting the smaller ratio preserves square dots for non-uniform scaling, and downscaled viewports retain a minimum size of one pixel.
    • The calculated size is clamped to OpenGL's reported aliased-point range and is applied consistently to direct and batched point rendering.

    Verification

    • Win32 x86 Release builds of repiu_aot_probe, repiu_glide_render_probe, and repiu succeeded.
    • The AOT probe passed with dos_file_create_traced_dispatch=true, dos_file_create_cleanup=true, and dos_file_create_all=true.
    • The Glide render probe passed point-scale cases for 1×, 2×, 3×, non-uniform, downscaled, and invalid viewport dimensions.
    • Only the existing C4819 code-page and optional LibUSB discovery warnings remain.

    Remaining live checks

    • Automated verification passed, but the next live-game execution frontier and generated ERRLOG.txt contents have not yet been inspected.
    • The pumpit8 service screen still needs a visual check that dots occupy 2×2 pixels in the 2× viewport.
  44. 2026-08-14

    v0.0.154

    Changes since v0.0.153. This release supports DOS date setting as per-run virtual state, expands the MAME Pump It Up target registry to the complete 22-entry catalog, and safely inherits clone CAT702 assets from parent ROM sets.

    Full release notes

    Changes since v0.0.153. This release supports DOS date setting as per-run virtual state, expands the MAME Pump It Up target registry to the complete 22-entry catalog, and safely inherits clone CAT702 assets from parent ROM sets.

    Virtual DOS system date

    • Implemented INT 21h/AH=2Bh Set System Date. A valid date is stored in the execution context as a day offset from the host's local date.
    • AH=2Ah Get System Date returns the virtual date and its weekday without changing the real Windows system date.
    • Ordinary DOS HLE and traced/AOT paths share the same contract, covered by date validation and set/get round-trip probes.
    • The user confirmed normal progress after date setting in a real run.

    Complete 22-entry Pump It Up target catalog

    • Expanded the previous 14 MAME profiles to the 22-entry GAME catalog order from xtom3d.cpp, adding pumpit2a, pumpit3a, pumpitpru, pumpitea, pumpipx2p, pumpitp3a, pumpipx3a, and pumpipx3b.
    • Preserved version, year, and date details in display names and normalized the brand spelling to Pump It Up.
    • Removed the temporary piu_1st profile and changed runner, supervisor, and analyzer defaults to pumpit1.

    Parent ROM-set CAT702 fallback

    • Every PIU profile now records its direct MAME parent ROM-set ID.
    • CAT702 lookup checks the current name in the current ZIP, the parent name in that ZIP, then the parent name in the sibling parent ZIP. Only an absent member advances to fallback; archive, extraction, and CRC failures remain visible.
    • A real pumpitpru run selected the eight-byte pumpitpr.cat702 member, initialized PIU10, handled all 33 observed PIU10 port-I/O operations, and reached Glide initialization.

    Verification

    • Full Win32 x86 Debug and Release builds passed.
    • Full pumpipx3 AOT probes exited zero in both configurations with piu10_target_profiles=true, dos_date_probe=pass, and coherence_all=true.
    • Debug and Release --piu10 probes passed the profile, CAT702 vector, MP3 ring, and frame-audit checks.
    • Only the existing C4819 and LNK4217 warnings remain.
  45. 2026-08-13

    v0.0.153

    Changes since v0.0.152. This release corrects the Glide texture-table calling convention observed in pumpipx3 and restores corrupted colours by making existing P_8/AP_88 indexed textures consume palettes downloaded later.

    Full release notes

    Changes since v0.0.152. This release corrects the Glide texture-table calling convention observed in pumpipx3 and restores corrupted colours by making existing P_8/AP_88 indexed textures consume palettes downloaded later.

    Glide texture-table ABI correction

    • _GRTEXDOWNLOADTABLE@12 now decodes four guest-stack dwords: return address, TMU, table type, and data pointer. The old path read only three dwords including the return address, shifted type and data by one slot, and cleaned 12 rather than 16 bytes on return.
    • Every handled path, including success, unsupported table types, and read failures, now applies the stdcall ESP += 16 contract.
    • The exact logged frame plus short and null frames are covered by glide_texture_table_stack_probe.

    Indexed-texture colour restoration

    • The ignored high byte of a standard Glide palette entry is no longer treated as alpha. P_8 uses opaque palette RGB, while AP_88 retains alpha from the texel's high byte.
    • The backend now preserves P_8/AP_88 indices and re-decodes existing indexed textures after a palette download, matching Glide's separate texture and palette lifetimes.
    • The user confirmed that the previously corrupted palette colours are restored in the real scene.

    Verification

    • Win32 x86 Debug and Release builds of repiu_aot_probe and repiu succeeded.
    • Both full probes exited zero with glide_texture_table_stack_probe=pass and glide_texture_census_all=true.
    • Only the existing C4819 and LNK4217 warnings remain.

    Known performance limitation

    • The user's runtime check found a severe performance loss after colour restoration.
    • Every palette download currently CPU-decodes all retained P_8/AP_88 textures to RGBA and re-uploads them through glTexImage2D. Unchanged-palette suppression, lazy refresh of used textures, and GPU indexed sampling are not implemented.
    • This release keeps the accuracy fix. Measurement and optimization are deferred in the Task 483 follow-up recorded in docs/TODO.md.
  46. 2026-08-12

    v0.0.150

    Everything since the previous release, v0.0.149. This release adds a guest memory-range dump diagnostic to the execution probe and, acting on the cause that diagnostic established, extends DOS low-memory read servicing to string instructions, removing the pumpit8 termination during background loading.

    Full release notes

    Everything since the previous release, v0.0.149. This release adds a guest memory-range dump diagnostic to the execution probe and, acting on the cause that diagnostic established, extends DOS low-memory read servicing to string instructions, removing the pumpit8 termination during background loading.

    Execution probe memory-range dump

    • REPIU_EXECUTION_PROBE_DUMP_* preserves a guest memory range to a file at the probe's first hit. The base is a register name or an absolute address, with optional indirect dereference to follow a pointer inside a structure and up to 1 MiB copied.
    • The capture buffer is allocated once before execution starts, so the first-hit recorder never allocates during exception handling. The file is written after the guest thread stops, and no file I/O happens inside the VEH.
    • The bytes never travel through the telemetry snapshot, which carries only capture and write status with the addresses.
    • The copy happens only when the complete range is readable inside the guest arena; no partial range or arbitrary host address is read. Without the environment variables the existing execution path is unchanged.
    • The repeatable procedure is in docs/guides/execution-probe-memory-dump.md.

    DOS low-memory string instruction servicing

    • HandleGuestLowMemoryReadFault accepted only MOV, MOVZX, and MOVSX and rejected every other mnemonic, leaving access violations from string instructions reading low memory unserviced.
    • Added a dedicated module servicing SCAS, LODS, and CMPS reads of low memory. MOVS is excluded because a dedicated handler already owns it, and STOS is excluded because it writes and falls outside this read facility.
    • Using the restartability of REP forms between iterations, EIP advances only when the repetition finished and is otherwise left in place, so the handler never tracks where low memory ends.
    • Width comes from the mnemonic rather than the operand width, since a 66h prefix changes the mnemonic itself and makes the mapping exact. ZYDIS_CATEGORY_STRINGOP gates first because the SSE cmpsd shares its mnemonic with the string form.
    • SetCompareFlags8 is generalized into a width-taking SetCompareFlags with the original delegating to it; 8-bit results are unchanged.
    • Servicing declines, leaving the existing diagnostics to report the cause, when the address size is not 32-bit, the access straddles the low-memory boundary, the CMPS counterpart fails the guest-range check, or a REP has ECX already zero.
    • No target-specific or executable-address behavior bypass was added.

    pumpit8 background-loading termination resolved

    • The 0xC0000005 termination while reading BR01_01.tga — actually a PNG — from BGA/083.DAT is gone.
    • The cause is that the asset's iCCP chunk carries a zlib datastream written without its four-byte adler32. The chunk CRC32 is 0x8FC1BA08 both stored and computed, confirming the chunk reached memory exactly as authored, so rePIU's file I/O, RES decryption, and PNG delivery are sound.
    • The original libpng 1.0.6 does not check the null return from png_decompress_chunk before adding the prefix 0x17 and dereferencing it with repne scasb. Static disassembly confirms no conditional branch separates the call from the fault.
    • The analysis is accumulated in docs/analysis/pumpit8-bga-iccp-crash.md.

    Verification

    • Win32 x86 Debug and Release builds passed.
    • A Debug pumpit8 run recorded 2,522 string services over 2,522 iterations, last EIP 0x040E5D0D, address 0x00000017, mnemonic SCASB, and zero exceptions.
    • The same run proceeded past BGA/083.DAT to AUDIO/004.AUD, rendering at roughly 6.9 FPS. It ended on an SDL exit request issued to stop the observation, not on an access violation.
    • All 819 OpenWatcom samples were built and the suite re-measured in the baseline's Release configuration, giving RunPassed 528 with zero regressions and zero new passes.

    Known limitations

    • pumpit8 only passes this site; completion is not verified, and later stages remain separate observation targets.
    • That linear 0x17 is actually readable under DOS/4GW is inferred from standard protected-mode extender behavior and was not observed directly in the original environment.
    • Low-memory servicing covers reads only. Paths that write to low memory, such as STOS, are not serviced yet.
    • No 67h-prefixed 16-bit address-size string instruction has been observed, so that form is not implemented.
    • The sample suite never reaches a low-memory string read, so zero regressions confirms the absence of impact rather than validating this path.
  47. 2026-08-11

    v0.0.149

    Everything since the previous release, v0.0.148. This release separates per-target CAT702 configuration, extends address- and binary-independent batching to wrapped port I/O and MP3 feeders, and adds seven later PIU10 target profiles.

    Full release notes

    Everything since the previous release, v0.0.148. This release separates per-target CAT702 configuration, extends address- and binary-independent batching to wrapped port I/O and MP3 feeders, and adds seven later PIU10 target profiles.

    TargetProfile and CAT702

    • Added TargetProfile::enable_cat702 independently from the PIU10 ISA-board capability.
    • With CAT702 disabled, PIU10 flash, MP3, and DAC remain active; CAT702 data-out is zero, serial input is ignored, and <target>.cat702 is not required.
    • Disabling CAT702 for pumpitpc reaches the real Lock Error key-wait path, while restoring the default enabled value continues beyond the same point. This verifies that the profile setting controls the original security-check result.
    • No title-specific adjustment was made to the CAT702 transform or challenge-response algorithm.

    Wrapped port-I/O delay loops

    • The batcher validates input-wrapper register preservation, port transfer, relative call, guest stack return address, and caller loop before skipping only iterations whose input is discarded.
    • The original guest executes the final iteration and flag calculation, and matching uses no target name or fixed code address.
    • In a same-Release-binary pumpitpc A/B, total port I/O fell about 94.2%, from 244,970 to 14,110, and privileged-instruction exceptions fell about 96.7%, from 236,962 to 7,936.

    Wrapped PIU10 MP3 feeders

    • The existing direct OUT DX,AL matcher remains first, followed by a matcher that validates a register-preserving byte-output wrapper and feeder call/return relationship.
    • Source cursor, frame count, ECX, backward loop, and service conditions come from instruction operands, without profile, executable-address, or hash dependencies.
    • Audit plans also stop at the compressed-inflight 0xE00 DEMAND boundary, preserving the same return point as scalar status polling.
    • A live pumpitpc audit passed 3,700 consecutive segments. A normal run batched 136,595 of 140,795 bytes, about 97.0%, with no drops or starvation.

    Additional target profiles

    • Added pumpitpr, pumpitpx, pumpit8, pumpitp2, pumpipx2, pumpitp3, and pumpipx3 immediately after the existing pumpite profile.
    • They use the shared piu_common, CHD mount, JAMMA/YMZ280B, PIU10 ISA, and CAT702 paths, with zero milliseconds of default MP3 startup latency.
    • Debug and Release profile probes validate registration order, uniqueness, paths, ROM-set ids, and capabilities.

    Verification

    • Win32 x86 Debug and Release builds passed.
    • Debug and Release repiu_aot_probe --piu10 passed.
    • Local ROM ZIPs for all seven new targets passed required PIU10/CAT702 entry validation.
    • Live pumpitpc normal execution, batching A/B, and MP3 audit runs completed.

    Known limitations

    • Copyrighted ROM ZIP and CHD assets are not distributed.
    • The seven new targets have no local CHDs, so validation currently stops after profile and ZIP checks; ISO mounting and guest execution remain unverified.
    • If a wrapper, loop, memory alias, or runtime range cannot be fully validated, the matchers retain the existing scalar path.
  48. 2026-08-11

    v0.0.148

    Everything since the previous release, v0.0.147. This release fixes the Lock Error!!! seen during the pumpitpc CAT702 security check. It does not tune the CAT702 algorithm for one binary; it corrects the shared Port I/O policy so Watcom output wrappers reused across devices remain intact.

    Full release notes

    Everything since the previous release, v0.0.147. This release fixes the Lock Error!!! seen during the pumpitpc CAT702 security check. It does not tune the CAT702 algorithm for one binary; it corrects the shared Port I/O policy so Watcom output wrappers reused across devices remain intact.

    pumpitpc CAT702 security check

    • A shared OUT DX,AX wrapper handles both startup writes to 0x2AC and 0x2A6 and later PIU10 CAT702 writes to 0x2D4, 0x2D6, and 0x2DA.
    • The previous HLE permanently NOP-patched the wrapper's OUT instruction on the first ignored or observation-only output. Later CAT702 serial transactions could no longer execute, sending the game into its Lock Error path.
    • Ignored and deferred outputs now advance only the current EIP without modifying guest code. This discards one transaction while retaining the shared wrapper for later DX-port calls.
    • The policy does not branch on target ID, executable address, or a specific port number, so it applies generally to equivalent dynamic DX-output wrappers.

    CAT702 model verification

    • The regression probe now covers real transform F0 1C FE 03 81 40 38 F8, challenge D0 AA C9 F8 96 4C D0 DE B6 0B, and response 7D 77 FE EA 8B 7A 55 7D 5F 1E.
    • With the guest's inverted LSB-first input, MSB-first response accumulation, extra clocks, adjacent-byte alignment, and final bit reversal included, the existing CAT702 HLE model matches the real response exactly.
    • No CAT702 transform, bit-order, or per-binary adjustment was added.

    Verification

    • The Win32 x86 Debug build passed.
    • The real PIU10 CAT702-vector probe and complete pumpitpc AOT probe passed.
    • A default-environment pumpitpc run continued for more than 50 seconds beyond the previous failure point.

    Known limitations

    • Copyrighted ROM ZIP and CHD assets are not distributed.
    • Additional CAT702 command forms used by other executables remain separate compatibility work.
  49. 2026-08-10

    v0.0.147

    Everything since the previous release, v0.0.146. This release makes AOT execute the zlib DEFLATE state dispatch that previously fell through the exception path millions of times during pumpito loading.

    Full release notes

    Everything since the previous release, v0.0.146. This release makes AOT execute the zlib DEFLATE state dispatch that previously fell through the exception path millions of times during pumpito loading.

    pumpito loading performance

    • AOT now recognizes the byte-state jump table used by pumpito's statically linked zlib 1.1.3 inflate_codes(). The previous log recorded roughly 12.26 million HLE dispatcher round trips at this loading hotspot.
    • The matcher validates the register and mask relationship in cmp low-r8, bound; jnbe; and parent-r32, 0xff; jmp cs:[parent-r32*4+table], then reuses the existing native jump-table emitter.
    • No pumpito-specific address, executable name, or zlib function name participates in matching. This is a general AOT optimization for binaries with the same machine-code contract.
    • A different mask or destination register, a high-byte comparison, or omitted normalization safely falls back to the existing dispatcher path.

    Verification

    • Planning directly from pumpito PIU.EXE's inflate_codes() region produces one native jump table with ten targets and zero HLE boundaries.
    • Synthetic probes cover the supported byte-guard form, the existing 32-bit guard form, and four fail-closed variants.
    • Win32 x86 Release builds of repiu and repiu_aot_probe, plus the full probe suite, pass.

    Known limitations

    • Loading-time and exception-count reductions still require final measurement from a new run with user-supplied game assets. Static planning and code-cache generation are verified, so no measured speedup multiplier is claimed yet.
    • Copyrighted ROM ZIP and CHD assets are not distributed.
  50. 2026-08-09

    v0.0.146

    Everything since the previous release, v0.0.143. This stretch focuses on later PIU targets and PIU10 sound-board HLE. pumpito, pumpitc, pumpitpc, and pumpite now have a path that keeps the original game code running while MP3 music and rendering progress concurrently.

    Full release notes

    Everything since the previous release, v0.0.143. This stretch focuses on later PIU targets and PIU10 sound-board HLE. pumpito, pumpitc, pumpitpc, and pumpite now have a path that keeps the original game code running while MP3 music and rendering progress concurrently.

    New targets and CHD support

    • Added MAME CHD profiles for pumpito, pumpitc, pumpitpc, and pumpite, bringing the selectable pumpit* profiles to seven together with pumpit1, pumpit2, and pumpit3.
    • Added cooked Mode1 CHD sectors plus data-track LBA and ISO-extent correction, allowing ISO9660 mounts for images such as pumpite, whose data track does not begin at disc LBA zero.
    • Mount caches are reused while CHD identity is unchanged. Each profile launches build/runtime_mounts/<target>/PIU/PIU.EXE.
    • JAMMA input is enabled for every pumpit* profile. PIU10 ISA flash, CAT702, and MP3 capability is enabled only for the hardware-appropriate pumpito, pumpitc, pumpitpc, and pumpite.

    PIU10 ISA-board HLE

    • Added platform-neutral HLE for the PIU10 20-bit flash address, read-only piu10.u8, destination selection, status source, and CAT702 PIU serial-security device.
    • piu10.u8 and <target>.cat702 are extracted from the matching ROM ZIP with CRC validation. The board is not exposed to unsupported profiles, keeping the pumpit1 through pumpit3 paths separate.
    • Only the ISA port-I/O boundary is replaced; original executable and gameplay logic remain authoritative.

    MAS3507D MP3 streaming HLE

    • PIU10 MPEG byte streams are played through minimp3 and SDL3. MAME's MAS3507D implementation informed the device contract only; no MAME MAS3507D code is included.
    • Frames are decoded as they become available instead of waiting for a complete song. A persistent decoder and dedicated audio worker let original game execution and rendering continue during playback.
    • Stream sync requires three compatible headers, and exactly one parser-validated frame is passed to the decoder, preventing an incomplete next header at a FIFO boundary from invalidating the current frame.
    • MAS3507D DEMAND is based on compressed inflight across both the 4 KiB physical ring and worker staging. This backpressure fixes mid-song interruption caused by the guest running seconds ahead of HLE output.
    • Frame-sync status follows actual SDL queue playback position rather than decode-ahead time.

    DAC3350A output control

    • DAC3350A AVOL transactions are reconstructed from destination-0x010 SDA/SCL traffic.
    • AVOL mute and dB steps control SDL audio-stream gain, including PCM already queued for output, without changing the compressed FIFO or MPEG decoder state.
    • MP3 pause is not implemented.

    MP3 input performance and generalization

    • Batches the repeated OUT DX,AL feeder tail, removing a per-byte HLE dispatcher round trip.
    • The matcher uses no target name, fixed EIP, LE object number, or data offset. It validates the current instructions' load/increment/store aliases, register def-use, and branch boundaries, so equivalent compiler register allocations share one device contract.
    • Live logs batch about 98.6% of MP3 input on pumpitc and about 98.7% at the first pumpite checkpoint. Any validation failure preserves guest state and falls back to the scalar byte path.

    MP3 latency and diagnostics

    • Every PIU10 profile now defaults to 0 ms MP3 startup latency.
    • REPIU_PIU10_MP3_LATENCY_MS=0..500 can request per-run silence compensation when needed.
    • REPIU_PIU10_DAC_AUDIT=1 records PCM queue, device buffer, compressed ring, decoder pending, and inflight state at each DAC transaction.
    • REPIU_PIU10_MP3_STREAM_AUDIT=1 compares producer and consumer streams with independent 4 KiB hashes. Both diagnostics are off by default.

    Third-party code and licensing

    • PIU10 and CAT702 contracts are informed by BSD-3-Clause MAME sources.
    • MP3 frame decoding uses a pinned upstream minimp3 commit under CC0 1.0 Universal.
    • No MAME emulator, CPU/machine framework, or MAS3507D wrapper code is included.

    Known limitations

    • Copyrighted ROM ZIP and CHD assets are not distributed; users must supply legally owned copies.
    • CI has no game assets, so concurrent music/rendering behavior remains a local live-validation responsibility.
    • rePIU remains research-stage software rather than a finished game launcher.

    Versions included

    v0.0.144, v0.0.145, v0.0.146.

  51. 2026-08-07

    v0.0.143

    Everything since the previous release, v0.0.136. This stretch is about performance: pumpit2 gameplay went from 69.3 to 107.2 frames per second, +54.7%.

    Full release notes

    Everything since the previous release, v0.0.136. This stretch is about performance: pumpit2 gameplay went from 69.3 to 107.2 frames per second, +54.7%.

    ⚠️ The executable was renamed

    repiu_loader_win32.exe is now repiu.exe. Update any script or shortcut that points at the old name.

    Performance

    Removing the inline-cache patch round trip is where the frames came from: +54.7%. Every indirect call/jump cache miss writes fourteen bytes into the code cache, and the guest thread was waking a worker thread and waiting for it to do that — roughly 350 times per frame, holding 30.4% of the guest thread's sampled positions. The guest now patches its own caches.

    Glide setter state elision skips the host round trip when a setter is called with the value already applied; 89.6% of setter calls are now elided, and grDitherMode costs 95% less per call. Draw batching collects consecutive primitives into one submission, raising the mean batch from 2.0 to 32.1 primitives.

    All three are on by default and each has an off switch: REPIU_AOT_INLINE_CACHE_PATCH_INLINE=0, REPIU_GLIDE_SETTER_ELIDE=0 and REPIU_GLIDE_DRAW_BATCH=0.

    Diagnostics

    A host crash reporter now records the exception code, address, access kind and a symbolised call stack with source lines. It earned itself immediately: a teardown crash that five rounds of guessing had failed to find was named on the first crashing run.

    The asynchronous present axis was closed as a measurement error. grBufferSwap looked large with vsync on, but that was display wait; with vsync off it is 2.9%. The implementation stays opt-in, kept on accuracy grounds rather than speed.

    Build and CI

    OpenWatcom is pinned to an immutable dated snapshot instead of upstream's rolling Current-build, whose asset is replaced in place so that a pinned hash expires with nothing here changing. The toolchain moved with it and the sample baseline did not: 819 samples build, 528 pass, zero regressions. And the sample suite now builds without the original asset tree — it compiles its own DOS programs and never loads the game, but the environment check demanded the assets anyway.

    Versions included

    v0.0.137, v0.0.138, v0.0.139, v0.0.140, v0.0.141, v0.0.142, v0.0.143.

  52. 2026-08-07

    v0.0.136

    Full Changelog: https://github.com/nworkers/rePIU/compare/v0.0.81...v0.0.136

    Full release notes

    ✨ New Features & Emulation (HLE)

    • Emulation & Audio
      • Emulate the PIU10 YMZ280B and route its output to SDL audio (b3dcb5e)
      • Deliver CD playback position to the guest and start track 2 at its true beginning (5f93b64)
    • Graphics & Glide
      • HLE: Implement Glide Combine and TMU state for full alpha and texture accuracy (3047b5c)
      • Implement Glide line drawing and cull modes (2a9456a)
      • Implement safe GlideGateId O(1) switch dispatch and complete Glide API family (73a3122)
    • Target Bring-up
      • Add pumpit2 multisession CHD profile and handle unresolved AOT-DBT direct edges (919ff28)
      • Bring up the pumpit3 target through five HLE gaps to its render loop (211cbfc)
    • Execution & AOT
      • Add AOT-DBT execution and improve SDL host lifecycle (84797bf)
      • Implement Port I/O AOT Fast-Path and Opcode-directed HLE Dispatcher (62c89f8)
      • Native region execution foundation: diagnose single-step wall, prove the interception mechanism (0c7a5ba)
      • Translate AOT segment instructions natively, removing the dominant segmentation exception cost (61d04ae)

    ⚡ Performance & Optimization

    • AOT & Execution Speed
      • Translate from the live guest arena and cut append cost 10.5x (3552db7)
      • Unblock the post-HLE return path and raise the frame rate 2.19x (e2f6563)
      • Move the performance baseline to Release and raise the frame rate 5.8x (c1087b4)
      • Harden AOT-DBT dispatch and enable native linear spans (d790ab7)
      • Reduce AOT-DBT single-step overhead safely (fa73415)
      • Add guarded AOT segment-pop fast path (18540db)
      • Improve AOT fallback performance and diagnostics (e67a9e7)
      • Improve AOT caching and native execution coverage (fc7ddba)
      • Attribute whole-run execution time and remove the AOT lookup bottleneck (f687886)
      • Drop the redundant per-gate pump rendezvous (a2982d9)
      • Re-baseline the cost profile and cut the Glide rendezvous latency (6101dbe)
      • Optimize Music Select execution boundaries (672e343)
    • System & I/O
      • Keep DOS file handles open across reads (Task 374) (4760c19)
      • Replace Glide GL error polling and find the display limit (Tasks 369-371) (8bf7d52)

    🐛 Bug Fixes

    • Graphics (Glide)
      • Fix the corrupted 3D model by connecting Glide vertex depth (6ab688a)
      • Fix Glide LFB rendering and stabilize Win32 AOT execution (ea609ba)
      • Fix Glide background, fog, and LFB rendering (bd5a5c4)
      • Draw Glide sprites at the right scale and color, and implement grHints (9bcba7b)
      • Harden Glide gates and report implementation gaps (7c758b0)
    • System & Inputs
      • Fix the note and background animation (BGA) jumping by preserving owed timer ticks (e341c8f)
      • Fix guest timer delivery and honor PIT cadence (b18e54a)
      • Fix the pumpit3 startup stall and add the instruments that found it (280ed28)
      • Fix JAMMA system and P2 keypad mappings (dc8852f)
      • Classify AOT-DBT RET fallbacks by exclusive cause and fix the next DBT target (f815d2e)

    🔍 Profiling & Analysis

    • Bottleneck Identification
      • Find pumpit3's real input cost and bound it with a snapshot (cc21627)
      • Locate pumpit3's dominant cost in one port I/O instruction (8d9b365)
      • Reproduce the pumpit3 startup stall and rule out its two obvious causes (f78b895)
      • Locate the dynamic translation bottleneck in the full-arena snapshot (79691a2)
      • Instrument AOT boundary exits and pin the root cause to segmentation instructions (c4885c3)
    • Cost Attribution & Profiling
      • Attribute pumpit3 arena execution and record the next ten steps (458efdd)
      • Attribute every VEH exit and settle who consumes the arena-entry single step (d789786)
      • Measure the kernel exception round trip and scope the next axis (Tasks 372-373) (d1155a3)
      • Attribute and act on the Glide/exception axis (Tasks 364-368) (3ab6a1b)
      • Attribute execution cost down to dynamic translation on the worker thread (47e1b96)
      • Profile Release execution and Glide bottlenecks (2e502e3)
      • Record aot-dynamic being 14.6x slower than legacy as the next task (c15c362)
    • Investigations & Diagnostics
      • Instrument the CD audio path and trace the stall to our own watchdog (7583fde)
      • Disprove the hidden single-step population and retract 6.5% (Task 376) (99d7016)
      • Settle the texture suspicion with a census and pixel dump (Task 375) (206d414)

    🛠 Chore, Refactoring & CI/CD

    • Refactoring
      • Migrate window and CD audio services to SDL3 (26f28fd)
      • Consolidate the execution backends to legacy and dynamic (78366f8)
    • CI/CD & Testing
      • Add tag-triggered build verification and release artifacts on GitHub Actions (c6e9117)
      • Require a version tag on every main merge (35842b6)
      • Require completion in the OpenWatcom sample pass criterion (3b5073c)
      • Refresh the OpenWatcom sample baseline to v0.0.133 (2038829)
    • Configuration & Documentation
      • Default execution to dynamic with no time limit, and make source comments English-only (d78e328)
      • Attribute plan build and move the performance baseline to Release (dcd8937)
      • Document Glide call-volume performance plan (609d77d)
      • Record the GLSL shader review and the Task 377 direction (0e49182)
      • Record next optimization frontier: multi-slot indirect inline cache (b3e7fa2)
      • Add the post on tracing note jumping to lost timer ticks (a19360e)

    Full Changelog: https://github.com/nworkers/rePIU/compare/v0.0.81...v0.0.136