Files
yaze/docs/internal/wasm/playbook.md

26 KiB
Raw Blame History

WASM Antigravity Playbook

Status: ACTIVE Owner: docs-janitor Created: 2025-11-24 Last Reviewed: 2025-11-24 Next Review: 2025-12-08 Coordination: coordination-board entry


Purpose

Canonical entry point for Antigravity/Gemini when operating the yaze WASM build. This document consolidates build instructions, AI integration notes, filesystem setup, and debug workflows so agents can:

  1. Build and serve the WASM app reliably
  2. Load ROMs safely with visual progress feedback
  3. Debug editor rendering using the yazeDebug API
  4. Troubleshoot WASM-specific issues quickly

For detailed build troubleshooting, API reference, and roadmap updates, see the reference docs listed at the end.


Quick Start

Prerequisites

Emscripten SDK must be installed and activated:

source /path/to/emsdk/emsdk_env.sh
which emcmake  # verify it's available

Build Commands

# Full debug build (SAFE_HEAP + ASSERTIONS for debugging)
./scripts/build-wasm.sh debug

# Clean rebuild (after CMakePresets.json changes)
./scripts/build-wasm.sh debug --clean

# Incremental debug build (skips CMake cache, 30-60s faster after first build)
./scripts/build-wasm.sh debug --incremental

# Release build (optimized for production)
./scripts/build-wasm.sh release

# Serve locally (uses custom server with COOP/COEP headers)
./scripts/serve-wasm.sh --force 8080        # Release (default)
./scripts/serve-wasm.sh --debug --force 8080 # Debug build

# Manual CMake (alternative)
cmake --preset wasm-debug
cmake --build build-wasm --parallel

Important:

  • Always serve from dist/, not bin/. The serve script handles this automatically.
  • The serve script now uses a custom Python server that sets COOP/COEP headers for SharedArrayBuffer support.
  • Use --clean flag after modifying CMakePresets.json to ensure changes take effect.

Attach Antigravity + Initial Setup

  1. Build and serve (see Quick Start above).
  2. Whitelist in Antigravity: Allow http://127.0.0.1:8080 (or your chosen port).
  3. Open the app in Antigravity's browser and verify initialization:
    // In DevTools console, run these checks:
    window.Module?.calledRun                    // should be true
    window.z3edTerminal?.executeCommand('help') // prints command list
    toggleCollabConsole()                       // opens collab pane if needed
    
  4. Focus terminal: Press backtick or click the bottom terminal pane.
  5. Clean session (if needed): localStorage.clear(); sessionStorage.clear(); location.reload();

Step-by-Step Workflow for AI Agents (Gemini)

This section provides explicit, ordered steps for AI agents to navigate the application.

Phase 1: Verify Module Ready

Before doing anything, verify the WASM module is initialized:

// Step 1: Check module ready
const moduleReady = window.Module?.calledRun === true;
const apiReady = window.yaze?.control?.isReady?.() === true;
console.log('Module ready:', moduleReady, 'API ready:', apiReady);

// If not ready, wait and retry (poll every 500ms)
// Expected: both should be true within 5 seconds of page load

Phase 2: ROM Loading (User-Initiated)

CRITICAL: ROM loading MUST be initiated by the user through the UI. Do NOT attempt programmatic ROM loading.

Step-by-step for guiding user:

  1. Locate the Open ROM button: Look for the folder icon (📁) in the top navigation bar
  2. Click "Open ROM" or drag a .sfc/.smc file onto the canvas
  3. Wait for loading overlay: A progress indicator shows loading stages
  4. Verify ROM loaded:
    // Check ROM status after user loads ROM
    const status = window.yaze.control.getRomStatus();
    console.log('ROM loaded:', status.loaded, 'Title:', status.title);
    // Expected: { loaded: true, filename: "zelda3.sfc", title: "THE LEGEND OF ZELDA", ... }
    

If ROM not loading:

  • Check FilesystemManager.ready === true
  • Check browser console for errors
  • Try: FS.stat('/roms') - should not throw

Phase 3: Dismiss Welcome Screen / Initial View

After ROM loads, the app may show a Welcome screen or Settings editor by default.

Switch to a working editor:

// Step 1: Check current editor
const current = window.yaze.control.getCurrentEditor();
console.log('Current editor:', current.name);

// Step 2: Switch to Dungeon or Overworld editor
window.yaze.control.switchEditor('Dungeon');
// OR for async with confirmation:
const result = await window.yazeDebug.switchToEditorAsync('Dungeon');
console.log('Switch result:', result);
// Expected: { success: true, editor: "Dungeon", session_id: 1 }

// Step 3: Verify switch
const newEditor = window.yaze.control.getCurrentEditor();
console.log('Now in:', newEditor.name);

Available editors: Overworld, Dungeon, Graphics, Palette, Sprite, Music, Message, Screen, Assembly, Hex, Agent, Settings

Phase 4: Make Cards Visible

After switching editors, the canvas may appear empty if no cards are visible.

Show essential cards for Dungeon editor:

// Option A: Show a predefined card group
window.yazeDebug.cards.showGroup('dungeon_editing');
// Shows: room_selector, object_editor, canvas

// Option B: Show cards individually
window.yazeDebug.cards.show('dungeon.room_selector');
window.yazeDebug.cards.show('dungeon.object_editor');

// Option C: Apply a layout preset
window.yaze.control.setCardLayout('dungeon_default');

Show essential cards for Overworld editor:

window.yazeDebug.cards.showGroup('overworld_editing');
// OR
window.yaze.control.setCardLayout('overworld_default');

Query visible cards:

const visible = window.yaze.control.getVisibleCards();
console.log('Visible cards:', visible);

// Get all available cards for current editor
const available = window.yaze.control.getAvailableCards();
console.log('Available cards:', available);

Phase 5: Verify Working State

After completing setup, verify the editor is functional:

// Full state check
const state = window.aiTools.getAppState();
// Logs: ROM Status, Current Editor, Visible Cards, Available Editors

// Or get structured data:
const snapshot = {
  rom: window.yaze.control.getRomStatus(),
  editor: window.yaze.control.getCurrentEditor(),
  cards: window.yaze.control.getVisibleCards(),
  session: window.yaze.control.getSessionInfo()
};
console.log(JSON.stringify(snapshot, null, 2));

Expected successful state:

{
  "rom": { "loaded": true, "title": "THE LEGEND OF ZELDA" },
  "editor": { "name": "Dungeon", "active": true },
  "cards": ["Room Selector", "Object Editor", ...],
  "session": { "rom_loaded": true, "current_editor": "Dungeon" }
}

Quick Command Reference for AI Agents

Copy-paste ready commands for common operations:

// ========== INITIAL SETUP ==========
// 1. Verify ready state
window.Module?.calledRun && window.yaze.control.isReady()

// 2. Check ROM (after user loads it)
window.yaze.control.getRomStatus()

// 3. Switch editor (away from welcome/settings)
await window.yazeDebug.switchToEditorAsync('Dungeon')

// 4. Show cards
window.yazeDebug.cards.showGroup('dungeon_editing')

// 5. Full state dump
window.aiTools.getAppState()

// ========== NAVIGATION ==========
// Switch editors
window.yaze.control.switchEditor('Overworld')
window.yaze.control.switchEditor('Dungeon')
window.yaze.control.switchEditor('Graphics')

// Jump to specific room/map
window.aiTools.jumpToRoom(0)    // Dungeon room 0
window.aiTools.jumpToMap(0)     // Overworld map 0

// ========== CARD CONTROL ==========
// Show/hide cards
window.yazeDebug.cards.show('dungeon.room_selector')
window.yazeDebug.cards.hide('dungeon.object_editor')
window.yazeDebug.cards.toggle('dungeon.room_selector')

// Card groups
window.yazeDebug.cards.showGroup('dungeon_editing')
window.yazeDebug.cards.showGroup('overworld_editing')
window.yazeDebug.cards.showGroup('minimal')

// Layout presets
window.yaze.control.setCardLayout('dungeon_default')
window.yaze.control.setCardLayout('overworld_default')
window.yaze.control.getAvailableLayouts()

// ========== DATA ACCESS ==========
// Dungeon data
window.yaze.data.getRoomTiles(0)
window.yaze.data.getRoomObjects(0)
window.yaze.data.getRoomProperties(0)

// Overworld data
window.yaze.data.getMapTiles(0)
window.yaze.data.getMapEntities(0)
window.yaze.data.getMapProperties(0)

// ========== SIDEBAR/PANEL CONTROL ==========
window.yazeDebug.sidebar.setTreeView(true)   // Expand sidebar
window.yazeDebug.sidebar.setTreeView(false)  // Collapse to icons
window.yazeDebug.rightPanel.open('properties')
window.yazeDebug.rightPanel.close()

// ========== SCREENSHOTS ==========
window.yaze.gui.takeScreenshot()  // Returns { dataUrl: "data:image/png;base64,..." }

Build and Serve Strategy

Minimize Rebuild Time

Use --incremental flag after the first full build:

./scripts/build-wasm.sh debug --incremental

Saves 30-60 seconds by preserving the CMake cache.

Batch your changes: Make all planned C++ changes first, then rebuild once. JS/CSS changes don't require rebuilding—they're copied from source on startup.

JS/CSS-only changes: No rebuild needed. Just copy files and refresh:

cp src/web/app.js build-wasm/dist/
cp -r src/web/styles/* build-wasm/dist/styles/
cp -r src/web/components/* build-wasm/dist/components/
cp -r src/web/core/* build-wasm/dist/core/
# Then refresh browser

When to do a full rebuild:

  • After modifying CMakeLists.txt or CMakePresets.json
  • After changing compiler flags or linker options
  • After adding/removing source files
  • When incremental build produces unexpected behavior

Typical Development Workflow

  1. First session: Full debug build

    ./scripts/build-wasm.sh debug
    ./scripts/serve-wasm.sh --debug --force 8080
    
  2. Subsequent C++ changes: Incremental rebuild

    ./scripts/build-wasm.sh debug --incremental
    # Server auto-detects new files, just refresh browser
    
  3. JS/CSS only changes: No rebuild needed

    # Copy changed files directly
    cp src/web/core/filesystem_manager.js build-wasm/dist/core/
    # Refresh browser
    
  4. Verify before lengthy debug session:

    // In browser console
    console.log(Module?.calledRun);           // should be true
    console.log(FilesystemManager.ready);     // should be true after FS init
    

Loading ROMs Safely

ROM Input Methods

Supported input formats:

  • Drag/drop: .sfc, .smc, or .zip files (writes to /roms in MEMFS)
  • File dialog: Via UI "Open ROM" button (Folder icon in Nav Bar)

Important

Do not use window.yazeApp.loadRom() or other JS calls to programmatically open ROMs. These methods are unreliable because they bypass the browser's security model for file access or expect files to already exist in the virtual filesystem. Always ask the user to load the ROM using the UI.

Async JavaScript Calls

When using execute_browser_javascript or similar tools:

  • Async Syntax: If you need to use await (e.g., for navigator.clipboard.writeText), wrap your code in an async IIFE:
    (async () => {
      await someAsyncFunction();
      return "done";
    })();
    
  • Top-level await: Direct top-level await is often not supported by runner contexts (like Playwright's evaluate).

Filesystem Readiness

WASM mounts IDBFS in C++ during initialization. JS no longer remounts. To verify:

// Check if filesystem is ready
FS && FS.stat('/roms')  // should not throw

// Or use the debug API
window.yazeDebug?.rom.getStatus()  // should show { loaded: true, ... }

If "Open ROM" appears dead:

  • Check console for FS already initialized by C++ runtime message
  • Verify FilesystemManager.ready === true
  • If fsReady is false, wait for header status to show "Ready" or refresh after build/serve

After Loading

Verify ROM loaded successfully:

window.yazeDebug?.rom.getStatus()
// Expected: { loaded: true, size: 1048576, title: "THE LEGEND OF ZELDA", version: 0 }

Loading progress should show overlay UI with messages like "Loading graphics...", "Loading dungeons...", etc.


Directory Reorganization (November 2025)

The src/web/ directory is now organized into logical subdirectories:

src/web/
├── app.js                    # Main application logic
├── shell.html                # HTML shell template
├── components/               # UI Component JS files
│   ├── collab_console.js
│   ├── collaboration_ui.js
│   ├── drop_zone.js          # Drag/drop (C++ handler takes precedence)
│   ├── shortcuts_overlay.js
│   ├── terminal.js
│   └── touch_gestures.js
├── core/                     # Core infrastructure
│   ├── config.js             # YAZE_CONFIG settings
│   ├── error_handler.js
│   ├── filesystem_manager.js # ROM file handling (VFS)
│   └── loading_indicator.js  # Loading progress UI
├── debug/                    # Debug utilities
│   └── yaze_debug_inspector.cc
├── icons/                    # PWA icons
├── pwa/                      # Progressive Web App files
│   ├── coi-serviceworker.js  # SharedArrayBuffer support
│   ├── manifest.json
│   └── service-worker.js
└── styles/                   # CSS stylesheets
    ├── main.css
    └── terminal.css

Update all file paths in your prompts and code accordingly.


Key Systems Reference

FilesystemManager (src/web/core/filesystem_manager.js)

Global object for ROM file operations:

FilesystemManager.ready                    // Boolean: true when /roms is accessible
FilesystemManager.ensureReady()            // Check + show status if not ready
FilesystemManager.handleRomUpload(file)    // Write to VFS + call LoadRomFromWeb
FilesystemManager.onFileSystemReady()      // Called by C++ when FS is mounted

WasmLoadingManager (src/app/platform/wasm/wasm_loading_manager.cc)

C++ system that creates browser UI overlays during asset loading:

auto handle = WasmLoadingManager::BeginLoading("Task Name");
WasmLoadingManager::UpdateProgress(handle, 0.5f);
WasmLoadingManager::UpdateMessage(handle, "Loading dungeons...");
WasmLoadingManager::EndLoading(handle);

Corresponding JS functions: createLoadingIndicator(), updateLoadingProgress(), removeLoadingIndicator()

WasmDropHandler (src/app/platform/wasm/wasm_drop_handler.cc)

C++ drag/drop handler that:

  • Registered in wasm_bootstrap.cc::InitializeWasmPlatform()
  • Writes dropped files to /roms/ and calls LoadRomFromWeb()
  • JS drop_zone.js is disabled to avoid conflicts

Debug API: yazeDebug

For detailed API documentation, see docs/internal/wasm-yazeDebug-api-reference.md.

The window.yazeDebug API provides unified access to WASM debug infrastructure. Key functions:

Quick Checks

// Is module ready?
window.yazeDebug.isReady()

// Complete state dump as JSON
window.yazeDebug.dumpAll()

// Human-readable summary for AI
window.yazeDebug.formatForAI()

ROM + Emulator

// ROM load status
window.yazeDebug.rom.getStatus()
// → { loaded: true, size: 1048576, title: "...", version: 0 }

// Read ROM bytes (up to 256)
window.yazeDebug.rom.readBytes(0x10000, 32)
// → { address: 65536, count: 32, bytes: [...] }

// ROM palette lookup
window.yazeDebug.rom.getPaletteGroup("dungeon_main", 0)

// Emulator CPU/memory state
window.yazeDebug.emulator.getStatus()
window.yazeDebug.emulator.readMemory(0x7E0000, 16)
window.yazeDebug.emulator.getVideoState()

Graphics + Palettes

// Graphics sheet diagnostics
window.yazeDebug.graphics.getDiagnostics()
window.yazeDebug.graphics.detect0xFFPattern()  // Regression check

// Palette events (DungeonEditor debug)
window.yazeDebug.palette.getEvents()
window.yazeDebug.palette.getFullState()
window.yazeDebug.palette.samplePixel(x, y)

// Arena (graphics queue) status
window.yazeDebug.arena.getStatus()
window.yazeDebug.arena.getSheetInfo(index)

Editor + Overworld

// Current editor state
window.yazeDebug.editor.getState()
window.yazeDebug.editor.executeCommand("cmd")

// Overworld data
window.yazeDebug.overworld.getMapInfo(mapId)
window.yazeDebug.overworld.getTileInfo(mapId, x, y)

DOM Hooks (for Antigravity)

document.getElementById('loading-overlay')  // Progress UI
document.getElementById('status')           // Status text
document.getElementById('header-status')    // Header status
document.getElementById('canvas')           // Main canvas
document.getElementById('rom-input')        // File input

Quick Reference for Antigravity:

// 1. Capture screenshot for visual analysis
const result = window.yaze.gui.takeScreenshot();
const dataUrl = result.dataUrl;

// 2. Get room data for comparison
const roomData = window.aiTools.getRoomData();
const tiles = window.yaze.data.getRoomTiles(roomData.id || 0);

// 3. Check graphics loading status
const arena = window.yazeDebug.arena.getStatus();

// 4. Full diagnostic dump
async function getDiagnostic(roomId) {
  const data = {
    room_id: roomId,
    objects: window.yaze.data.getRoomObjects(roomId),
    properties: window.yaze.data.getRoomProperties(roomId),
    arena: window.yazeDebug?.arena?.getStatus(),
    visible_cards: window.aiTools.getVisibleCards()
  };
  await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
  return data;
}

Current Issues and Priorities

ROM Loading Reliability

Past issue: duplicate IDBFS initialization (app.js:initPersistentFS vs C++ mount). FIXED in November 2025.

Current best practice: UI paths should only gate on fsReady flag or /roms existence. Surface helpful status text if initialization is in progress.

DungeonEditor Object Rendering (WASM)

Rendering runs on the main thread (DungeonEditorV2::DrawRoomTab), causing UI freezes on large rooms with many objects.

Debug approach:

  1. Use window.yazeDebug.palette.getEvents() to capture palette application
  2. Use window.yazeDebug.arena.getStatus() to check texture queue depth
  3. Consider offloading LoadRoomGraphics to WasmWorkerPool or batching uploads

Network Blocking

Direct EmscriptenHttpClient calls on the main thread can stall the UI. Keep AI/HTTP calls inside worker threads or async handlers (current browser_agent.cc uses std::thread).

Short-term Tasks (Phase 9 Roadmap)

  • Palette import for .pal files in wasm_drop_handler
  • Deep-linking (?rom=url) parsing in app.js/main.cc
  • Improve drag/drop UI feedback

SNES Dungeon Context (for Object Rendering Debugging)

Load Path

Vanilla (usdasm reference):

Underworld_LoadRoom $01:873A
  → RoomDraw_DrawFloors
  → RoomDraw_LayoutPointers (layout pass)
  → RoomDraw_DrawAllObjects (three object passes):
     1. Layout stream
     2. Object stream
     3. BG2/BG1 pointer swaps
  → Doors start after sentinel $FFF0
  → Stream terminates with $FFFF

Object Byte Format

3-byte entries: (x|layer, y|layer, id/size)

Object Types:

  • Type1: $00$FF (standard tiles)
  • Type2: $100$1FF (extended tiles)
  • Type3: $F00$FFF (chests, pipes)

Position = upper 6 bits of first two bytes Size = lower 2 bits of each byte Layer = lowest bits

See usdasm RoomDraw_RoomObject $01:893C and RoomDraw_DoorObject $01:8916 for details.

Key ROM Pointers (Vanilla)

RoomData_ObjectDataPointers = $1F8000 (LoROM)
kRoomObjectLayoutPointer    = $882D
kRoomObjectPointer          = $874C
kRoomHeaderPointer          = $B5DD
Palette group table         = $0DEC4B
Dungeon tile data           = $091B52

yaze Rendering Pipeline

  1. Room::RenderRoomGraphics() sets the dungeon palette on BG1/BG2 before ObjectDrawer writes indexed pixels
  2. ObjectDrawer chooses BG by layer bits
  3. Known issue: BothBG stubs (e.g., DrawRightwards2x4spaced4_1to16_BothBG) currently single-buffer (incomplete)
  4. Texture uploads deferred via gfx::Arena::QueueTextureCommand

Reference Materials

  • ZScream parity: DungeonObjectData defines tiles/routines; Room_Object.LayerType supports BG1/BG2/BG3. yaze ObjectDrawer mirrors ZScream tables.
  • Oracle-of-Secrets: Custom Object Handler in Docs/World/Dungeons/Dungeons.md replaces reserved object IDs with data-driven multi-tile draws (hooks $31/$32/$54). Useful precedent for custom draw hooks.

Ready-to-send Prompt for Gemini

Copy and customize this prompt when attaching Gemini to the WASM build:

You are operating the yaze WASM build at http://127.0.0.1:8080 in Antigravity browser.
Stay in-browser; all fixes must be WASM-compatible.

=== STARTUP SEQUENCE (Follow in order) ===

NOTE: All commands are SYNCHRONOUS unless marked (async).
      For async, wrap in: (async () => { await ...; })()

STEP 1: Verify module ready
  window.Module?.calledRun === true
  window.yaze?.control?.isReady?.() === true
  → If false, wait and retry

STEP 2: ROM Loading (USER MUST DO THIS)
  - Tell user: "Please click the folder icon (📁) in the nav bar to load a ROM"
  - Or tell user: "Drag your .sfc/.smc file onto the canvas"
  - DO NOT attempt programmatic ROM loading - it won't work

STEP 3: Verify ROM loaded
  window.yaze.control.getRomStatus()
  → Expected: { loaded: true, title: "THE LEGEND OF ZELDA" }

STEP 4: Switch away from welcome/settings screen
  window.yaze.control.switchEditor('Dungeon')   // ← SYNC, use this one
  → Verify: window.yaze.control.getCurrentEditor()

STEP 5: Make cards visible (editor may appear empty without this!)
  window.yaze.control.setCardLayout('dungeon_default')  // ← SYNC
  → Verify: window.yaze.control.getVisibleCards()

STEP 6: Confirm working state
  window.aiTools.getAppState()  // Logs full state to console

=== QUICK REFERENCE ===

Check state:
  window.yaze.control.getRomStatus()
  window.yaze.control.getCurrentEditor()
  window.yaze.control.getVisibleCards()
  window.aiTools.getAppState()

Switch editors:
  window.yaze.control.switchEditor('Dungeon')
  window.yaze.control.switchEditor('Overworld')
  window.yaze.control.switchEditor('Graphics')

Show cards:
  window.yazeDebug.cards.showGroup('dungeon_editing')
  window.yazeDebug.cards.showGroup('overworld_editing')
  window.yaze.control.setCardLayout('dungeon_default')

Navigate:
  window.aiTools.jumpToRoom(0)  // Go to dungeon room 0
  window.aiTools.jumpToMap(0)   // Go to overworld map 0

Screenshot:
  window.yaze.gui.takeScreenshot()

=== COMMON ISSUES ===

"Canvas is blank / no content visible"
  → Run: window.yazeDebug.cards.showGroup('dungeon_editing')
  → Or: window.yaze.control.setCardLayout('dungeon_default')

"ROM not loading"
  → User must load via UI (folder icon or drag-drop)
  → Check: FilesystemManager.ready === true
  → Check: FS.stat('/roms') should not throw

"Still on welcome/settings screen"
  → Run: window.yaze.control.switchEditor('Dungeon')

"API calls return { error: ... }"
  → Check: window.yaze.control.isReady()
  → Check: window.yaze.control.getRomStatus().loaded

=== CURRENT FOCUS ===
[Describe the specific debugging goal, e.g., "DungeonEditor rendering"]

=== SUCCESS CRITERIA ===
1. ROM loaded (verified via getRomStatus)
2. Editor switched (not on welcome/settings)
3. Cards visible (content displaying)
4. Specific goal achieved

Reference Documentation

For detailed information, consult these three primary WASM docs:

  • Build & Debug: docs/internal/agents/wasm-development-guide.md - Build instructions, CMake config, debugging tips, performance best practices, ImGui ID conflict prevention
  • API Reference: docs/internal/wasm-yazeDebug-api-reference.md - Full JavaScript API documentation, including Agent Discoverability Infrastructure (widget overlay, canvas data attributes, card registry APIs)
  • This Playbook: AI agent workflows, Gemini integration, quick start guides

Archived docs (for historical reference only): docs/internal/agents/archive/wasm-docs-2025/


Common Time Wasters to Avoid

  • Don't rebuild for JS/CSS changes — just copy files and refresh
  • Don't clean CMake cache unless necessary — use --incremental
  • Don't restart server after rebuild — it serves from dist/ which gets updated automatically
  • Don't rebuild to test yazeDebug API — it's already in the running module
  • Batch multiple C++ fixes before rebuilding instead of rebuild-per-fix

Troubleshooting Common Issues

SharedArrayBuffer / COI Reload Loop

If the app is stuck in a reload loop or shows "SharedArrayBuffer unavailable":

  1. Reset COI state: Add ?reset-coi=1 to the URL (e.g., http://localhost:8080?reset-coi=1)
  2. Clear browser state:
    • DevTools → Application → Service Workers → Unregister all
    • DevTools → Application → Storage → Clear site data
  3. Verify server headers: The serve script should show "Server running with COOP/COEP headers enabled"

FS Not Available / ROM Loading Fails

The FS object must be exported from the WASM module. If window.FS is undefined:

  1. Verify CMakePresets.json includes EXPORTED_RUNTIME_METHODS=['FS','ccall','cwrap',...]
  2. Check console for [FilesystemManager] Aliasing Module.FS to window.FS
  3. If missing, rebuild with --clean flag

Thread Pool Exhausted

If you see "Tried to spawn a new thread, but the thread pool is exhausted":

  • Current setting: PTHREAD_POOL_SIZE=8 in CMakePresets.json
  • If still insufficient, increase the value and rebuild with --clean

Canvas Resize Crash

If resizing the terminal panel causes WASM abort with "attempt to write non-integer":

  • This was fixed by ensuring Math.floor() is used for canvas dimensions
  • Verify src/web/app.js and src/web/shell.html both use integer values for resize

Missing CSS Files (404)

CSS files are in src/web/styles/. Component JS files that dynamically load CSS must use the correct path:

// Correct:
link.href = 'styles/shortcuts_overlay.css';
// Wrong:
link.href = 'shortcuts_overlay.css';

Key C++ Files for Debugging

  • src/app/editor/dungeon/dungeon_editor_v2.cc — Main dungeon editor
  • src/app/editor/dungeon/dungeon_room_loader.cc — Room loading logic
  • src/zelda3/dungeon/room.cc — Room data and rendering
  • src/zelda3/dungeon/room_object.cc — Object drawing
  • src/app/gfx/resource/arena.cc — Graphics sheet management
  • src/app/platform/wasm/wasm_loading_manager.cc — Loading progress UI
  • src/app/platform/wasm/wasm_drop_handler.cc — Drag/drop file handling
  • src/app/platform/wasm/wasm_control_api.cc — JS API implementation