Add real PinMAME (VPinMAME.Controller) integration for ROM-based tables
Statically links the real PinMAME emulation core (libpinmame) instead of leaving VPinMAME.Controller creation to fail, which was crashing table scripts on ROM-based tables before they could spawn a ball. PinMAME's own run_game()->cpu_run() loop is split into a one-shot init, a per-frame step, and a one-shot teardown (patches/pinmame/0004) so it runs cooperatively on the same frame callback as vpinball's own loop instead of on a real std::thread, which hard-aborts under Emscripten's single-threaded runtime - three smaller wasm32 portability fixes to libpinmame itself round out the patch set (0001-0003). Adds pinball.loadRom() to supply a ROM zip, written to the table-relative pinmame/roms/ path vpinball's own plugin already checks. Confirmed against a real community ROM-based table: Controller creation and game identification succeed, and a missing ROM now fails cleanly instead of crashing the page - actual ROM-driven gameplay is still unconfirmed since no ROM was available (or sought out) to test with. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
This commit is contained in:
@@ -21,6 +21,17 @@ 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.
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
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`.
|
||||
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-*`.
|
||||
|
||||
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.
|
||||
|
||||
## Why a source-level port, not a reimplementation
|
||||
|
||||
Projects like [`vpx-js`](https://github.com/vpdb/vpx-js) reimplement Visual Pinball's physics and a VBScript-to-JavaScript transpiler from scratch in TypeScript. That approach has to independently re-derive correct behavior for every physics quirk and every VBScript language feature — and in `vpx-js`'s case, its own test suite documents that it never achieved compatibility with `core.vbs`, the shared script library nearly all real tables depend on.
|
||||
@@ -72,14 +83,15 @@ Full research and two isolated feasibility spikes (proving real VBScript executi
|
||||
- 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.
|
||||
|
||||
**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).
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
**Explicitly out of scope for now** (browser-sandbox constraints or a real infrastructure-cost tradeoff, not a technical dead end — could be revisited):
|
||||
- **Multi-threading** (pthreads/SharedArrayBuffer): would require mandatory `Cross-Origin-Opener-Policy`/`Cross-Origin-Embedder-Policy` headers on every page hosting this widget, a `coi-serviceworker`-style workaround for static hosts that can't set custom headers, and real risk of breaking unrelated cross-origin embeds on host pages — a poor tradeoff for something meant to be embeddable in arbitrary third-party pages. `ThreadPool`'s one-off parallel work (e.g. parallel `.vpx` item deserialization) already runs synchronously instead (`patches/vpinball/0002-*`). Revisit only if real-world profiling shows single-threaded performance is genuinely insufficient.
|
||||
- **The heavy plugin ecosystem** (PinMAME real ROM emulation — copyrighted ROMs, can't legally bundle; DOF/AltSound/PUP/FlexDMD/Serum — real-cabinet-hardware or FFmpeg-dependent; `b2slegacy` — a ~11,000-line legacy VB6-COM compatibility layer). Excluded via a single CMake guard; zero core-engine impact.
|
||||
- **The modern `plugins/b2s` backglass plugin** — explicitly assessed and declined for now, not just left undone. `B2SServer` itself has no browser-incompatible networking (it's built on vpinball's own in-process plugin messaging bus, not real sockets), but our emscripten build currently excludes the *entire* plugin subsystem outright (`CMakeLists_plugins.txt` isn't included at all), so wiring B2S in means new static-linking plumbing modeled on iOS/Android's `__LIBVPINBALL__` path, which this project's `__STANDALONE__` build doesn't share — a genuinely multi-hour undertaking, and one with no way to confirm it actually renders anything without a real `.directb2s`-equipped test table, which isn't available. Revisit if/when such a table is available to validate against.
|
||||
- **The rest of the plugin ecosystem beyond PinMAME** (DOF/AltSound/PUP/FlexDMD/Serum — real-cabinet-hardware or FFmpeg-dependent; `b2slegacy` — a ~11,000-line legacy VB6-COM compatibility layer). Excluded via a single CMake guard; zero core-engine impact. (PinMAME itself is no longer in this category — see Status above.)
|
||||
- **The modern `plugins/b2s` backglass plugin** — still not wired in, but meaningfully cheaper now than when this was last assessed: PinMAME's own static-linking plumbing (`patches/vpinball/0006-*`, see Status above) already established the exact pattern B2S would need — registering a statically-linked plugin via `MsgPluginManager::RegisterPlugin` instead of desktop VP's dynamic `/plugins` folder scan — so this is now mostly "repeat the same wiring for a second plugin" rather than new infrastructure. Still no `.directb2s`-equipped test table available to confirm it actually renders anything against. Revisit if/when one is available.
|
||||
- Raw HID device input (`OpenPinDevHandler`/hidapi) — no browser equivalent for real-hardware nudge/plunger boards. Permanent, not a "for now."
|
||||
|
||||
**Further follow-up work:**
|
||||
@@ -95,9 +107,9 @@ Full research and two isolated feasibility spikes (proving real VBScript executi
|
||||
Requires: `bison` ≥ 3.8.2, `curl`, `git`, `cmake` ≥ 3.25, `python3` (for the dev server), a POSIX shell. Emscripten itself is installed automatically by `setup.sh`. Expect a multi-gigabyte `emsdk/` + `build/` footprint and a first build in the tens of minutes (subsequent builds are much faster, especially with CI caching).
|
||||
|
||||
```bash
|
||||
./scripts/setup.sh # installs emsdk + bison check, fetches vpinball + libwinevbs at pinned commits, applies patches/
|
||||
./scripts/setup.sh # installs emsdk + bison check, fetches vpinball + libwinevbs + pinmame at pinned commits, applies patches/
|
||||
source emsdk/emsdk_env.sh
|
||||
./scripts/build-deps.sh # builds SDL3/SDL3_image/SDL3_ttf/FreeImage/libwinevbs for wasm32
|
||||
./scripts/build-deps.sh # builds SDL3/SDL3_image/SDL3_ttf/FreeImage/libwinevbs/libpinmame for wasm32
|
||||
./scripts/build.sh # configures + builds vpinball itself -> dist/vpinball.{js,wasm,data}
|
||||
./scripts/dev-server.sh # serves dist/ locally for manual testing
|
||||
```
|
||||
@@ -126,6 +138,13 @@ startButton.addEventListener('click', () => {
|
||||
// Optional debug/utility hook: runs arbitrary VBScript against the live
|
||||
// table, via the same entry point the interpreter's own debug console uses.
|
||||
pinball.evalScript('DMDWidth = 128 : DMDHeight = 32');
|
||||
|
||||
// Optional: supply a ROM zip for a real-hardware ("SS") table's
|
||||
// CreateObject("VPinMAME.Controller")/Controller.Run() before start() -
|
||||
// ROM files are copyrighted, so bring your own legally-obtained one.
|
||||
// gameName is the short ROM name the table's script passes to LoadVPM
|
||||
// (e.g. "hvymetal"), not the table's display title.
|
||||
pinball.loadRom('hvymetal', romZipArrayBuffer);
|
||||
```
|
||||
|
||||
See `examples/basic/index.html` for a complete working page (loading progress, file upload, touch controls, fullscreen). Published as `@valknar/vpinball-wasm` on this project's Gitea npm registry — see `package.json`.
|
||||
@@ -133,7 +152,7 @@ See `examples/basic/index.html` for a complete working page (loading progress, f
|
||||
## Known limitations
|
||||
|
||||
- Single-threaded only (no pthreads/SharedArrayBuffer) — a deliberate tradeoff, not a gap; see Roadmap.
|
||||
- No heavy plugin ecosystem (PinMAME/DOF/FlexDMD/etc.), and the modern `plugins/b2s` backglass plugin specifically assessed and declined for now (no plugin subsystem wired for this build, no test table to validate against) — 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.
|
||||
- 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.
|
||||
- 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.
|
||||
@@ -141,10 +160,11 @@ See `examples/basic/index.html` for a complete working page (loading progress, f
|
||||
|
||||
## Licensing
|
||||
|
||||
Visual Pinball itself is under a **mixed license**: the project has been migrating file-by-file from a legacy "old MAME"-like license to GPLv3+ since October 2020; each GPLv3+ file is marked `// license:GPLv3+` at its top, and any file without that marking remains under the legacy license. See `vendor/vpinball/LICENSE` (fetched by `setup.sh`) for the authoritative text — **do not treat this README as a substitute for reading it** before redistributing built artifacts. `libwinevbs` is LGPL-2.1 (Wine-derived) with a handful of small ATL header stubs of less certain provenance (see its own `README.md`). This project's own glue code (CMake integration, patches, npm wrapper) has no license conflict with either, but the combined built artifact's distribution terms are governed by vpinball's and libwinevbs's licenses, not just this repository's.
|
||||
Visual Pinball itself is under a **mixed license**: the project has been migrating file-by-file from a legacy "old MAME"-like license to GPLv3+ since October 2020; each GPLv3+ file is marked `// license:GPLv3+` at its top, and any file without that marking remains under the legacy license. See `vendor/vpinball/LICENSE` (fetched by `setup.sh`) for the authoritative text — **do not treat this README as a substitute for reading it** before redistributing built artifacts. `libwinevbs` is LGPL-2.1 (Wine-derived) with a handful of small ATL header stubs of less certain provenance (see its own `README.md`). PinMAME is under a similar **mixed license** to vpinball's own — migrating file-by-file from the same inherited "old MAME" license to 3-Clause BSD, with each migrated file marked `// license:BSD-3-Clause`; see `vendor/pinmame/LICENSE` for the authoritative text. Separately, and unrelated to source licensing: PinMAME requires ROM images dumped from the real arcade/pinball hardware to actually run a game, which this project does not and cannot bundle — see `pinball.loadRom()` above. This project's own glue code (CMake integration, patches, npm wrapper) has no license conflict with any of the above, but the combined built artifact's distribution terms are governed by vpinball's, libwinevbs's, and PinMAME's licenses, not just this repository's.
|
||||
|
||||
## Attribution
|
||||
|
||||
- [Visual Pinball](https://github.com/vpinball/vpinball) — the engine this project ports.
|
||||
- [libwinevbs](https://github.com/vpinball/libwinevbs) — the real VBScript interpreter (Wine-derived), compiled here to wasm32.
|
||||
- [Wine](https://www.winehq.org/) — original source of the VBScript/OLE Automation engine libwinevbs extracts and packages.
|
||||
- [PinMAME](https://github.com/vpinball/pinmame) — the real ROM/hardware emulation core `VPinMAME.Controller` wraps, compiled here to wasm32.
|
||||
|
||||
Reference in New Issue
Block a user