Next's fallback-metrics table has no entry for this (newer, merged variable-font) family name, so the CLS-reducing fallback override it tries to generate always fails with a build warning. Correctly suppresses it under webpack; Turbopack (this project's default builder) doesn't yet honor the flag the same way, so the warning still prints there today, but the setting is still the semantically correct declaration of intent and forward-compatible. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hQoM3jJT1Lx7CMTciMzvD
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
.vpxtable — drag a file onto the landing page, or click to browse. - 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 |
|---|---|
| Left Shift | Left flipper |
| Right Shift | Right flipper |
| Enter | Plunger |
| 1 | Start game |
On touch devices, an on-screen control overlay appears automatically once a table is running.
Limitations
Inherited from the underlying engine:
- Only self-contained
.vpxtables are supported — tables that reference an external.vbsscript override or aMusic/folder won't load correctly. - No PinMAME, DOF, or FlexDMD plugin support.
- 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.
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.