Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

Quest Viva is an open-source system for creating and playing text adventure games. It's a .NET 10.0 C# application, the modern successor to Quest 5. The main deliverable is a web-based game player built with ASP.NET Core and Blazor.
Quest Viva is an open-source system for creating and playing text adventure games. It's a .NET 10.0 C# application, the modern successor to Quest 5. There are two player implementations: **WebPlayer** (ASP.NET Core + Blazor Server, Docker-deployed) and **WasmPlayer** (pure browser-WASM, AOT-compiled, no server required — the long-term direction).

## Build & Test Commands

```bash
# Build
# Build (full solution)
dotnet build --configuration Release

# Run all tests
Expand All @@ -21,8 +21,19 @@ dotnet test tests/EngineTests
# Run a specific test
dotnet test tests/EngineTests --filter "FullyQualifiedName~TestMethodName"

# Run with Docker
# Run WebPlayer with Docker
docker compose up --build # WebPlayer on http://localhost:8080

# Build WasmPlayer (Debug — fast interpreter mode)
dotnet build src/WasmPlayer/WasmPlayer.csproj

# Build WasmPlayer (Release — AOT compiled, ~15s)
dotnet build --configuration Release src/WasmPlayer/WasmPlayer.csproj

# Run WasmPlayer dev server (requires COOP/COEP headers for SharedArrayBuffer)
node src/WasmPlayer/dev-server.mjs # Debug build
node src/WasmPlayer/dev-server.mjs --release # Release/AOT build
# Open: http://localhost:5175/?game=/examples/simple.aslx
```

Tests use MSTest with Moq (mocking) and Shouldly (assertions).
Expand All @@ -45,7 +56,7 @@ The solution (`QuestViva.sln`) has a layered architecture:

```
WebPlayer (ASP.NET Core + Blazor Server) ─┐
WasmPlayer (Blazor WebAssembly) ─┤
WasmPlayer (browser-wasm, AOT) ─┤
├─► PlayerCore ─► Engine ─► Utility ─► Common
EditorCore ─────────────────────────────────┘ │
└─► Legacy
Expand All @@ -60,7 +71,7 @@ EditorCore ───────────────────────
- **EditorCore** — Game editor logic (non-UI)
- **Legacy** — Quest 4 (and earlier) backward-compatibility layer with embedded `.lib`/`.dat` files
- **WebPlayer** — ASP.NET Core web app with Blazor Razor components (`Game.razor`, `Slots.razor`, debugger)
- **WasmPlayer** — WebAssembly variant of the player
- **WasmPlayer** — Pure browser-WASM player (`browser-wasm` target, AOT-compiled). Uses `JSImport`/`JSExport` for JS interop. Serves as a static site with no server-side .NET required. IL trimming is enabled; `WasmPlayer.linker.xml` preserves the Engine assembly (which uses reflection-based type discovery).

**Test projects in `tests/`:** EngineTests, PlayerCoreTests, EditorCoreTests, UtilityTests, LegacyTests

Expand Down
15 changes: 15 additions & 0 deletions QuestViva.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebPlayerTests", "tests\Web
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmEditor", "src\WasmEditor\WasmEditor.csproj", "{A84D0E9B-E449-456B-B82E-C83FA68DE623}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmPlayer", "src\WasmPlayer\WasmPlayer.csproj", "{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -218,6 +220,18 @@ Global
{A84D0E9B-E449-456B-B82E-C83FA68DE623}.Release|x86.Build.0 = Release|Any CPU
{A84D0E9B-E449-456B-B82E-C83FA68DE623}.Release|x64.ActiveCfg = Release|Any CPU
{A84D0E9B-E449-456B-B82E-C83FA68DE623}.Release|x64.Build.0 = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|x86.ActiveCfg = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|x86.Build.0 = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Debug|x64.Build.0 = Debug|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|Any CPU.Build.0 = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|x86.ActiveCfg = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|x86.Build.0 = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|x64.ActiveCfg = Release|Any CPU
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -237,6 +251,7 @@ Global
{CB4137F3-BE35-4A58-9552-B80E578BB1C0} = {DE73B1F0-E136-444D-97EF-B31B1592532F}
{5F894C2F-4251-4594-B069-C1818D1BCD00} = {DE73B1F0-E136-444D-97EF-B31B1592532F}
{A84D0E9B-E449-456B-B82E-C83FA68DE623} = {5FFA7F3F-12FE-4727-9359-3D493A4E9F86}
{C4E7B8F2-3A91-4D5E-B6A0-89C3F1D2E456} = {5FFA7F3F-12FE-4727-9359-3D493A4E9F86}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {18B8911F-786E-48E1-8F32-208060A4A2C6}
Expand Down
220 changes: 0 additions & 220 deletions docs/async-ncalc-wasm-plan.md

This file was deleted.

15 changes: 14 additions & 1 deletion src/Engine/WorldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public partial class WorldModel : IGame, IGameDebug
private int _pendingCallbackCount;
private readonly List<(IScript Script, Context Context)> _onReadyQueue = [];

private bool _reportingScriptError;

private Walkthroughs? _walkthroughs;

static WorldModel()
Expand Down Expand Up @@ -1020,7 +1022,18 @@ internal Task RunScriptAsync(IScript script, Context c)
}
catch (Exception ex)
{
await PrintAsync("Error running script: " + Utility.SafeXML(ex.Message));
if (!_reportingScriptError)
{
_reportingScriptError = true;
try
{
await PrintAsync("Error running script: " + Utility.SafeXML(ex.Message));
}
finally
{
_reportingScriptError = false;
}
}
LogException(ex);
}

Expand Down
2 changes: 1 addition & 1 deletion src/PlayerCore/PlayerCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\grid.js"/>
<EmbeddedResource Include="Resources\player.js"/>
<EmbeddedResource Include="Resources\lib\images\ui-bg_flat_0_aaaaaa_40x100.png"/>
<EmbeddedResource Include="Resources\lib\images\ui-bg_flat_55_fbec88_40x100.png"/>
<EmbeddedResource Include="Resources\lib\images\ui-bg_glass_75_d0e5f5_1x400.png"/>
Expand All @@ -42,7 +43,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.JSInterop" Version="10.0.3"/>
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,9 @@ function sendCommand(text, metadata) {
canSendCommand = false;
markScrollPosition();

// TODO: See if setTimeout is still needed here
window.setTimeout(async function () {
await WebPlayer.sendCommand(text, getTickCountAndStopTimer(), metadata);
}, 100);
window.setTimeout(function () {
WebPlayer.sendCommand(text, getTickCountAndStopTimer(), metadata);
}, 0);
}

function ASLEvent(event, parameter) {
Expand Down
Loading
Loading