valknarandClaude Sonnet 5 44504c718d Fix eject hanging the tab on ROM-based tables
Bump @valknar/vpinball-wasm to 0.3.3, which fixes a genuine infinite
busy-wait in the PinMAME plugin's Controller::Stop() under Emscripten
(no OS thread ever clears the flag it spins on) — this fired from
dispose()'s synchronous script Exit event on any ROM-based table,
hanging the tab permanently.

Sequence unmount cleanup as stop() then two animation frames then
dispose(), instead of dispose() alone, matching the consumer pattern
the engine fix expects (stop() only takes effect on its next internal
step, so disposing immediately after can still race it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:27:35 +02:00

vpinball

Play real .vpx pinball tables in your browser. Drop in your own table or try the bundled demo — no plugins, no native install. Powered by @valknar/vpinball-wasm, a WebAssembly build of the real Visual Pinball engine (real physics, real VBScript table scripting, real WebGL2 rendering).

Features

  • Play any self-contained .vpx table — drag a file onto the landing page, or click to browse.
  • ROM support for real-hardware ("SS") tables — attach the table's PinMAME ROM .zip alongside the .vpx upload; most tables don't need this.
  • A local table library — uploads are saved in the browser (IndexedDB) so you don't have to re-upload the same table (and its ROM) on your next visit. Nothing leaves your browser; there's no server-side storage.
  • Demo mode — a bundled table plays instantly with no upload required.
  • Fullscreen, a live FPS/stats readout, and a game info panel (table name, size, load time) in an in-game HUD styled after a pinball cabinet's coin door.
  • Installable as a PWA — add it to your home screen or desktop; the engine is cached after your first game so it's available offline afterward.
  • Deployable as a static site via Docker Compose — no Node server at runtime, just nginx serving prebuilt files.

Prerequisites

  • Node.js ≥ 20, pnpm ≥ 11
  • Docker + Docker Compose, for the containerized deploy

Install

pnpm install --frozen-lockfile --trust-lockfile

vpinball-wasm is published to a private Gitea npm registry (https://dev.pivoine.art/api/packages/valknar/npm/) under the @valknar scope. .npmrc maps that scope to the registry; the package is publicly readable, so no auth token is needed to install it.

To bump the engine to a newer published version:

pnpm add @valknar/vpinball-wasm@<version>

Development

pnpm dev

pnpm dev copies the engine's build output into public/vendor/vpinball-wasm/ first (via the predev script) — it's gitignored and regenerated from node_modules, so this only needs pnpm install to have completed. Re-run pnpm run copy-engine-assets by hand if you update the dependency without restarting the dev server.

The dev server runs at http://localhost:3000.

Build

pnpm build

Produces a fully static export in out/ (next.config.ts sets output: "export") — no Node server needed to serve it.

Docker deploy

No secrets or registry authentication are required to build the image.

The compose file is set up for a Coolify/Traefik deployment: it joins an external Docker network and routes entirely through Traefik labels, with no host port published. Copy .env.example to .env and set:

  • TRAEFIK_ENABLED=true
  • TRAEFIK_HOST — the domain to route to this service
  • NETWORK_NAME — the external Docker network Traefik and this service both join (Coolify provisions this automatically for projects deployed through it)
docker compose up -d --build

There's no local-only mode — without Traefik routing to it, the container isn't reachable from the host. To preview the static build locally instead, run pnpm build and serve out/ with any static file server (e.g. npx serve out).

PWA

The app can be installed from the browser's install prompt. A hand-rolled service worker (public/sw.js) precaches the app shell; the engine's .wasm/.data files (~34 MB) are cached on first visit to /play, not upfront, so installing the app doesn't force a large download before anyone has actually played. After that first visit, offline reloads work.

When you ship a release that changes the app shell or updates the vendored engine build, bump the cache version so returning visitors actually get the update:

pnpm run sw:bump

Controls

Key Action
4 Insert coin
1 Start game
Enter Launch ball
Left Shift Left flipper
Right Shift Right flipper

On touch devices, an on-screen control overlay appears automatically once a table is running.

Limitations

Inherited from the underlying engine:

  • Only self-contained .vpx tables are supported — tables that reference an external .vbs script override or a Music/ folder won't load correctly.
  • PinMAME (VPinMAME.Controller) ROM-driven emulation runs, but on-screen DMD/backglass output and hands-on switch/solenoid/scoring behavior aren't yet confirmed end-to-end — see the engine's own README for the current state. No DOF, FlexDMD, or backglass (.directb2s) plugin support. ROM files are copyrighted; only supply ones you're legally entitled to use.
  • The ROM gameName PinMAME needs is derived from the uploaded zip's filename (e.g. hvymetal.ziphvymetal) — rename the zip to match if a table doesn't recognize its ROM.
  • Single-threaded (no SharedArrayBuffer) — performance depends on your device's single-core WebGL2 throughput.
  • Initial engine download is ~34 MB (cached after the first visit).
  • Gamepad input and DMD rendering are implemented but not yet confirmed on real hardware/tables.
  • Raw cabinet hardware input (real nudge/plunger boards) has no browser equivalent.
  • The table library lives in the browser's IndexedDB storage — clearing site data removes it, and it doesn't sync across browsers/devices.

License

This app's own source has no license file yet — add one before distributing it. Separately, the built output bundles the @valknar/vpinball-wasm engine and its default table, which redistribute Visual Pinball source under a mixed legacy/GPLv3+ license (migrating file-by-file to GPLv3+ since October 2020) — see that package's own LICENSE for the authoritative terms before distributing a build.

S
Description
Play real .vpx pinball tables in your browser. Drop in your own table or try the bundled demo — no plugins, no native install.
https://vpinball.pivoine.art
Readme
356 KiB
Languages
TypeScript 85.6%
JavaScript 11.2%
CSS 2.3%
Dockerfile 0.9%