Confirm real PinMAME ROM-driven emulation actually running

Tested pinball.loadRom() against a correctly-matched ROM zip for a real
community table (Heavy Metal Meltdown, Bally 1987): PinMAME loads the ROM,
and its own video/audio subsystems come up and run with no errors
(osd_create_display: 60.00 fps; real INT16/44100Hz audio format), not just
Controller object creation succeeding as before. Updates the README to move
this from "not yet validated" to confirmed, narrowing the remaining gap to
on-screen DMD/backglass output and hands-on switch/solenoid/scoring
behavior during actual play, which nobody has watched yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
This commit is contained in:
2026-08-23 12:34:25 +02:00
co-authored by Claude Sonnet 5
parent 9479cea280
commit a7b85ec8a4
+6 -5
View File
@@ -21,16 +21,16 @@ Getting here required finding and fixing four real, previously-unknown bugs in t
3. The window's OpenGL back buffer was created with the window's *logical* (CSS) pixel size instead of its *device* pixel size — on any browser tab with `devicePixelRatio != 1` (essentially all HiDPI displays), the GL viewport only covered a fraction of the canvas's actual backing buffer, rendering anchored to one corner (GL's viewport origin) instead of filling the canvas. 3. The window's OpenGL back buffer was created with the window's *logical* (CSS) pixel size instead of its *device* pixel size — on any browser tab with `devicePixelRatio != 1` (essentially all HiDPI displays), the GL viewport only covered a fraction of the canvas's actual backing buffer, rendering anchored to one corner (GL's viewport origin) instead of filling the canvas.
4. `Window::OnResized()` (called on every SDL resize event, including entering/leaving browser fullscreen) updated the window's own tracked pixel size but never propagated it to the window's back buffer render target — so toggling fullscreen resized the canvas but rendering stayed pinned to the pre-resize viewport size. 4. `Window::OnResized()` (called on every SDL resize event, including entering/leaving browser fullscreen) updated the window's own tracked pixel size but never propagated it to the window's back buffer render target — so toggling fullscreen resized the canvas but rendering stayed pinned to the pre-resize viewport size.
## Status: real PinMAME (`VPinMAME.Controller`) integration — cooperative scheduling confirmed, gameplay unconfirmed (no ROM to test with) ## Status: real PinMAME (`VPinMAME.Controller`) integration — real ROM-driven emulation confirmed running
Real-hardware ("SS"/ROM-based) tables `CreateObject("VPinMAME.Controller")` against the actual PinMAME emulation core (`libpinmame`, from the real [vpinball/pinmame](https://github.com/vpinball/pinmame) project — not a stub, not a mock), compiled to wasm32 and statically linked in. Confirmed by hands-on testing against a real community ROM-based table: the Controller correctly identifies the requested game from PinMAME's actual ~2,900-entry built-in driver database (`PinMAME::Controller::SetGameName``Game found: name=hvymetal, description=Heavy Metal Meltdown, manufacturer=Bally, year=1987`), and a missing/invalid ROM now fails cleanly (a logged error, the game just doesn't start) instead of crashing the page. Real-hardware ("SS"/ROM-based) tables `CreateObject("VPinMAME.Controller")` against the actual PinMAME emulation core (`libpinmame`, from the real [vpinball/pinmame](https://github.com/vpinball/pinmame) project — not a stub, not a mock), compiled to wasm32 and statically linked in. Confirmed by hands-on testing against a real community ROM-based table with its real, correctly-matched ROM zip supplied via `pinball.loadRom()`: the Controller identifies the requested game from PinMAME's actual ~2,900-entry built-in driver database (`PinMAME::Controller::SetGameName``Game found: name=hvymetal, description=Heavy Metal Meltdown, manufacturer=Bally, year=1987`), loads the ROM successfully, and PinMAME's own video and audio subsystems come up and run with no errors (`osd_create_display: 60.00 fps`; `OnAudioAvailable: format=INT16, channels=1, sampleRate=44100.00, framesPerSecond=60.00`) — this is real emulation actually executing, not just object creation succeeding. A missing/invalid ROM (verified separately, with two different non-matching ROM zips) fails cleanly instead — a logged error, the game just doesn't start, no page crash.
Getting a real OS-thread-based emulator core running inside a single-threaded WASM build required the same class of fix as the vsync/game-loop bugs above, just one level deeper — this time inside PinMAME's own CPU-execution loop, not vpinball's: Getting a real OS-thread-based emulator core running inside a single-threaded WASM build required the same class of fix as the vsync/game-loop bugs above, just one level deeper — this time inside PinMAME's own CPU-execution loop, not vpinball's:
1. `PinmameRun()` spawned PinMAME's actual emulation main loop (`run_game()``run_machine()``run_machine_core()``cpu_run()`, MAME's real CPU-cycle scheduler) on a real `std::thread` — the same hard-abort-under-Emscripten bug as `RenderDevice::WaitForVSync()` above, just for an entire emulation session instead of one vsync wait. Fixed by splitting that whole call chain into a one-shot init, a step bounded to roughly one host video frame of emulated time (`timer_get_time()`-bounded, called once per frame from `Player::EmscriptenStepFrame()` itself), and a one-shot teardown — see `patches/pinmame/0004-emscripten-cooperative-scheduling.patch`. 1. `PinmameRun()` spawned PinMAME's actual emulation main loop (`run_game()``run_machine()``run_machine_core()``cpu_run()`, MAME's real CPU-cycle scheduler) on a real `std::thread` — the same hard-abort-under-Emscripten bug as `RenderDevice::WaitForVSync()` above, just for an entire emulation session instead of one vsync wait. Fixed by splitting that whole call chain into a one-shot init, a step bounded to roughly one host video frame of emulated time (`timer_get_time()`-bounded, called once per frame from `Player::EmscriptenStepFrame()` itself), and a one-shot teardown — see `patches/pinmame/0004-emscripten-cooperative-scheduling.patch`.
2. Two small wasm32 portability bugs in libpinmame itself, unrelated to threading: an x86-only compiler intrinsic (`__rolq`/`__rorq`) reached by a portability guard that didn't exclude wasm32, and a bundled-zlib include path only wired up for the Windows build — see `patches/pinmame/0001-*` and `0003-*`. 2. Two small wasm32 portability bugs in libpinmame itself, unrelated to threading: an x86-only compiler intrinsic (`__rolq`/`__rorq`) reached by a portability guard that didn't exclude wasm32, and a bundled-zlib include path only wired up for the Windows build — see `patches/pinmame/0001-*` and `0003-*`.
3. An optional external-clock-sync feature (`time_fence`, mirrored by `Controller.TimeFence` in table scripts) used POSIX semaphores unavailable in this build; it now reports itself as unsupported instead, the same way every other optional sync/timing feature becomes a no-op under this build's single-threaded model — see `patches/pinmame/0002-*`. 3. An optional external-clock-sync feature (`time_fence`, mirrored by `Controller.TimeFence` in table scripts) used POSIX semaphores unavailable in this build; it now reports itself as unsupported instead, the same way every other optional sync/timing feature becomes a no-op under this build's single-threaded model — see `patches/pinmame/0002-*`.
A ROM zip can be supplied via `pinball.loadRom(gameName, romZipBytes)`, written to `/tables/pinmame/roms/<gameName>.zip` — the same table-relative convention vpinball's own PinMAME plugin already checks before any global settings path, so no extra configuration is needed. **Not yet validated: actual gameplay** (real switch/solenoid state, sound, scoring) against a real ROM — none was available to test against, and ROM files are copyrighted, so none was sought out. Table script compatibility for tables that don't need real ROM emulation — most tables, including the bundled default — is unaffected either way. A ROM zip can be supplied via `pinball.loadRom(gameName, romZipBytes)`, written to `/tables/pinmame/roms/<gameName>.zip` — the same table-relative convention vpinball's own PinMAME plugin already checks before any global settings path, so no extra configuration is needed. **Still not visually/interactively confirmed:** actual on-screen DMD/backglass output and hands-on switch/solenoid/scoring behavior during play — video/audio subsystem startup is confirmed (see above), but no one has yet watched a full game played against this integration. Table script compatibility for tables that don't need real ROM emulation — most tables, including the bundled default — is unaffected either way.
## Why a source-level port, not a reimplementation ## Why a source-level port, not a reimplementation
@@ -81,9 +81,10 @@ Full research and two isolated feasibility spikes (proving real VBScript executi
- **A full loading → play flow in a real page** (`examples/basic/index.html`): a byte-level download progress bar, a user-gesture "Start" button (required for both audio autoplay and fullscreen to work), a working fullscreen button, and responsive canvas sizing that fills its container correctly. - **A full loading → play flow in a real page** (`examples/basic/index.html`): a byte-level download progress bar, a user-gesture "Start" button (required for both audio autoplay and fullscreen to work), a working fullscreen button, and responsive canvas sizing that fills its container correctly.
- **Browser file-loading for self-contained tables**: picking a `.vpx` file, mounting it via `FS.writeFile`, and starting the engine against it — verified end-to-end (`PinTable::LoadGameFromFilename /tables/uploaded.vpx` in the boot log, followed by a normal render). - **Browser file-loading for self-contained tables**: picking a `.vpx` file, mounting it via `FS.writeFile`, and starting the engine against it — verified end-to-end (`PinTable::LoadGameFromFilename /tables/uploaded.vpx` in the boot log, followed by a normal render).
- A JS-callable `evalScript()` debug/utility hook (`pinball.evalScript(script)`) that runs arbitrary VBScript against the live table via the same entry point the interpreter's own debug console uses. - A JS-callable `evalScript()` debug/utility hook (`pinball.evalScript(script)`) that runs arbitrary VBScript against the live table via the same entry point the interpreter's own debug console uses.
- **Real PinMAME (`VPinMAME.Controller`) ROM-driven emulation actually running**, not just object creation — see Status above for the exact confirmation (real video/audio subsystem startup against a real, correctly-matched ROM zip supplied via `pinball.loadRom()`).
**Not yet validated (should work, per code review and by analogy to already-proven mechanisms, but unconfirmed end-to-end):** **Not yet validated (should work, per code review and by analogy to already-proven mechanisms, but unconfirmed end-to-end):**
- **PinMAME (`VPinMAME.Controller`) real gameplay** — Controller creation, game identification, and graceful missing-ROM handling are confirmed (see Status above), but actual ROM-driven switch/solenoid/sound/scoring behavior is unconfirmed end-to-end, since no ROM file was available to test against (and none was sought out — they're copyrighted). - **PinMAME on-screen DMD/backglass output and hands-on gameplay** — the emulation session itself is confirmed running (see Status above), but no one has yet watched real DMD frames render or played a full game against it to confirm switch/solenoid/scoring behavior end-to-end.
- Gamepad/joystick input during real gameplay (keyboard input uses the identical `InputManager` pipeline and is confirmed working; gamepad support is code-complete but untested on real hardware). - Gamepad/joystick input during real gameplay (keyboard input uses the identical `InputManager` pipeline and is confirmed working; gamepad support is code-complete but untested on real hardware).
- Touch-control overlay (`attachTouchControls`) actually flipping a flipper on a real touch device — implemented and included in the example (synthesizes the real default keyboard scancodes on `window`), not yet confirmed on physical touch hardware. - Touch-control overlay (`attachTouchControls`) actually flipping a flipper on a real touch device — implemented and included in the example (synthesizes the real default keyboard scancodes on `window`), not yet confirmed on physical touch hardware.
- DMD rendering via a script-driven `Flasher`/`ScriptGlobalTable::put_DMDPixels`. This is native, core-engine functionality requiring no new code (`src/core/ScriptGlobalTable.cpp:886-937`, `src/parts/flasher.cpp:1312-1341`), and rides the exact same `ScriptInterpreter::Evaluate()` VBScript-dispatch path already confirmed working by the flipper test above — but the bundled default table has no DMD-configured `Flasher`, so a full *visual* confirmation needs a real DMD-equipped table (uploadable via the file picker) or authoring one, neither done yet. - DMD rendering via a script-driven `Flasher`/`ScriptGlobalTable::put_DMDPixels`. This is native, core-engine functionality requiring no new code (`src/core/ScriptGlobalTable.cpp:886-937`, `src/parts/flasher.cpp:1312-1341`), and rides the exact same `ScriptInterpreter::Evaluate()` VBScript-dispatch path already confirmed working by the flipper test above — but the bundled default table has no DMD-configured `Flasher`, so a full *visual* confirmation needs a real DMD-equipped table (uploadable via the file picker) or authoring one, neither done yet.
@@ -152,7 +153,7 @@ See `examples/basic/index.html` for a complete working page (loading progress, f
## Known limitations ## Known limitations
- Single-threaded only (no pthreads/SharedArrayBuffer) — a deliberate tradeoff, not a gap; see Roadmap. - Single-threaded only (no pthreads/SharedArrayBuffer) — a deliberate tradeoff, not a gap; see Roadmap.
- Real PinMAME (`VPinMAME.Controller`) integration for ROM-based tables — Controller creation, game identification, and graceful missing-ROM handling confirmed, but actual ROM-driven gameplay unconfirmed without a real ROM (copyrighted, none sought out) — see Status and Roadmap. The rest of the plugin ecosystem (DOF/FlexDMD/etc., and the modern `plugins/b2s` backglass plugin) remains unwired — see Roadmap. - Real PinMAME (`VPinMAME.Controller`) integration for ROM-based tables — confirmed actually running real ROM-driven emulation (video/audio subsystem startup against a real ROM), but on-screen DMD/backglass output and hands-on switch/solenoid/scoring behavior during play are not yet confirmed — see Status and Roadmap. The rest of the plugin ecosystem (DOF/FlexDMD/etc., and the modern `plugins/b2s` backglass plugin) remains unwired — see Roadmap.
- No raw hardware input (real cabinet nudge/plunger boards) — permanent, no browser equivalent. - No raw hardware input (real cabinet nudge/plunger boards) — permanent, no browser equivalent.
- Keyboard input, audio, and VBScript-driven gameplay are confirmed working end-to-end by hands-on manual testing (not just code review). Gamepad input and the touch-control overlay are code-complete and use the identical input pipeline, but are not yet confirmed on real gamepad/touch hardware — see Roadmap. - Keyboard input, audio, and VBScript-driven gameplay are confirmed working end-to-end by hands-on manual testing (not just code review). Gamepad input and the touch-control overlay are code-complete and use the identical input pipeline, but are not yet confirmed on real gamepad/touch hardware — see Roadmap.
- DMD rendering is native, code-complete functionality riding the same proven VBScript-dispatch path, but not yet visually confirmed since the bundled default table has no DMD-configured `Flasher` — see Roadmap. - DMD rendering is native, code-complete functionality riding the same proven VBScript-dispatch path, but not yet visually confirmed since the bundled default table has no DMD-configured `Flasher` — see Roadmap.