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:
2026-08-23 12:09:35 +02:00
co-authored by Claude Sonnet 5
parent d91ef0c96d
commit 9479cea280
13 changed files with 756 additions and 8 deletions
+16 -1
View File
@@ -48,6 +48,8 @@
<div id="progress-track"><div id="progress-bar"></div></div>
<div id="file-picker">
<label>Optional: play your own table (self-contained .vpx only) <input type="file" id="table-file" accept=".vpx" /></label>
<br />
<label>Optional: ROM zip for a real-hardware table (e.g. hvymetal.zip) <input type="file" id="rom-file" accept=".zip" /></label>
</div>
<button id="start-button" disabled>Loading...</button>
</div>
@@ -62,6 +64,7 @@
const progressBar = document.getElementById('progress-bar');
const startButton = document.getElementById('start-button');
const fileInput = document.getElementById('table-file');
const romInput = document.getElementById('rom-file');
const fullscreenButton = document.getElementById('fullscreen-button');
let uploadedTableData;
@@ -70,6 +73,14 @@
if (file) uploadedTableData = await file.arrayBuffer();
});
// ROM zips are copyrighted - only pick one you're legally entitled to
// use. Named after its own short ROM/game name by convention (e.g.
// hvymetal.zip), which loadRom() below relies on to derive gameName.
let uploadedRomFile;
romInput.addEventListener('change', () => {
uploadedRomFile = romInput.files?.[0];
});
const pinball = await loadPinball({
canvas,
baseUrl: '../../dist',
@@ -82,10 +93,14 @@
startButton.disabled = false;
startButton.textContent = 'Start';
startButton.addEventListener('click', () => {
startButton.addEventListener('click', async () => {
if (uploadedTableData) {
pinball.loadTable(uploadedTableData);
}
if (uploadedRomFile) {
const gameName = uploadedRomFile.name.replace(/\.zip$/i, '');
pinball.loadRom(gameName, await uploadedRomFile.arrayBuffer());
}
pinball.start();
overlay.classList.add('hidden');
fullscreenButton.classList.remove('hidden');