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)
- Example: Check Link's X-Coordinate ($20):
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.jscaptures your keystrokes and callsModule.ccall('Z3edProcessCommand', ...)which routes tosrc/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:
- Save your work:
save - Dump state: (If a custom command exists)
- Reset the emulator.
- Save your work:
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
z3edCLI command to toggle this boolean.- Implementation: Add a command
editor debug toggleinwasm_terminal_bridge.ccthat finds the activeDungeonEditorV2instance and flipsshow_debug_controls_ = !show_debug_controls_. This would give you on-screen access to render passes and layer toggles.
- Implementation: Add a command
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
TickFrameloop 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_SIZEin 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_heapcalls 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.aiToolsfunctions 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:
- Room Graphics Card is visible (shows background)
- Object Editor Card is visible (shows objects overlaid)
- 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:
-
Open Room Graphics Card only:
window.aiTools.hideCard('Object Editor'); window.aiTools.showCard('Room Canvas'); // Take screenshot #1 -
Enable Object Editor overlay:
window.aiTools.showCard('Object Editor'); // Take screenshot #2 -
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');