Skip to content

WasmPlayer: browser-WASM AOT game player#1782

Merged
alexwarren merged 9 commits into
mainfrom
wasm-player
Jun 29, 2026
Merged

WasmPlayer: browser-WASM AOT game player#1782
alexwarren merged 9 commits into
mainfrom
wasm-player

Conversation

@alexwarren

Copy link
Copy Markdown
Contributor

Summary

  • Adds WasmPlayer: a pure browser-WASM game player (browser-wasm target, AOT-compiled) that runs entirely as a static site with no server-side .NET required
  • Uses JSImport/JSExport for JS↔C# interop; shares PlayerCore, Engine, and all JS/CSS assets with the existing WebPlayer
  • IL trimming enabled with WasmPlayer.linker.xml preserving the Engine assembly (reflection-based type discovery)
  • Fixes two bugs discovered during AOT testing:
    • JSON serialization: replaced reflection-based JsonSerializer.Serialize(object) calls with a source-generated WasmJsonContext and a SerializeJsArg() helper that dispatches on concrete type (including QuestDictionary<string> and QuestList<string> for updateObjectLinks etc.)
    • Script error recursion: RunScriptAsync's catch block calling PrintAsync could re-enter OutputText, which would throw the same exception and recurse until stack overflow; guarded with _reportingScriptError flag
  • Dev server (src/WasmPlayer/dev-server.mjs) sets required COOP/COEP headers for SharedArrayBuffer and supports --release flag
  • Removes 100ms artificial delay from sendCommand (now 0ms — one browser tick to clear the input, then WASM runs)

Test plan

  • dotnet build src/WasmPlayer/WasmPlayer.csproj (Debug)
  • dotnet build --configuration Release src/WasmPlayer/WasmPlayer.csproj (AOT)
  • node src/WasmPlayer/dev-server.mjs --release → open http://localhost:5175/?game=/examples/simple.aslx
  • Game loads and room description displays
  • Commands process correctly (examine, go, etc.)
  • Object hyperlink menus show verb list on click
  • dotnet test --configuration Release passes

🤖 Generated with Claude Code

alexwarren and others added 9 commits June 28, 2026 16:46
- New `src/WasmPlayer/` project targeting `browser-wasm` with `[JSExport]`
  entry points for game control and `[JSImport]` callbacks for all UI events
- Move `player.js` from `WebPlayer/wwwroot/` into `PlayerCore/Resources/`
  so it's shared between WebPlayer and WasmPlayer; update Game.razor src path
- `index.html` embeds the playercore.htm UI inline for a standalone AppBundle
- `wasm-player.js` replaces `playerweb.js` with WASM bridge calls behind the
  same `WebPlayer.*` surface API that playercore.js and player.js already use

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The publish output places dotnet.js at the root of the publish directory,
not under _framework/ (that layout is only in AppBundle, used by WebEditor).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Content items with CopyToPublishDirectory go to the publish/ staging dir, not
the AppBundle. Use an AfterTargets="_WasmGenerateAppBundle" target to copy
player JS/CSS/HTML and lib/ assets directly to WasmAppDir (the AppBundle).
Also fix dotnet.js import to use ./_framework/ (AppBundle layout).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Engine's reflection-based type discovery (Classes.GetImplementations +
Activator.CreateInstance) is incompatible with the IL trimmer — trimming
removes constructors that are only reached at runtime via Assembly.GetTypes().
Disable PublishTrimmed until the Engine gets explicit trimmer roots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The package was never used in PlayerCore code. Its .NET Standard 2.0 assemblies
reference System.Runtime.InteropServices.RuntimeInformation via token lookups
that fail at load time in the browser-wasm runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
node src/WasmPlayer/dev-server.mjs starts a local server at :5175 that
serves the Debug AppBundle with Cross-Origin-Opener-Policy and
Cross-Origin-Embedder-Policy headers required by the .NET WASM runtime.
Also serves examples/ at /examples/* for easy game testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…script error recursion

- Enable RunAOTCompilation and IL trimming in WasmPlayer.csproj
- Add WasmPlayer.linker.xml to preserve Engine assembly (uses reflection-based type discovery)
- Replace reflection-based JsonSerializer calls with source-generated WasmJsonContext, and add
  SerializeJsArg() helper that dispatches on concrete type for object[] JS arguments (including
  QuestDictionary<string> and QuestList<string> passed to JS.updateObjectLinks etc.)
- Fix infinite recursion in WorldModel.RunScriptAsync: the catch block calling PrintAsync() could
  re-enter OutputText, which would throw the same exception and recurse until stack overflow;
  guard with _reportingScriptError flag to skip the PrintAsync call on re-entry
- paper.js null guard in playercore.js (paper.view may be null if canvas is missing)
- Dev server --release flag support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- player.js sendCommand: reduce setTimeout from 100ms to 0ms — yields one
  browser tick so the input clears visually before WASM execution begins,
  without the previous artificial 100ms delay
- CLAUDE.md: add WasmPlayer build and dev server commands; correct
  architecture entry (browser-wasm/AOT, not Blazor WebAssembly); expand
  WasmPlayer description with trimming/linker details
- Delete docs/async-ncalc-wasm-plan.md (implementation complete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…index.html

Fetch playercore.htm at runtime (in parallel with the WASM runtime load via
Promise.all) and inject it into the body, so playercore.htm remains the single
source of truth. Add playercore.htm to the CopyPlayerAssetsToAppBundle target
so it lands in the AppBundle. Move addPaperScript() from an inline script in
index.html into wasm-player.js where it belongs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alexwarren alexwarren merged commit 75e17cc into main Jun 29, 2026
3 checks passed
@alexwarren alexwarren deleted the wasm-player branch June 29, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant