Files
yaze/docs/internal/agents/archive/wasm-docs-2025/wasm_dungeon_debugging.md

22 KiB

WASM Debugging Guide: Dungeon Editor

Status: Current (November 2025) Last Updated: 2025-11-25 Version: 2.3.0

The WASM build includes a powerful, hidden "Debug Inspector" that bypasses the need for GDB/LLDB by exposing C++ state directly to the browser console.

Cross-Reference: For comprehensive debug API reference, see wasm-debug-infrastructure.md and wasm-yazeDebug-api-reference.md.

1. The "God Mode" Console Inspector

The file src/web/yaze_debug_inspector.cc binds C++ functions to the global Module object. You can invoke these directly from Chrome/Firefox DevTools.

Status & State:

  • Module.getEmulatorStatus(): Returns JSON with CPU registers (A, X, Y, PC), Flags, and PPU state.
  • Module.getFullDebugState(): Returns a massive JSON dump suitable for pasting into an AI prompt for analysis.
  • Module.getArenaStatus(): Checks the memory arena used for dungeon rendering (vital for "out of memory" rendering glitches).

Memory Inspection:

  • Module.readEmulatorMemory(addr, length): Reads WRAM/SRAM.
    • Example: Check Link's X-Coordinate ($20): Module.readEmulatorMemory(0x7E0020, 2)
  • Module.readRom(addr, length): Verifies if your ROM patch actually applied in memory.

Graphics & Palette:

  • Module.getDungeonPaletteEvents(): Returns a log of recent palette uploads. Use this if colors look wrong or "flashy" in the editor.

2. The Command Line Bridge

The terminal you see in the web app isn't just a UI toy; it's a direct bridge to the C++ backend.

  • Architecture: src/web/terminal.js captures your keystrokes and calls Module.ccall('Z3edProcessCommand', ...) which routes to src/cli/wasm_terminal_bridge.cc.
  • Debug Tip: If the editor UI freezes, the terminal often remains responsive (running on a separate event cadence). You can use it to:
    1. Save your work: save
    2. Dump state: (If a custom command exists)
    3. Reset the emulator.

3. The Hidden "Debug Controls" Card

The code in src/app/editor/dungeon/dungeon_editor_v2.cc contains a function DrawDebugControlsCard(), controlled by the boolean show_debug_controls_.

  • Current Status: This is currently hidden by default and likely has no UI toggle in the public build.
  • Recommended Task: Create a z3ed CLI command to toggle this boolean.
    • Implementation: Add a command editor debug toggle in wasm_terminal_bridge.cc that finds the active DungeonEditorV2 instance and flips show_debug_controls_ = !show_debug_controls_. This would give you on-screen access to render passes and layer toggles.

4. Feature Parity

There are NO __EMSCRIPTEN__ checks inside the src/app/editor/dungeon/ logic.

  • Implication: If a logic bug exists in WASM, it likely exists in the Native macOS/Linux build too. Reproduce bugs on Desktop first for easier debugging (breakpoints, etc.), then verify the fix on Web.
  • Exception: Rendering glitches are likely WASM-specific due to the single-threaded TickFrame loop vs. the multi-threaded desktop renderer.

5. Thread Pool Configuration

The WASM build uses a fixed thread pool (PTHREAD_POOL_SIZE=8 in CMakePresets.json).

  • Warning Signs: If you see "Tried to spawn a new thread, but the thread pool is exhausted", heavy parallel operations are exceeding the pool size.
  • Fix: Increase PTHREAD_POOL_SIZE in CMakePresets.json and rebuild with --clean
  • Root Cause: Often happens during ROM loading when multiple graphics sheets are decompressed in parallel.

6. Memory Configuration

The WASM build uses optimized memory settings to reduce heap resize operations:

Setting Value Purpose
INITIAL_MEMORY 256MB Reduces heap resizing during ROM load
MAXIMUM_MEMORY 1GB Prevents runaway allocations
STACK_SIZE 8MB Handles recursive asset decompression
  • Warning Signs: Console shows _emscripten_resize_heap calls during loading
  • Common Causes: Overworld map loading (~160MB for 160 maps), sprite preview buffers, dungeon object emulator
  • Optimization Applied: Lazy initialization for SNES emulator instances and sprite preview buffers

7. ROM Loading Progress

The C++ WasmLoadingManager controls loading progress display. Monitor loading status:

// Check if ROM is loaded
window.yaze.control.getRomStatus()
// Returns: { loaded: true/false, filename: "...", title: "...", size: ... }

// Check arena (graphics) status
window.yazeDebug.arena.getStatus()

// Full editor state after loading
window.yaze.editor.getSnapshot()

Loading Progress Stages:

Progress Stage
10% Initializing editors...
18% Loading graphics sheets...
26% Loading overworld...
34% Loading dungeons...
42%+ Loading remaining editors...
100% Complete

8. Prompting AI Agents (Claude Code / Gemini Antigravity)

This section provides precise prompts and workflows for AI agents to interact with the YAZE WASM app.

Step 1: Verify the App is Ready

Before any operations, the AI must confirm the WASM module is initialized:

// Check module ready state
window.YAZE_MODULE_READY  // Should be true

// Check if APIs are available
typeof window.yaze !== 'undefined' &&
typeof window.yaze.control !== 'undefined'  // Should be true

If not ready, wait and retry:

// Poll until ready (max 10 seconds)
async function waitForYaze() {
  for (let i = 0; i < 100; i++) {
    if (window.YAZE_MODULE_READY && window.yaze?.control) return true;
    await new Promise(r => setTimeout(r, 100));
  }
  return false;
}
await waitForYaze();
Step 2: Load a ROM File

Option A: User Drag-and-Drop (Recommended) Prompt the user: "Please drag and drop your Zelda 3 ROM (.sfc or .smc) onto the canvas."

Then verify:

// Check if ROM loaded successfully
const status = window.yaze.control.getRomStatus();
console.log(status);
// Expected: { loaded: true, filename: "zelda3.sfc", title: "...", size: 1048576 }

Option B: Check if ROM Already Loaded

window.yaze.control.getRomStatus().loaded  // true if ROM is present

Option C: Load from IndexedDB (if previously saved)

// List saved ROMs
FilesystemManager.listSavedRoms && FilesystemManager.listSavedRoms();
Step 3: Open the Dungeon Editor
// Switch to dungeon editor
window.yaze.control.switchEditor('Dungeon');

// Verify switch was successful
const snapshot = window.yaze.editor.getSnapshot();
console.log(snapshot.editor_type);  // Should be "Dungeon"
Step 4: Navigate to a Specific Room
// Get current room info
window.yaze.editor.getCurrentRoom();
// Returns: { room_id: 0, active_rooms: [...], visible_cards: [...] }

// Navigate to room 42 (Hyrule Castle Entrance)
window.aiTools.navigateTo('room:42');

// Or use control API
window.yaze.control.openCard('Room 42');
Step 5: Inspect Room Data
// Get room properties
const props = window.yaze.data.getRoomProperties(42);
console.log(props);
// Returns: { music: 5, palette: 2, tileset: 7, ... }

// Get room objects (chests, torches, blocks, etc.)
const objects = window.yaze.data.getRoomObjects(42);
console.log(objects);

// Get room tile data
const tiles = window.yaze.data.getRoomTiles(42);
console.log(tiles.layer1, tiles.layer2);
Example AI Prompt Workflow

User asks: "Show me the objects in room 42 of the dungeon editor"

AI should execute:

// 1. Verify ready
if (!window.YAZE_MODULE_READY) throw new Error("WASM not ready");

// 2. Check ROM
const rom = window.yaze.control.getRomStatus();
if (!rom.loaded) throw new Error("No ROM loaded - please drag a ROM file onto the canvas");

// 3. Switch to dungeon editor
window.yaze.control.switchEditor('Dungeon');

// 4. Navigate to room
window.aiTools.navigateTo('room:42');

// 5. Get and display data
const objects = window.yaze.data.getRoomObjects(42);
console.log("Room 42 Objects:", JSON.stringify(objects, null, 2));

9. JavaScript Tips for Browser Debugging

Console Shortcuts
// Alias for quick access
const y = window.yaze;
const yd = window.yazeDebug;

// Quick status check
y.control.getRomStatus()
y.editor.getSnapshot()
yd.arena.getStatus()
Error Handling Pattern

Always wrap API calls in try-catch when automating:

function safeCall(fn, fallback = null) {
  try {
    return fn();
  } catch (e) {
    console.error('[YAZE API Error]', e.message);
    return fallback;
  }
}

// Usage
const status = safeCall(() => window.yaze.control.getRomStatus(), { loaded: false });
Async Operations

Some operations are async. Use proper await patterns:

// Wait for element to appear in GUI
await window.yaze.gui.waitForElement('dungeon-room-canvas', 5000);

// Then interact
window.yaze.gui.click('dungeon-room-canvas');
Debugging State Issues
// Full state dump for debugging
const debugState = {
  moduleReady: window.YAZE_MODULE_READY,
  romStatus: window.yaze?.control?.getRomStatus?.() || 'API unavailable',
  editorSnapshot: window.yaze?.editor?.getSnapshot?.() || 'API unavailable',
  arenaStatus: window.yazeDebug?.arena?.getStatus?.() || 'API unavailable',
  consoleErrors: window._yazeConsoleLogs?.filter(l => l.includes('[ERROR]')) || []
};
console.log(JSON.stringify(debugState, null, 2));
Monitoring Loading Progress
// Set up a loading progress monitor
let lastProgress = 0;
const progressInterval = setInterval(() => {
  const status = window.yaze?.control?.getRomStatus?.();
  if (status?.loaded) {
    console.log('ROM loaded successfully!');
    clearInterval(progressInterval);
  }
}, 500);

// Clear after 30 seconds timeout
setTimeout(() => clearInterval(progressInterval), 30000);
Inspecting Graphics Issues
// Check if graphics sheets are loaded
const arenaStatus = window.yazeDebug.arena.getStatus();
console.log('Loaded sheets:', arenaStatus.loaded_sheets);
console.log('Pending textures:', arenaStatus.pending_textures);

// Check for palette issues
const paletteEvents = Module.getDungeonPaletteEvents?.() || 'Not available';
console.log('Recent palette changes:', paletteEvents);
Memory Usage Check
// Check WASM memory usage
const memInfo = {
  heapSize: Module.HEAPU8?.length || 0,
  heapSizeMB: ((Module.HEAPU8?.length || 0) / 1024 / 1024).toFixed(2) + ' MB'
};
console.log('Memory:', memInfo);

10. Gemini Antigravity AI Tools

For AI assistants using the Antigravity browser extension, use the high-level window.aiTools API:

Complete Workflow Example
// Step-by-step for Gemini/Antigravity
async function inspectDungeonRoom(roomId) {
  // 1. Verify environment
  if (!window.YAZE_MODULE_READY) {
    return { error: "WASM module not ready. Please wait for initialization." };
  }

  // 2. Check ROM
  const romStatus = window.yaze.control.getRomStatus();
  if (!romStatus.loaded) {
    return { error: "No ROM loaded. Please drag a Zelda 3 ROM onto the canvas." };
  }

  // 3. Switch to dungeon editor
  window.yaze.control.switchEditor('Dungeon');

  // 4. Navigate to room
  window.aiTools.navigateTo(`room:${roomId}`);

  // 5. Gather all data
  return {
    room_id: roomId,
    properties: window.yaze.data.getRoomProperties(roomId),
    objects: window.yaze.data.getRoomObjects(roomId),
    tiles: window.yaze.data.getRoomTiles(roomId),
    editor_state: window.yaze.editor.getCurrentRoom()
  };
}

// Usage
const data = await inspectDungeonRoom(42);
console.log(JSON.stringify(data, null, 2));
Quick Reference Commands
// Get full application state with console output
window.aiTools.getAppState()

// Get dungeon room data
window.aiTools.getRoomData(0)

// Navigate directly to a room
window.aiTools.navigateTo('room:42')

// Show/hide editor cards
window.aiTools.showCard('Room Selector')
window.aiTools.hideCard('Object Editor')

// Get complete API reference
window.aiTools.dumpAPIReference()

// AI-formatted state (paste-ready for prompts)
window.yazeDebug.formatForAI()
Handling Common Errors
Error Cause Solution
window.yaze is undefined Module not initialized Wait for YAZE_MODULE_READY
getRomStatus().loaded = false No ROM file Prompt user to drag ROM
switchEditor returns error Invalid editor name Use: Dungeon, Overworld, Graphics, Palette, Sprite, Music
getRoomObjects returns empty Room not loaded Navigate to room first
Canvas shows black Graphics not loaded Check yazeDebug.arena.getStatus()

Nav Bar Access: The web UI includes dedicated dropdown menus:

  • Editor - Quick switch to any editor
  • Emulator - Run/Pause/Step/Reset controls
  • Layouts - Preset card configurations
  • AI Tools - All window.aiTools functions via UI clicks

Command Palette (Ctrl+K): Search for "AI:" to access all AI helper commands.

11. Copying Data for External Analysis

// Copy room data to clipboard for pasting elsewhere
async function copyRoomDataToClipboard(roomId) {
  const data = {
    timestamp: new Date().toISOString(),
    room_id: roomId,
    properties: window.yaze.data.getRoomProperties(roomId),
    objects: window.yaze.data.getRoomObjects(roomId)
  };
  await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
  console.log(`Room ${roomId} data copied to clipboard!`);
}

// Usage
copyRoomDataToClipboard(42);

12. Antigravity: Debugging Dungeon Object Rendering Issues

This section provides Gemini Antigravity with specific workflows for identifying and analyzing dungeon object rendering problems, particularly discrepancies between the Room Graphics Card (background tiles) and the Object Editor Card (dungeon objects like pots, torches, chests).

Understanding the Rendering Pipeline

Dungeon rooms have two distinct rendering layers:

Card What It Shows Render Source
Room Graphics Background tiles (floors, walls, pits) Layer1/Layer2 tile data
Object Editor Interactive objects (pots, chests, blocks, torches) Object list with sprite-based rendering

Common Issue: Objects appear at wrong positions, wrong sprites, or don't match their expected appearance in the Object Editor compared to how they should look based on the room data.

Step-by-Step Debugging Workflow
1. Capture the Current Visual State

Use the browser's screenshot capability to capture what you see:

// Capture the entire canvas as a data URL
async function captureCanvasScreenshot() {
  const canvas = document.getElementById('canvas');
  if (!canvas) return { error: 'Canvas not found' };

  const dataUrl = canvas.toDataURL('image/png');
  console.log('[Screenshot] Canvas captured, length:', dataUrl.length);

  // For AI analysis, you can copy to clipboard
  await navigator.clipboard.writeText(dataUrl);
  return { success: true, message: 'Screenshot copied to clipboard as data URL' };
}

// Usage
await captureCanvasScreenshot();

For Antigravity: Take screenshots when:

  1. Room Graphics Card is visible (shows background)
  2. Object Editor Card is visible (shows objects overlaid)
  3. Both cards side-by-side if possible
2. Extract Room Object Data Efficiently
// Get complete object rendering data for a room
function getDungeonObjectDebugData(roomId) {
  const data = {
    room_id: roomId,
    timestamp: Date.now(),

    // Room properties affecting rendering
    properties: window.yaze.data.getRoomProperties(roomId),

    // All objects in the room with positions
    objects: window.yaze.data.getRoomObjects(roomId),

    // Current editor state
    editor_state: window.yaze.editor.getCurrentRoom(),

    // Graphics arena status (textures loaded?)
    arena_status: window.yazeDebug?.arena?.getStatus() || 'unavailable',

    // Visible cards (what's being rendered)
    visible_cards: window.yaze.editor.getSnapshot()?.visible_cards || []
  };

  return data;
}

// Pretty print for analysis
const debugData = getDungeonObjectDebugData(42);
console.log(JSON.stringify(debugData, null, 2));
3. Compare Object Positions vs Tile Positions
// Check if objects are aligned with the tile grid
function analyzeObjectPlacement(roomId) {
  const objects = window.yaze.data.getRoomObjects(roomId);
  const tiles = window.yaze.data.getRoomTiles(roomId);

  const analysis = objects.map(obj => {
    // Objects use pixel coordinates, tiles are 8x8 or 16x16
    const tileX = Math.floor(obj.x / 8);
    const tileY = Math.floor(obj.y / 8);

    return {
      object_id: obj.id,
      type: obj.type,
      pixel_pos: { x: obj.x, y: obj.y },
      tile_pos: { x: tileX, y: tileY },
      // Check if position is on grid boundary
      aligned_8px: (obj.x % 8 === 0) && (obj.y % 8 === 0),
      aligned_16px: (obj.x % 16 === 0) && (obj.y % 16 === 0)
    };
  });

  return analysis;
}

console.log(JSON.stringify(analyzeObjectPlacement(42), null, 2));
4. Identify Visual Discrepancies

Symptoms to look for:

Symptom Likely Cause Debug Command
Objects invisible Texture not loaded window.yazeDebug.arena.getStatus()
Wrong sprite shown Object type mismatch window.yaze.data.getRoomObjects(roomId)
Position offset Coordinate transform bug Compare pixel_pos in data vs visual
Colors wrong Palette not applied Module.getDungeonPaletteEvents()
Flickering Z-order/layer issue Check layer property in object data
5. DOM Inspection for Card State

Efficiently query the DOM to understand what's being rendered:

// Get all visible ImGui windows (cards)
function getVisibleCards() {
  // ImGui renders to canvas, but card state is tracked in JS
  const snapshot = window.yaze.editor.getSnapshot();
  return {
    active_cards: snapshot.visible_cards || [],
    editor_type: snapshot.editor_type,
    // Check if specific cards are open
    has_room_selector: snapshot.visible_cards?.includes('Room Selector'),
    has_object_editor: snapshot.visible_cards?.includes('Object Editor'),
    has_room_canvas: snapshot.visible_cards?.includes('Room Canvas')
  };
}

console.log(getVisibleCards());
6. Full Diagnostic Dump for AI Analysis
// Complete diagnostic for Antigravity to analyze rendering issues
async function generateRenderingDiagnostic(roomId) {
  const diagnostic = {
    timestamp: new Date().toISOString(),
    room_id: roomId,

    // Visual state
    visible_cards: getVisibleCards(),

    // Data state
    room_properties: window.yaze.data.getRoomProperties(roomId),
    room_objects: window.yaze.data.getRoomObjects(roomId),
    object_analysis: analyzeObjectPlacement(roomId),

    // Graphics state
    arena: window.yazeDebug?.arena?.getStatus(),
    palette_events: (() => {
      try { return Module.getDungeonPaletteEvents(); }
      catch { return 'unavailable'; }
    })(),

    // Memory state
    heap_mb: ((Module.HEAPU8?.length || 0) / 1024 / 1024).toFixed(2),

    // Console errors (last 10)
    recent_errors: window._yazeConsoleLogs?.slice(-10) || []
  };

  // Copy to clipboard for easy pasting
  const json = JSON.stringify(diagnostic, null, 2);
  await navigator.clipboard.writeText(json);
  console.log('[Diagnostic] Copied to clipboard');

  return diagnostic;
}

// Usage: Run this, then paste into your AI prompt
await generateRenderingDiagnostic(42);
Common Object Rendering Bugs
Bug: Objects Render at (0,0)

Diagnosis:

// Check for objects with zero coordinates
const objects = window.yaze.data.getRoomObjects(roomId);
const atOrigin = objects.filter(o => o.x === 0 && o.y === 0);
console.log('Objects at origin:', atOrigin);

Cause: Object position data not loaded or coordinate transformation failed.

Bug: Sprite Shows as Black Square

Diagnosis:

// Check if graphics sheet is loaded for object type
const arena = window.yazeDebug.arena.getStatus();
console.log('Loaded sheets:', arena.loaded_sheets);
console.log('Pending textures:', arena.pending_textures);

Cause: Texture not yet loaded from deferred queue. Force process:

// Wait for textures to load
await new Promise(r => setTimeout(r, 500));
Bug: Object in Wrong Location vs Room Graphics

Diagnosis:

// Compare layer1 tile at object position
const obj = window.yaze.data.getRoomObjects(roomId)[0];
const tiles = window.yaze.data.getRoomTiles(roomId);
const tileAtPos = tiles.layer1[Math.floor(obj.y / 8) * 64 + Math.floor(obj.x / 8)];
console.log('Object at:', obj.x, obj.y);
console.log('Tile at that position:', tileAtPos);
Screenshot Comparison Workflow

For visual debugging, use this workflow:

  1. Open Room Graphics Card only:

    window.aiTools.hideCard('Object Editor');
    window.aiTools.showCard('Room Canvas');
    // Take screenshot #1
    
  2. Enable Object Editor overlay:

    window.aiTools.showCard('Object Editor');
    // Take screenshot #2
    
  3. Compare: Objects should align with the room's floor/wall tiles. Misalignment indicates a coordinate bug.

Reporting Issues

When reporting dungeon rendering bugs, include:

// Generate a complete bug report
async function generateBugReport(roomId, description) {
  const report = {
    bug_description: description,
    room_id: roomId,
    diagnostic: await generateRenderingDiagnostic(roomId),
    steps_to_reproduce: [
      '1. Load ROM',
      '2. Open Dungeon Editor',
      `3. Navigate to Room ${roomId}`,
      '4. Observe [specific issue]'
    ],
    expected_behavior: 'Objects should render at correct positions matching tile grid',
    actual_behavior: description
  };

  console.log(JSON.stringify(report, null, 2));
  return report;
}

// Usage
await generateBugReport(42, 'Chest renders 8 pixels too far right');