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).
This commit is contained in:
2026-08-22 16:45:04 +02:00
parent a5e5b6f34a
commit 487ca40a44
6 changed files with 352 additions and 46 deletions
+29 -8
View File
@@ -9,22 +9,43 @@ export interface LoadPinballOptions {
/** Canvas the engine renders into via SDL3's WebGL2 backend. */
canvas: HTMLCanvasElement;
/**
* Raw bytes of a .vpx table file to load on startup. If omitted, the
* bundled default table (package/assets/test000-default-table.vpx) is
* used instead.
* Raw bytes of a self-contained .vpx table file to play instead of the
* bundled default table. Real-world tables that reference an external
* .vbs script override or a Music/ folder are not supported this way -
* see the README's "Browser file-loading UX" section.
*/
tableData?: ArrayBuffer;
/** Base URL to fetch dist/vpinball.wasm (and .data, if present) from. */
/** Base URL to fetch dist/vpinball.wasm/.data (and vpinball.js) from. */
baseUrl?: string;
/**
* Called as the wasm binary and preloaded asset package download,
* with a 0-1 fraction of bytes received. Not called at all if the
* browser doesn't support streaming fetch progress (falls back to
* Emscripten's own default loading behavior with no progress callback).
*/
onProgress?: (fraction: number) => void;
}
export interface PinballInstance {
/** Load a different .vpx table at runtime, replacing the current one. */
/**
* Stage a different self-contained .vpx table to play on the *next*
* start() call. Does not affect an already-running session - table
* switching mid-session isn't supported; call stop() first (a fresh
* loadPinball() call is the supported way to load a genuinely new
* session with a different table).
*/
loadTable(vpxBytes: ArrayBuffer): void;
/** Start (or resume) the simulation's main loop. */
/** Start the simulation's main loop. No-op if already running. */
start(): void;
/** Pause the simulation's main loop. */
/** Stop the simulation; the engine tears down (script Exit event, settings save) on its next step. */
stop(): void;
/** Tear down the instance and free its WebAssembly memory. */
/** Tear down the instance immediately and free its WebAssembly memory. */
dispose(): void;
/**
* Request fullscreen on the canvas via the standard Fullscreen API.
* Must be called from within a user gesture (e.g. a click handler) -
* browsers reject fullscreen requests otherwise. Returns the promise
* from the underlying requestFullscreen() call.
*/
requestFullscreen(): Promise<void>;
}