valknarandClaude Sonnet 5 35fb68c916 Add ROM upload, local table library, engine bump, and UX/responsiveness fixes
- Bump @valknar/vpinball-wasm to 0.3.2 (PinMAME ROM support, MsgBox fix)
- Add optional PinMAME ROM .zip upload alongside .vpx tables
- Persist uploaded tables/ROMs in IndexedDB so users don't have to
  re-upload on later visits, with a library list on the landing page
- Fix Insert Coin key mismatch with the engine's real coin-door gating
  (now bound to 4, matching the shared table scripts) and surface it
  in the controls legend and "Tap to Start" hint
- Make the landing page, staging panel, and in-game HUD responsive for
  portrait phone widths; drop the landscape hint since portrait plays fine
- Replace the separate STATS HUD toggle with an FPS row in the info panel

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9JmzRBZenapVkFWjw9jmG
2026-08-23 20:15:41 +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

docker compose build
docker compose up -d

Serves the static build via nginx on http://localhost:8080. No secrets or registry authentication are required to build the image.

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%