fix(web): resolve wasm32 runtime panics; game boots and renders in Firefox
Build and Deploy / build-and-push (push) Failing after 1m6s

Fixes found while testing the Bevy WASM build in a real browser:

1. chrono wasmbind: add `wasmbind` feature to workspace chrono dep so
   Local::now()/Utc::now() use js-sys::Date on wasm32 (previously
   fell through to std::time::SystemTime which panics on wasm32).

2. std::time::SystemTime: replace all remaining direct SystemTime::now()
   calls (4 sites across game_plugin, difficulty_plugin, time_attack_plugin,
   solitaire_data/storage) with chrono::Utc::now() which is wasm32-safe.

3. user_dir: return empty PathBuf (instead of panicking) when data_dir()
   is None on wasm32; there is no filesystem in the browser so user themes
   are unsupported and a benign empty path is correct.

4. ThemeRegistryPlugin: gate build_registry_on_startup to non-wasm32
   (the filesystem scan for user themes has nothing to scan in the browser;
   only the bundled embedded themes are available).

5. AssetMetaCheck::Never: configure AssetPlugin in solitaire_web to skip
   `.meta` sidecar fetches — we don't ship .meta files, so the default
   AssetMetaCheck::Always produced a 404 flood on every card/background asset.

Result: `http://localhost:<port>/play` boots in Firefox with zero errors
and renders the full Bevy game — home screen, onboarding modal, HUD all
visible. Assets load correctly from /assets/. Chromium has a separate
wgpu-27/ANGLE/GLES shader translation bug (not in our code); Firefox works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-01 14:16:19 -07:00
parent f464aab543
commit a92ac066a6
10 changed files with 72 additions and 56 deletions
+20 -11
View File
@@ -9,6 +9,7 @@
//! - Storage is handled automatically by `WasmStorage` (localStorage-backed),
//! wired by `CoreGamePlugin` via `default_storage_backend()`.
use bevy::asset::AssetMetaCheck;
use bevy::prelude::*;
use bevy::window::{Window, WindowPlugin};
use solitaire_data::LocalOnlyProvider;
@@ -21,19 +22,27 @@ pub fn start() {
App::new()
.add_plugins(
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
// Bind to the existing <canvas id="bevy-canvas"> in play.html.
// Without this, Bevy appends its own canvas to <body>.
canvas: Some("#bevy-canvas".into()),
// Let CSS size the canvas; Bevy follows the element's size.
fit_canvas_to_parent: true,
// Prevent the browser stealing keyboard events and scroll.
prevent_default_event_handling: true,
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
// Bind to the existing <canvas id="bevy-canvas"> in play.html.
// Without this, Bevy appends its own canvas to <body>.
canvas: Some("#bevy-canvas".into()),
// Let CSS size the canvas; Bevy follows the element's size.
fit_canvas_to_parent: true,
// Prevent the browser stealing keyboard events and scroll.
prevent_default_event_handling: true,
..default()
}),
..default()
})
// Bevy's default AssetPlugin fetches a `.meta` sidecar file for
// every asset before loading the asset itself. We don't ship
// `.meta` files, so skip the check to avoid a flood of 404s.
.set(AssetPlugin {
meta_check: AssetMetaCheck::Never,
..default()
}),
..default()
}),
)
// LocalOnlyProvider disables cloud sync — correct for the web build
// since SyncPlugin is cfg-gated out on wasm32 anyway.