Commit Graph
31 Commits
Author SHA1 Message Date
valknar 10f32789e6 Trigger CI to verify simplified embedded-cache config
CI / Build wasm engine (push) Successful in 8m48s
2026-08-24 12:01:25 +02:00
valknar bf818bdd29 Trigger CI to verify gitea/runner migration + cache config fix
CI / Build wasm engine (push) Canceled after 4m37s
2026-08-24 11:56:29 +02:00
valknarandClaude Sonnet 5 4946dd2033 Trigger CI again after force-recreating the stale runner daemon
CI / Build wasm engine (push) Canceled after 1m16s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 11:43:24 +02:00
valknarandClaude Sonnet 5 7e5793bb17 Trigger CI to test the dedicated cache-server setup
CI / Build wasm engine (push) Canceled after 4m13s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 11:39:56 +02:00
valknarandClaude Sonnet 5 f52fa0cb79 Trigger CI with debug logging to capture the exact cache URL
CI / Build wasm engine (push) Successful in 11m17s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:54:35 +02:00
valknarandClaude Sonnet 5 9c8afb7541 Trigger CI again to inspect the live job container's network
CI / Build wasm engine (push) Canceled after 5m3s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:49:51 +02:00
valknarandClaude Sonnet 5 02f17165f2 Trigger CI to verify Gitea Actions cache fix (act_runner network/host config)
CI / Build wasm engine (push) Successful in 11m23s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:33:08 +02:00
valknarandClaude Sonnet 5 0f945ed68e Bump version to 0.3.3 for the dispose() hang fix
CI / Build wasm engine (push) Successful in 11m16s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:12:51 +02:00
valknarandClaude Sonnet 5 43216a408e Fix PinMAME busy-wait freeze on Controller.Stop() (dispose() hang)
Controller::Stop() (real desktop vpinball code, unmodified until now)
busy-waits for PinmameIsRunning() to clear after calling PinmameStop(),
in a sleep loop meant for a real OS thread to notice the quit flag and
finish stopping on its own. Under Emscripten there is no such thread -
PinmameStop() there only sets the quit flag - so the loop spun forever
on the single available thread, with nothing left to ever clear it.

This fires from Player::~Player()'s GameEvents_Exit script event
(controller.vbs's default Exit handler calls Controller.Stop), which
runs synchronously inside dispose() - hanging any consumer's teardown
for a ROM-based table, most visibly React StrictMode/unmount cleanup
calling stop() then dispose() shortly after.

Fixed by driving one more (now-instant, since the quit flag is already
set) PinmameEmscriptenStep() call directly under __EMSCRIPTEN__ instead
of busy-waiting - it runs cpu_post_run() and OnStateChange(0)
synchronously right there.

Also adds a "Stop & Dispose" button to the basic example, exercising
the same stop()-then-wait-two-frames-then-dispose() sequence consumers
use, to make this kind of regression visible without a separate app.

Confirmed fixed by hands-on testing: dispose() on a running ROM-based
table now returns immediately instead of hanging the tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 10:12:25 +02:00
valknarandClaude Sonnet 5 6ff116ba02 Bump version to 0.3.2 for the MsgBox fix
CI / Build wasm engine (push) Successful in 10m55s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 19:31:05 +02:00
valknarandClaude Sonnet 5 1d7c4e7b7d Implement real MsgBox() for VBScript table scripts (browser confirm/alert)
libwinevbs's __LIBWINEVBS__ build of Global_MsgBox() only logged the
prompt text and never set a return value, since it has no host UI to
show a dialog through. Any table script that branches on
MsgBox(...) = vbYes (a common pattern - e.g. core.vbs's own trough
ball-count dialog) always took the "no" branch, since the result
stayed at its zero-initialized default.

Adds a msgbox callback to libwinevbs_callbacks_t, wired on the
vpinball-wasm side to a real window.confirm()/alert() call. Both are
synchronous browser APIs that block JS execution and return a value
immediately, matching what VBScript's MsgBox() needs (its result is
used by the calling statement right away) - the only way to answer it
asynchronously would require Asyncify or a busy-wait poll loop, which
this project has deliberately avoided elsewhere.

Confirmed fixed by hands-on testing: a table's ball-count confirm
dialog now correctly returns Yes/No based on the user's click, instead
of always silently taking the "no" branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 19:30:25 +02:00
valknarandClaude Sonnet 5 c7f7cb6afa Bump version to 0.3.1 for the focus-loss freeze fix
CI / Build wasm engine (push) Successful in 10m50s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 15:17:21 +02:00
valknarandClaude Sonnet 5 99ae5b9eda Fix PinMAME busy-wait freeze on browser/window focus loss
core.vbs auto-wires Controller.Pause = True into every table's Paused
event, which Player::OnFocusChanged() fires on focus loss. PinMAME's
own pause handling in usrintrf.c is a "while (g_fPause) { ...;
draw_screen(); ... }" busy-wait written for a real-OS-thread host,
where a separate thread later flips g_fPause back to 0. Under our
single-threaded Emscripten cooperative-scheduling model there is no
second thread to clear it, so entering that loop spins forever at
100% CPU with the tab completely unresponsive.

cpu_run_emscripten_step() now checks g_fPause up front and skips
cpu_timeslice() (and therefore that loop) entirely while paused;
emulation simply doesn't advance until Controller.Pause is set back
to False and the step function is called again next frame.

Confirmed fixed by hands-on testing: unfocusing the browser during a
PinMAME ROM session no longer freezes the tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 15:15:48 +02:00
valknarandClaude Sonnet 5 5570955bc5 Fix patches/pinmame/0004: drop duplicate hunk conflicting with 0002
CI / Build wasm engine (push) Successful in 10m51s
0004 was generated via a plain `git diff` against pinmame's pristine
commit, which captured the *cumulative* diff for cpuexec.c - including
patch 0002's time_fence stub hunk, not just 0004's own new cpu_run
splitting code. Applying 0002 then 0004 in sequence (exactly what
setup.sh does) failed: 0004's duplicate hunk expected pre-0002 context
that no longer matched.

Regenerated by diffing the current tree against a baseline with 0001-0003
already applied (matching the vpinball/0006 patch's existing approach)
instead of against pristine directly. Verified: all 4 pinmame patches now
apply cleanly in sequence against a fresh pristine checkout, and the
result is byte-identical to the working tree that was actually built and
tested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
2026-08-23 12:47:51 +02:00
valknarandClaude Sonnet 5 6c8d08e618 Bump version to 0.3.0 for the PinMAME integration release
CI / Build wasm engine (push) Failing after 2m19s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
2026-08-23 12:38:16 +02:00
valknarandClaude Sonnet 5 a7b85ec8a4 Confirm real PinMAME ROM-driven emulation actually running
Tested pinball.loadRom() against a correctly-matched ROM zip for a real
community table (Heavy Metal Meltdown, Bally 1987): PinMAME loads the ROM,
and its own video/audio subsystems come up and run with no errors
(osd_create_display: 60.00 fps; real INT16/44100Hz audio format), not just
Controller object creation succeeding as before. Updates the README to move
this from "not yet validated" to confirmed, narrowing the remaining gap to
on-screen DMD/backglass output and hands-on switch/solenoid/scoring
behavior during actual play, which nobody has watched yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
2026-08-23 12:34:25 +02:00
valknarandClaude Sonnet 5 9479cea280 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
2026-08-23 12:09:35 +02:00
valknarandClaude Sonnet 5 d91ef0c96d Rename package to scoped @valknar/vpinball-wasm
CI / Build wasm engine (push) Successful in 9m24s
Reverts the earlier unscoping: an unscoped package on a Gitea npm
registry has no `.npmrc` mapping a consumer can use other than pinning
the exact tarball URL, since Gitea's registry doesn't proxy npmjs.org
and only scoped packages support the standard `@scope:registry=`
config. Scoping restores that: consumers add one `@valknar:registry=`
line and depend on a normal semver range.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hQoM3jJT1Lx7CMTciMzvD
2026-08-23 01:06:15 +02:00
valknarandClaude Sonnet 5 ad66bbf6be Rename package from @valknar/vpinball-wasm to vpinball-wasm
CI / Build wasm engine (push) Successful in 9m32s
Unscoped, matching this project's other Gitea-published package
(triggershell) - the registry is already scoped to the right owner via
publishConfig.registry's URL path (.../valknar/npm/), so an npm scope
on the package name itself is redundant. The old @valknar/vpinball-wasm
package published under the previous name is being removed manually.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 19:06:44 +02:00
valknarandClaude Sonnet 5 243c975cec Merge build and publish into one job, eliminating redundant CI builds
CI / Build wasm engine (push) Successful in 9m24s
The separate "publish" job had needs: build but never reused any of
build's output - it re-ran the entire checkout/cache/setup/build-deps/
build/wrapper pipeline from scratch, so every tagged release did two
full independent builds for no benefit. Gitea Actions doesn't support
upload-artifact@v4+/download-artifact@v4+ (GHESNotSupportedError),
which rules out the usual "build uploads, publish downloads" pattern
anyway, so the simplest fix is to run the build pipeline exactly once
and gate the three publish-only steps (version bump from tag, registry
auth, npm publish) behind `if: startsWith(github.ref, 'refs/tags/')`
at the step level instead of duplicating everything in a second job.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:50:12 +02:00
valknarandClaude Sonnet 5 536e09dff4 Bump version to 0.1.0 for the first release
CI / Build wasm engine (push) Successful in 9m17s
CI / Publish to npm registry (push) Canceled after 3m47s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:36:26 +02:00
valknarandClaude Sonnet 5 d22dc28af3 Fix CI: drop upload-artifact (unsupported on Gitea, unused downstream)
CI / Publish to npm registry (push) Canceled after 0s
CI / Build wasm engine (push) Canceled after 36s
actions/upload-artifact@v4+ refuses to run on Gitea (it's detected as
GHES, and v4's new backend API explicitly isn't supported there -
GHESNotSupportedError). Rather than pin back to the last GHES-compatible
v3.2.2, just remove the step: the publish job doesn't consume this
artifact - it does its own independent checkout+build - so it was only
ever a convenience for manually downloading dist/ from a CI run, not
something the pipeline depends on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:34:09 +02:00
valknarandClaude Sonnet 5 4f4c2b6b91 Fix CI: Gitea Actions cache keys never actually changed
CI / Publish to npm registry (push) Canceled after 0s
CI / Build wasm engine (push) Canceled after 5m11s
Confirmed via Gitea's own documentation (about.gitea.com's actions-cache
tutorial): Gitea Actions doesn't support the built-in hashFiles()
expression function GitHub Actions provides - it silently evaluates to
an empty string rather than erroring, so all three cache keys
(emsdk-${{hashFiles(...)}}, etc.) were actually just the constant
strings "emsdk-", "deps-wasm32-", "libwinevbs-wasm32-" on every run,
regardless of whether scripts/versions.sh or patches/ actually changed -
this is why caching "didn't work properly": correctness-wise it's worse
than no caching, since a stale cache from before a version/patch bump
would keep being reused indefinitely instead of invalidating.

Replaced with a plain sha256sum-based step that hashes the same inputs
by hand and exposes them via GITHUB_OUTPUT - no dependency on Gitea
gaining hashFiles() support, and verified locally to produce distinct,
non-empty hashes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:27:07 +02:00
valknarandClaude Sonnet 5 d7b95dbac8 Fix CI: install cmake/curl/python3, not just bison
CI / Build wasm engine (push) Failing after 8m31s
CI / Publish to npm registry (push) Skipped
The CI runner's base image doesn't ship cmake (build failed with
"cmake executable not found on PATH" during emcmake), and only bison
was ever explicitly installed - curl and python3 happened to work by
luck of the current runner image, not because the workflow guarantees
them. Install the full README-documented toolchain explicitly in both
the build and publish jobs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:22:00 +02:00
valknarandClaude Sonnet 5 ef20b0d6c3 Fix CI: vendor-fetch commit fails with no git identity configured
CI / Build wasm engine (push) Failing after 2m14s
CI / Publish to npm registry (push) Skipped
scripts/setup.sh's fetch_pinned() creates a fresh git repo per vendored
dependency and commits it with --author set, but --author alone doesn't
satisfy git's separate committer-identity requirement - it works on a
dev machine with ~/.gitconfig already set, but fails outright in a
clean CI container with no git identity anywhere (confirmed: Gitea
Actions failed at exactly this step with "Committer identity unknown").
Fixed by scoping user.name/user.email via -c flags to just this commit
invocation, verified to succeed even with HOME pointed at an empty
directory and no inherited git env vars.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:17:41 +02:00
valknarandClaude Sonnet 5 4638c06b68 Phase E/I: expose evalScript() as a permanent debug API, finalize README
Promotes vpinball_wasm_eval_script from a one-off validation hack to a
supported PinballInstance.evalScript() method (runs arbitrary VBScript
against the live table via the interpreter's own debug-console entry
point) - useful standalone, and it's what confirms the DMD script API
rides the same execution path already proven by real flipper input.

Rewrites the README's Status/Roadmap/Known limitations to reflect
tonight's actual, hands-on-confirmed state: keyboard input, audio, and
VBScript-driven gameplay all verified end-to-end (not just code
review); two more real engine bugs found and fixed (GL back buffer
sized in logical instead of device pixels; back buffer never resynced
on resize, breaking fullscreen); the default table swap and why;
B2S backglass explicitly assessed and declined for now, with the
concrete reasons (no plugin subsystem wired for this build, no test
table to validate against) rather than left as a vague TODO.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:14:49 +02:00
valknarandClaude Sonnet 5 b8bf9f1510 Fix back buffer not resizing on fullscreen toggle
Window::OnResized() (called for every SDL window-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, whose stale size kept driving glViewport() - so toggling
fullscreen resized the canvas but rendering stayed pinned to the old,
smaller viewport. Mirrors the resize handling the BGFX backend already
does explicitly elsewhere in RenderDevice.cpp.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:03:08 +02:00
valknarandClaude Sonnet 5 7c63f01527 Fix GL viewport sizing and ship a real playable default table
The OpenGL back-buffer render target was created with the window's
logical/CSS size instead of its device-pixel size, so on any browser
tab with devicePixelRatio != 1 the GL viewport only covered a fraction
of the canvas's actual backing buffer (rendering anchored bottom-left,
matching GL's viewport origin) - confirmed against a live repro and
fixed to match the convention already used by the BGFX backend
elsewhere in the same file.

Also swap the bundled default table from test000-default-table.vpx
(upstream's rendering/component regression-test fixture, which has no
gameplay script) to exampleTable.vpx - a genuine playable demo table
with working flippers, slingshots, bumpers, targets and a plunger -
confirmed keyboard input and audio now work end-to-end through it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:56:51 +02:00
valknar 487ca40a44 Phase D/F/H: embedding shell - loading progress, fullscreen, file upload, touch controls
package/src/index.ts: fixes a real pre-existing bug (start()/loadTable()
never actually called vpinball_wasm_start with a table path argument),
adds byte-level download progress (onProgress) and requestFullscreen().

package/src/touch-controls.ts: new on-screen virtual flipper/plunger/start
button overlay that synthesizes the actual default keyboard scancodes
(SDL_SCANCODE_LSHIFT/RSHIFT/RETURN, read from InputManager.cpp) as real
KeyboardEvents dispatched on window, matching SDL3's Emscripten keyboard
target - keeps the native input path completely unmodified.

examples/basic/index.html: a real demo page - progress bar, a user-gesture
"Start" button (required for both audio autoplay and fullscreen), a file
picker for self-contained .vpx uploads, and the touch overlay shown on
touch-capable devices.

scripts/build.sh: two real bugs found and fixed via actual browser testing:
- MODULARIZE=1 without EXPORT_ES6=1 produces a classic script, not an ES
  module with a default export - `await import(...)).default` was always
  undefined. Fixes with -sEXPORT_ES6=1.
- --closure 1 silently stripped FS.writeFile/readFile/mkdir down to just
  low-level node ops, breaking runtime table loading with no compile-time
  warning. Dropped until root-caused; -O2 alone is kept.

Verified end-to-end in Chrome: full load->progress->start flow with no
errors, fullscreen actually engaging (document.fullscreenElement true),
and a file-picker-selected table loading correctly (LoadGameFromFilename
/tables/uploaded.vpx in the boot log, followed by a normal render).

README updated to reflect what's now validated vs. still open (real-device
input/audio confirmation remains the main open item).
2026-08-22 16:45:04 +02:00
valknar a5e5b6f34a Phase A/B: fix the game loop and trim the asset payload
Phase A (the hard blocker): the engine now plays a real table live in
the browser via a real per-frame game loop, not just a static render.

Two real, previously-unknown upstream bugs found and fixed along the way:
- RenderDevice::WaitForVSync() unconditionally spawned a real std::thread
  every frame, even on __STANDALONE__ builds - a hard crash under
  Emscripten's single-threaded runtime (0003).
- The desktop game loop is a blocking native while loop with manual
  uSleep throttling, incompatible with a single-threaded WASM main
  thread. Adds Player::EmscriptenStepFrame() (one frame, no internal
  loop) driven by emscripten_set_main_loop, plus new JS-callable
  lifecycle entry points (vpinball_wasm_start/stop/dispose in the new
  src/core/EmscriptenBridge.cpp) that bypass the desktop main()/WinMain()
  chain entirely, since that chain assumes the process runs exactly one
  table to completion then exits (0004).

Verified end-to-end in Chrome: real .vpx load, real shader compilation,
real physics/script engine init, a real running frame loop (observed
advancing), and a clean stop() -> ~Player() teardown mid-session with
no crash or hang.

Phase B: trims the preloaded asset payload from ~51MB to ~11MB by
excluding editor-only bundled example tables and a Monaco code editor
never used by the player runtime, and adds real link-time optimization
(-O2 --closure 1) and --use-preload-cache for repeat visits.
2026-08-22 16:06:17 +02:00
valknar 0a63835689 Scaffold vpinball-wasm: Emscripten port of Visual Pinball
CI / Build wasm engine (push) Failing after 3m30s
CI / Publish to npm registry (push) Skipped
Build scripts, CMake/source patches, npm packaging, and a Gitea CI
workflow to compile the real Visual Pinball engine (SDL3 + WebGL2 +
libwinevbs for real VBScript) to WebAssembly.

The patches are validated end-to-end: the patched engine boots in a
real browser, loads a real .vpx table, compiles its real GLSL shaders,
computes environment map radiance, initializes physics, and starts the
VBScript engine, before hanging on the one deliberately-deferred piece
of work (game loop rewrite around emscripten_set_main_loop), documented
in the README's roadmap.
2026-08-22 14:30:14 +02:00