backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)

This commit is contained in:
scawful
2025-12-22 00:20:49 +00:00
parent 2934c82b75
commit 5c4cd57ff8
1259 changed files with 239160 additions and 43801 deletions

View File

@@ -0,0 +1,358 @@
# WASM Agent API Testing Guide
**Status:** Active
**Last Updated:** 2025-11-27
**Purpose:** Guide for browser-capable AI agents to test the new `window.yaze.agent` API
**Audience:** AI agents (Gemini Antigravity, Claude, etc.) testing the WASM web port
## Overview
The yaze WASM build now includes a dedicated `window.yaze.agent` API namespace for AI/LLM agent integration. This API enables browser-based agents to:
- Send messages to the built-in AI chat system
- Access and manage chat history
- Configure AI providers (Ollama, Gemini, Mock)
- Review, accept, and reject code proposals
- Control the agent sidebar UI
This guide provides step-by-step instructions for testing all agent API features.
## Prerequisites
### 1. Serve the WASM Build
```bash
# Build (if needed)
./scripts/build-wasm.sh
# Serve locally
./scripts/serve-wasm.sh --force 8080
```
### 2. Open in Browser
Navigate to `http://127.0.0.1:8080` in a browser with DevTools access.
### 3. Load a ROM
Drop a Zelda 3 ROM file onto the application or use the File menu to load one. Many agent APIs require a loaded ROM.
### 4. Verify Module Ready
Open browser DevTools console and verify:
```javascript
// Check if module is ready
window.Module?.calledRun // Should be true
// Check if control API is ready
window.yaze.control.isReady() // Should be true
// Check if agent API is ready
window.yaze.agent.isReady() // Should be true (after ROM loaded)
```
## API Reference
### Agent Namespace: `window.yaze.agent`
All agent APIs return JSON objects with either success data or `{error: "message"}`.
---
## Testing Workflow
### Phase 1: Verify API Availability
Run these commands in the browser console to verify the agent API is available:
```javascript
// 1. Check API readiness
console.log("Agent ready:", window.yaze.agent.isReady());
// 2. List all available methods
console.log("Agent API methods:", Object.keys(window.yaze.agent));
// Expected: sendMessage, getChatHistory, getConfig, setConfig,
// getProviders, getProposals, acceptProposal, rejectProposal,
// getProposalDetails, openSidebar, closeSidebar, isReady
```
### Phase 2: Test Configuration APIs
```javascript
// 1. Get available AI providers
const providers = window.yaze.agent.getProviders();
console.log("Available providers:", providers);
// Expected: [{id: "mock", name: "Mock Provider", ...},
// {id: "ollama", name: "Ollama", ...},
// {id: "gemini", name: "Google Gemini", ...}]
// 2. Get current configuration
const config = window.yaze.agent.getConfig();
console.log("Current config:", config);
// Expected: {provider: "mock", model: "", ollama_host: "http://localhost:11434", ...}
// 3. Set new configuration
const result = window.yaze.agent.setConfig({
provider: "ollama",
model: "llama3",
ollama_host: "http://localhost:11434",
verbose: true
});
console.log("Config update result:", result);
// Expected: {success: true}
// 4. Verify configuration was applied
const newConfig = window.yaze.agent.getConfig();
console.log("Updated config:", newConfig);
```
### Phase 3: Test Sidebar Control
```javascript
// 1. Open the agent sidebar
const openResult = window.yaze.agent.openSidebar();
console.log("Open sidebar:", openResult);
// Expected: {success: true, sidebar_open: true}
// 2. Verify sidebar state via yazeDebug
const panelState = window.yazeDebug?.rightPanel?.getState?.();
console.log("Panel state:", panelState);
// 3. Close the agent sidebar
const closeResult = window.yaze.agent.closeSidebar();
console.log("Close sidebar:", closeResult);
// Expected: {success: true, sidebar_open: false}
```
### Phase 4: Test Chat APIs
```javascript
// 1. Send a test message
const msgResult = window.yaze.agent.sendMessage("Hello, agent! What can you help me with?");
console.log("Send message result:", msgResult);
// Expected: {success: true, status: "queued", message: "Hello, agent!..."}
// 2. Get chat history
const history = window.yaze.agent.getChatHistory();
console.log("Chat history:", history);
// Note: May be empty array initially - full implementation
// requires AgentChatWidget to expose history
// 3. Send a task-oriented message
const taskResult = window.yaze.agent.sendMessage("Analyze dungeon room 0");
console.log("Task message result:", taskResult);
```
### Phase 5: Test Proposal APIs
```javascript
// 1. Get current proposals
const proposals = window.yaze.agent.getProposals();
console.log("Proposals:", proposals);
// Note: Returns empty array until proposal system is integrated
// 2. Test accept proposal (with mock ID)
const acceptResult = window.yaze.agent.acceptProposal("proposal-123");
console.log("Accept result:", acceptResult);
// Expected: {success: false, error: "Proposal system not yet integrated", ...}
// 3. Test reject proposal (with mock ID)
const rejectResult = window.yaze.agent.rejectProposal("proposal-456");
console.log("Reject result:", rejectResult);
// 4. Test get proposal details
const details = window.yaze.agent.getProposalDetails("proposal-123");
console.log("Proposal details:", details);
```
## Complete Test Script
Copy and paste this complete test script into the browser console:
```javascript
// ============================================================================
// YAZE Agent API Test Suite
// ============================================================================
async function runAgentAPITests() {
const results = [];
function test(name, fn) {
try {
const result = fn();
results.push({name, status: 'PASS', result});
console.log(`${name}:`, result);
} catch (e) {
results.push({name, status: 'FAIL', error: e.message});
console.error(`${name}:`, e.message);
}
}
console.log("=== YAZE Agent API Test Suite ===\n");
// Phase 1: Availability
console.log("--- Phase 1: API Availability ---");
test("Agent API exists", () => typeof window.yaze.agent === 'object');
test("isReady() returns boolean", () => typeof window.yaze.agent.isReady() === 'boolean');
test("All expected methods exist", () => {
const expected = ['sendMessage', 'getChatHistory', 'getConfig', 'setConfig',
'getProviders', 'getProposals', 'acceptProposal', 'rejectProposal',
'getProposalDetails', 'openSidebar', 'closeSidebar', 'isReady'];
const missing = expected.filter(m => typeof window.yaze.agent[m] !== 'function');
if (missing.length > 0) throw new Error(`Missing: ${missing.join(', ')}`);
return true;
});
// Phase 2: Configuration
console.log("\n--- Phase 2: Configuration APIs ---");
test("getProviders() returns array", () => {
const providers = window.yaze.agent.getProviders();
if (!Array.isArray(providers)) throw new Error("Not an array");
if (providers.length < 3) throw new Error("Expected at least 3 providers");
return providers;
});
test("getConfig() returns object", () => {
const config = window.yaze.agent.getConfig();
if (typeof config !== 'object') throw new Error("Not an object");
return config;
});
test("setConfig() returns result", () => {
const result = window.yaze.agent.setConfig({provider: "mock"});
return result;
});
// Phase 3: Sidebar Control
console.log("\n--- Phase 3: Sidebar Control ---");
test("openSidebar() returns result", () => window.yaze.agent.openSidebar());
test("closeSidebar() returns result", () => window.yaze.agent.closeSidebar());
// Phase 4: Chat APIs
console.log("\n--- Phase 4: Chat APIs ---");
test("sendMessage() returns result", () => {
return window.yaze.agent.sendMessage("Test message from API suite");
});
test("getChatHistory() returns array", () => {
const history = window.yaze.agent.getChatHistory();
if (!Array.isArray(history)) throw new Error("Not an array");
return history;
});
// Phase 5: Proposal APIs
console.log("\n--- Phase 5: Proposal APIs ---");
test("getProposals() returns array", () => {
const proposals = window.yaze.agent.getProposals();
if (!Array.isArray(proposals)) throw new Error("Not an array");
return proposals;
});
test("acceptProposal() returns result", () => {
return window.yaze.agent.acceptProposal("test-proposal-id");
});
test("rejectProposal() returns result", () => {
return window.yaze.agent.rejectProposal("test-proposal-id");
});
test("getProposalDetails() returns result", () => {
return window.yaze.agent.getProposalDetails("test-proposal-id");
});
// Summary
console.log("\n=== Test Summary ===");
const passed = results.filter(r => r.status === 'PASS').length;
const failed = results.filter(r => r.status === 'FAIL').length;
console.log(`Passed: ${passed}/${results.length}`);
console.log(`Failed: ${failed}/${results.length}`);
if (failed > 0) {
console.log("\nFailed tests:");
results.filter(r => r.status === 'FAIL').forEach(r => {
console.log(` - ${r.name}: ${r.error}`);
});
}
return results;
}
// Run the tests
runAgentAPITests();
```
## Integration with Existing APIs
The Agent API works alongside existing WASM APIs:
### Combined Usage Example
```javascript
// 1. Use control API to switch to Agent editor
window.yaze.control.switchEditor('Agent');
// 2. Use agent API to configure
window.yaze.agent.setConfig({
provider: "ollama",
model: "codellama"
});
// 3. Open the sidebar
window.yaze.agent.openSidebar();
// 4. Send a message
window.yaze.agent.sendMessage("Help me analyze this ROM");
// 5. Use yazeDebug for additional diagnostics
console.log(window.yazeDebug.formatForAI());
```
### Using with aiTools
```javascript
// Get full app state including agent status
const state = window.aiTools.getAppState();
console.log("App state:", state);
// Navigate to agent editor
window.aiTools.navigateTo('Agent');
// Then use agent API
window.yaze.agent.openSidebar();
```
## Error Handling
All API calls return objects. Check for errors before processing:
```javascript
const result = window.yaze.agent.sendMessage("Hello");
if (result.error) {
console.error("API error:", result.error);
} else {
console.log("Success:", result);
}
```
Common errors:
- `"API not ready"` - Module not initialized or ROM not loaded
- `"Agent editor not available"` - Agent UI not built (`YAZE_BUILD_AGENT_UI=OFF`)
- `"Chat widget not available"` - AgentChatWidget not initialized
- `"Proposal system not yet integrated"` - Proposal APIs pending full integration
## Current Limitations
1. **Chat History**: `getChatHistory()` returns empty array until AgentChatWidget exposes history
2. **Proposals**: Proposal APIs return stub responses until proposal system integration
3. **Message Processing**: `sendMessage()` queues messages but actual processing is async
4. **ROM Required**: Most APIs require a ROM to be loaded first
## Related Documentation
- [WASM API Reference](../wasm-yazeDebug-api-reference.md) - Full JavaScript API documentation
- [WASM Development Guide](./wasm-development-guide.md) - Building and debugging WASM
- [WASM Antigravity Playbook](./wasm-antigravity-playbook.md) - AI agent workflows
## Version History
**1.0.0** (2025-11-27)
- Initial agent API documentation
- 12 API methods: isReady, sendMessage, getChatHistory, getConfig, setConfig,
getProviders, getProposals, acceptProposal, rejectProposal, getProposalDetails,
openSidebar, closeSidebar
- Test suite script for automated validation

View File

@@ -0,0 +1,925 @@
# Yaze WASM JavaScript API Reference
> **Note**: For a general debugging walkthrough, see the [WASM Debugging Guide](wasm-debugging-guide.md).
## Overview
The yaze WASM build exposes a comprehensive set of JavaScript APIs for programmatic control and data access. These APIs are organized into six main namespaces:
- **`window.yaze.control`** - Editor control and manipulation
- **`window.yaze.editor`** - Query current editor state
- **`window.yaze.data`** - Read-only access to ROM data
- **`window.yaze.gui`** - GUI automation and interaction
- **`window.yaze.agent`** - AI agent integration (chat, proposals, configuration)
- **`window.yazeDebug`** - Debug utilities and diagnostics
- **`window.aiTools`** - High-level AI assistant tools (Gemini Antigravity)
## API Version
- Version: 2.5.0
- Last Updated: 2025-11-27
- Capabilities: `['palette', 'arena', 'graphics', 'timeline', 'pixel-inspector', 'rom', 'overworld', 'emulator', 'editor', 'control', 'data', 'gui', 'agent', 'loading-progress', 'ai-tools', 'async-editor-switch', 'card-groups', 'tree-sidebar', 'properties-panel']`
## Build Requirements
The WASM module must be built with these Emscripten flags for full API access:
```
-s MODULARIZE=1
-s EXPORT_NAME='createYazeModule'
-s EXPORTED_RUNTIME_METHODS=['FS','ccall','cwrap','lengthBytesUTF8','stringToUTF8','UTF8ToString','getValue','setValue']
-s INITIAL_MEMORY=268435456 # 256MB initial heap
-s ALLOW_MEMORY_GROWTH=1 # Dynamic heap growth
-s MAXIMUM_MEMORY=1073741824 # 1GB max
-s STACK_SIZE=8388608 # 8MB stack
```
The dev server must set COOP/COEP headers for SharedArrayBuffer support. Use `./scripts/serve-wasm.sh` which handles this automatically.
### Memory Configuration
The WASM build uses optimized memory settings to reduce heap resize operations during ROM loading:
- **Initial Memory**: 256MB - Reduces heap resizing during overworld map loading (~200MB required)
- **Maximum Memory**: 1GB - Prevents runaway allocations
- **Stack Size**: 8MB - Handles recursive operations during asset decompression
## Quick Start
### Check if API is Ready
```javascript
// All control APIs share the same ready state
if (window.yaze.control.isReady()) {
// APIs are available
}
```
### Basic Example
```javascript
// Switch to Dungeon editor
window.yaze.control.switchEditor('Dungeon');
// Get current editor state
const snapshot = window.yaze.editor.getSnapshot();
console.log('Active editor:', snapshot.editor_type);
// Get room tile data
const roomData = window.yaze.data.getRoomTiles(0);
console.log('Room dimensions:', roomData.width, 'x', roomData.height);
// Get available layouts
const layouts = window.yaze.control.getAvailableLayouts();
console.log('Available layouts:', layouts);
```
---
## window.yaze.control - Editor Control API
Provides programmatic control over the editor UI, ROM operations, and session management.
### Utility
#### isReady()
```javascript
const ready = window.yaze.control.isReady()
// Returns: boolean
```
Checks if the control API is initialized and ready for use.
### Editor Control
#### switchEditor(editorName)
```javascript
window.yaze.control.switchEditor('Dungeon')
window.yaze.control.switchEditor('Overworld')
window.yaze.control.switchEditor('Graphics')
```
**Parameters:**
- `editorName` (string): Name of editor to switch to
- Valid values: `"Overworld"`, `"Dungeon"`, `"Graphics"`, `"Palette"`, `"Sprite"`, `"Music"`, `"Message"`, `"Screen"`, `"Assembly"`, `"Hex"`, `"Agent"`, `"Settings"`
**Returns:**
```json
{
"success": true,
"editor": "Dungeon"
}
```
#### getCurrentEditor()
```javascript
const editor = window.yaze.control.getCurrentEditor()
```
**Returns:**
```json
{
"name": "Dungeon",
"type": 1,
"active": true
}
```
#### getAvailableEditors()
```javascript
const editors = window.yaze.control.getAvailableEditors()
```
**Returns:**
```json
[
{"name": "Overworld", "type": 0},
{"name": "Dungeon", "type": 1},
{"name": "Graphics", "type": 2}
]
```
### Card Control
Cards are dockable panels within each editor.
#### openCard(cardId)
```javascript
window.yaze.control.openCard('dungeon.room_selector')
```
**Parameters:**
- `cardId` (string): Card identifier
**Returns:**
```json
{
"success": true,
"card_id": "dungeon.room_selector",
"visible": true
}
```
#### closeCard(cardId)
```javascript
window.yaze.control.closeCard('dungeon.room_selector')
```
**Returns:**
```json
{
"success": true,
"card_id": "dungeon.room_selector",
"visible": false
}
```
#### toggleCard(cardId)
```javascript
window.yaze.control.toggleCard('dungeon.room_selector')
```
#### getVisibleCards()
```javascript
const cards = window.yaze.control.getVisibleCards()
```
#### getAvailableCards()
```javascript
const cards = window.yaze.control.getAvailableCards()
```
#### getCardsInCategory(category)
```javascript
const cards = window.yaze.control.getCardsInCategory('Dungeon')
```
### Layout Control
#### setCardLayout(layoutName)
```javascript
window.yaze.control.setCardLayout('dungeon_default')
```
**Parameters:**
- `layoutName` (string): Name of layout preset
**Returns:**
```json
{
"success": true,
"layout": "dungeon_default"
}
```
#### getAvailableLayouts()
```javascript
const layouts = window.yaze.control.getAvailableLayouts()
```
**Returns:**
```json
[
"overworld_default",
"dungeon_default",
"graphics_default",
"debug_default",
"minimal",
"all_cards"
]
```
#### saveCurrentLayout(layoutName)
```javascript
window.yaze.control.saveCurrentLayout('my_custom_layout')
```
### Menu/UI Actions
#### triggerMenuAction(actionPath)
```javascript
window.yaze.control.triggerMenuAction('File.Save')
```
**Parameters:**
- `actionPath` (string): Menu path (format: `"Menu.Action"`)
#### getAvailableMenuActions()
```javascript
const actions = window.yaze.control.getAvailableMenuActions()
```
### Session Control
#### getSessionInfo()
```javascript
const info = window.yaze.control.getSessionInfo()
```
**Returns:**
```json
{
"session_index": 0,
"session_count": 1,
"rom_loaded": true,
"rom_filename": "zelda3.sfc",
"rom_title": "THE LEGEND OF ZELDA",
"current_editor": "Dungeon"
}
```
#### createSession()
```javascript
const result = window.yaze.control.createSession()
```
#### switchSession(sessionIndex)
```javascript
window.yaze.control.switchSession(0)
```
### ROM Control
#### getRomStatus()
```javascript
const status = window.yaze.control.getRomStatus()
```
**Returns:**
```json
{
"loaded": true,
"filename": "zelda3.sfc",
"title": "THE LEGEND OF ZELDA",
"size": 1048576,
"dirty": false
}
```
#### readRomBytes(address, count)
```javascript
const bytes = window.yaze.control.readRomBytes(0x10000, 32)
```
**Parameters:**
- `address` (number): ROM address to read from
- `count` (number, optional): Number of bytes (default: 16, max: 256)
#### writeRomBytes(address, bytes)
```javascript
window.yaze.control.writeRomBytes(0x10000, [0x00, 0x01, 0x02, 0x03])
```
**Parameters:**
- `address` (number): ROM address to write to
- `bytes` (array): Array of byte values (0-255)
#### saveRom()
```javascript
const result = window.yaze.control.saveRom()
```
---
## window.yaze.editor - Editor State API
Query current editor state.
### getSnapshot()
```javascript
const snapshot = window.yaze.editor.getSnapshot()
```
Get comprehensive snapshot of current editor state.
### getCurrentRoom()
```javascript
const room = window.yaze.editor.getCurrentRoom()
```
Get current dungeon room (only in Dungeon editor).
### getCurrentMap()
```javascript
const map = window.yaze.editor.getCurrentMap()
```
Get current overworld map (only in Overworld editor).
### getSelection()
```javascript
const selection = window.yaze.editor.getSelection()
```
Get current selection in active editor.
---
## window.yaze.data - Read-only Data API
Access ROM data without modifying it.
### Dungeon Data
#### getRoomTiles(roomId)
```javascript
const tiles = window.yaze.data.getRoomTiles(0)
```
Get tile data for a dungeon room.
**Parameters:**
- `roomId` (number): Room ID (0-295)
#### getRoomObjects(roomId)
```javascript
const objects = window.yaze.data.getRoomObjects(0)
```
Get tile objects in a dungeon room.
#### getRoomProperties(roomId)
```javascript
const props = window.yaze.data.getRoomProperties(0)
```
Get properties for a dungeon room.
### Overworld Data
#### getMapTiles(mapId)
```javascript
const tiles = window.yaze.data.getMapTiles(0)
```
Get tile data for an overworld map.
**Parameters:**
- `mapId` (number): Map ID (0-159)
#### getMapEntities(mapId)
```javascript
const entities = window.yaze.data.getMapEntities(0)
```
Get entities (entrances, exits, items, sprites) on a map.
#### getMapProperties(mapId)
```javascript
const props = window.yaze.data.getMapProperties(0)
```
Get properties for an overworld map.
### Palette Data
#### getPalette(groupName, paletteId)
```javascript
const palette = window.yaze.data.getPalette('ow_main', 0)
```
Get palette colors.
**Parameters:**
- `groupName` (string): Palette group name
- `paletteId` (number): Palette ID within group
#### getPaletteGroups()
```javascript
const groups = window.yaze.data.getPaletteGroups()
```
Get list of available palette groups.
---
## window.yaze.gui - GUI Automation API
For LLM agents to interact with the ImGui UI.
### UI Discovery
#### discover()
```javascript
const elements = window.yaze.gui.discover()
```
Get complete UI element tree for discovery and automation.
#### getElementBounds(elementId)
```javascript
const bounds = window.yaze.gui.getElementBounds('dungeon_room_selector')
```
Get precise bounds for a specific UI element.
#### waitForElement(elementId, timeoutMs)
```javascript
const bounds = await window.yaze.gui.waitForElement('dungeon_room_selector', 5000)
```
Wait for an element to appear.
### Interaction
#### click(target)
```javascript
window.yaze.gui.click('dungeon_room_selector')
// OR
window.yaze.gui.click({x: 100, y: 200})
```
Simulate a click at coordinates or on an element by ID.
#### doubleClick(target)
```javascript
window.yaze.gui.doubleClick('dungeon_room_selector')
```
Simulate a double-click.
#### drag(from, to, steps)
```javascript
window.yaze.gui.drag({x: 0, y: 0}, {x: 100, y: 100}, 10)
```
Simulate a drag operation.
#### pressKey(key, modifiers)
```javascript
window.yaze.gui.pressKey('Enter', {ctrl: true})
```
Send a keyboard event to the canvas.
#### type(text, delayMs)
```javascript
await window.yaze.gui.type('Hello World', 50)
```
Type a string of text.
#### scroll(deltaX, deltaY)
```javascript
window.yaze.gui.scroll(0, 100)
```
Scroll the canvas.
### Canvas & State
#### takeScreenshot(format, quality)
```javascript
const dataUrl = window.yaze.gui.takeScreenshot('png', 0.92)
```
Take a screenshot of the canvas.
#### getCanvasInfo()
```javascript
const info = window.yaze.gui.getCanvasInfo()
```
Get canvas dimensions and position.
#### updateCanvasState()
```javascript
const state = window.yaze.gui.updateCanvasState()
```
Update canvas data-* attributes with current editor state.
#### startAutoUpdate(intervalMs)
```javascript
window.yaze.gui.startAutoUpdate(500)
```
Start automatic canvas state updates.
#### stopAutoUpdate()
```javascript
window.yaze.gui.stopAutoUpdate()
```
Stop automatic canvas state updates.
### Card Management
#### getAvailableCards()
```javascript
const cards = window.yaze.gui.getAvailableCards()
```
Get all available cards with their metadata.
#### showCard(cardId)
```javascript
window.yaze.gui.showCard('dungeon.room_selector')
```
Show a specific card.
#### hideCard(cardId)
```javascript
window.yaze.gui.hideCard('dungeon.room_selector')
```
Hide a specific card.
### Selection
#### getSelection()
```javascript
const selection = window.yaze.gui.getSelection()
```
Get the current selection in the active editor.
#### setSelection(ids)
```javascript
window.yaze.gui.setSelection(['obj_1', 'obj_2'])
```
Set selection programmatically.
---
## window.yaze.agent - AI Agent Integration API
Provides programmatic control over the AI agent system from JavaScript. Enables browser-based AI agents to interact with the built-in chat, manage proposals, and configure AI providers.
### Utility
#### isReady()
```javascript
const ready = window.yaze.agent.isReady()
// Returns: boolean
```
Checks if the agent system is initialized and ready for use. Requires ROM to be loaded.
### Chat Operations
#### sendMessage(message)
```javascript
const result = window.yaze.agent.sendMessage("Help me analyze dungeon room 0")
```
Send a message to the AI agent chat.
**Parameters:**
- `message` (string): User message to send
**Returns:**
```json
{
"success": true,
"status": "queued",
"message": "Help me analyze dungeon room 0"
}
```
#### getChatHistory()
```javascript
const history = window.yaze.agent.getChatHistory()
```
Get the chat message history.
**Returns:**
```json
[
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi! How can I help?"}
]
```
### Configuration
#### getConfig()
```javascript
const config = window.yaze.agent.getConfig()
```
Get current agent configuration.
**Returns:**
```json
{
"provider": "mock",
"model": "",
"ollama_host": "http://localhost:11434",
"verbose": false,
"show_reasoning": true,
"max_tool_iterations": 4
}
```
#### setConfig(config)
```javascript
window.yaze.agent.setConfig({
provider: "ollama",
model: "llama3",
ollama_host: "http://localhost:11434",
verbose: true
})
```
Update agent configuration.
**Parameters:**
- `config` (object): Configuration object with optional fields:
- `provider`: AI provider ID ("mock", "ollama", "gemini")
- `model`: Model name/ID
- `ollama_host`: Ollama server URL
- `verbose`: Enable verbose logging
- `show_reasoning`: Show AI reasoning in responses
- `max_tool_iterations`: Max tool call iterations
**Returns:**
```json
{
"success": true
}
```
#### getProviders()
```javascript
const providers = window.yaze.agent.getProviders()
```
Get list of available AI providers.
**Returns:**
```json
[
{
"id": "mock",
"name": "Mock Provider",
"description": "Testing provider that echoes messages"
},
{
"id": "ollama",
"name": "Ollama",
"description": "Local Ollama server",
"requires_host": true
},
{
"id": "gemini",
"name": "Google Gemini",
"description": "Google's Gemini API",
"requires_api_key": true
}
]
```
### Proposal Management
#### getProposals()
```javascript
const proposals = window.yaze.agent.getProposals()
```
Get list of pending/recent code proposals.
**Returns:**
```json
[
{
"id": "proposal-123",
"status": "pending",
"summary": "Modify room palette"
}
]
```
#### acceptProposal(proposalId)
```javascript
window.yaze.agent.acceptProposal("proposal-123")
```
Accept a proposal and apply its changes.
#### rejectProposal(proposalId)
```javascript
window.yaze.agent.rejectProposal("proposal-123")
```
Reject a proposal.
---
## window.yazeDebug - Debug Utilities
Low-level debugging tools for the WASM environment.
### dumpAll()
```javascript
window.yazeDebug.dumpAll()
```
Dump full application state to console.
### graphics.getDiagnostics()
```javascript
window.yazeDebug.graphics.getDiagnostics()
```
Get graphics subsystem diagnostics.
### memory.getUsage()
```javascript
window.yazeDebug.memory.getUsage()
```
Get current memory usage statistics.
---
## window.aiTools - High-Level Assistant Tools
Helper functions for the Gemini Antigravity agent.
### getAppState()
```javascript
window.aiTools.getAppState()
```
Get high-level application state summary.
### getEditorState()
```javascript
window.aiTools.getEditorState()
```
Get detailed state of the active editor.
### getVisibleCards()
```javascript
window.aiTools.getVisibleCards()
```
List currently visible UI cards.
### getAvailableCards()
```javascript
window.aiTools.getAvailableCards()
```
List all available UI cards.
### showCard(cardId)
```javascript
window.aiTools.showCard(cardId)
```
Show a card (wrapper for `window.yaze.control.openCard`).
### hideCard(cardId)
```javascript
window.aiTools.hideCard(cardId)
```
Hide a card.
### navigateTo(target)
```javascript
window.aiTools.navigateTo(target)
```
Navigate to a specific editor or view.
### getRoomData(roomId)
```javascript
window.aiTools.getRoomData(roomId)
```
Get dungeon room data.
### getMapData(mapId)
```javascript
window.aiTools.getMapData(mapId)
```
Get overworld map data.
### dumpAPIReference()
```javascript
window.aiTools.dumpAPIReference()
```
Dump this API reference to the console.

View File

@@ -0,0 +1,72 @@
# WASM Build Guide
This guide covers building the experimental WebAssembly version of YAZE.
## Prerequisites
1. **Emscripten SDK (emsdk)**
* Install from [emscripten.org](https://emscripten.org/docs/getting_started/downloads.html).
* Activate the environment: `source path/to/emsdk/emsdk_env.sh`
2. **Ninja Build System**
* `brew install ninja` (macOS) or `apt-get install ninja-build` (Linux).
3. **Python 3** (for serving locally).
## Quick Build
Use the helper script for a one-step build:
```bash
# Build Release version (default)
./scripts/build-wasm.sh
# Build Debug version (with assertions and source maps)
./scripts/build-wasm.sh debug
# Build with AI runtime enabled (experimental)
./scripts/build-wasm.sh ai
```
The script handles:
1. CMake configuration using `emcmake`.
2. Compilation with `ninja`.
3. Packaging assets (`src/web` -> `dist/`).
4. Ensuring `coi-serviceworker.js` is placed correctly for SharedArrayBuffer support.
## Serving Locally
You **cannot** open `index.html` directly from the file system due to CORS and SharedArrayBuffer security requirements. You must serve it with specific headers.
```bash
# Use the helper script (Python based)
./scripts/serve-wasm.sh [port]
```
Or manually:
```bash
cd build-wasm/dist
python3 -m http.server 8080
```
*Note: The helper script sets the required `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers.*
## Architecture
* **Entry Point:** `src/main_wasm.cpp` (or `src/main.cpp` with `__EMSCRIPTEN__` blocks).
* **Shell:** `src/web/index.html` template (populated by CMake/Emscripten).
* **Threading:** Uses `SharedArrayBuffer` and `pthread` pool. Requires HTTPS or localhost.
* **Filesystem:** Uses Emscripten's `IDBFS` mounted at `/home/web_user`. Data persists in IndexedDB.
## Troubleshooting
### "SharedArrayBuffer is not defined"
* **Cause:** Missing security headers.
* **Fix:** Ensure you are serving with `COOP: same-origin` and `COEP: require-corp`.
* **Check:** Is `coi-serviceworker.js` loading? It polyfills these headers for GitHub Pages (which doesn't support them natively yet).
### "Out of Memory" / "Asyncify" Crashes
* The build uses `ASYNCIFY` to support blocking calls (like `im_gui_loop`).
* If the stack overflows, check `ASYNCIFY_STACK_SIZE` in `CMakePresets.json`.
* Ensure infinite loops yield back to the browser event loop.
### "ReferenceError: _idb_... is not defined"
* **Cause:** Missing JS library imports.
* **Fix:** Check `CMAKE_EXE_LINKER_FLAGS` in `CMakePresets.json`. It should include `-lidbfs.js`.

View File

@@ -0,0 +1,167 @@
# WASM Debugging Guide
This guide provides a comprehensive walkthrough for debugging and developing with the WASM version of YAZE. It covers common workflows such as loading ROMs, switching editors, and using the built-in debugging tools.
## 1. Getting Started
### Running the WASM Server
To run the WASM version locally, use the provided script:
```bash
./scripts/serve-wasm.sh --dist build-wasm/dist --port 8080
```
This script ensures that the necessary Cross-Origin headers (COOP/COEP) are set, which are required for `SharedArrayBuffer` support.
### Accessing the App
Open your browser (Chrome or Edge recommended) and navigate to:
`http://localhost:8080`
## 2. Loading a ROM
There are two ways to load a ROM file:
1. **File Input**: Click the "Open ROM" folder icon in the top-left toolbar and select your `.sfc` or `.smc` file.
2. **Drag and Drop**: Drag a ROM file directly onto the application window.
Once loaded, the ROM info (name and size) will appear in the header status bar.
## 3. Switching Editors
YAZE provides multiple editors for different aspects of the ROM. You can switch between them using:
* **Dropdown Menu**: Click the "Editor" dropdown in the toolbar and select the desired editor (e.g., Dungeon, Overworld, Graphics).
* **Command Palette**: Press `Ctrl+K` (or `Cmd+K` on Mac) and type "Editor" to filter the list. Select an editor and press Enter.
## 4. Editor Selection Dialog
Some editors, like the Dungeon Editor, may prompt you with a selection dialog when first opened (e.g., to select a dungeon room).
* **Navigation**: Use the mouse to click on a room or item in the list.
* **Search**: If available, use the search box to filter items.
* **Confirm**: Double-click an item or select it and click "OK".
## 5. Setting Layouts
You can customize the workspace layout using presets:
* **Layout Menu**: Click the "Layout" dropdown (grid icon) in the toolbar.
* **Presets**:
* **Default**: Standard layout for the current editor.
* **Minimal**: Maximizes the main view.
* **All Cards**: Shows all available tool cards.
* **Debug**: Opens additional debugging panels (Memory, Disassembly).
## 6. Debugging Tools
### Emulator Controls
Control the emulation state via the "Emulator" dropdown or Command Palette:
* **Run/Pause**: Toggle execution.
* **Step**: Advance one frame.
* **Reset**: Soft reset the emulator.
### Pixel Inspector
Debug palette and rendering issues:
1. Click the "Pixel Inspector" icon (eyedropper) in the toolbar.
2. Hover over the canvas to see pixel coordinates and palette indices.
3. Click to log the current pixel's details to the browser console.
### VSCode-Style Panels
Toggle the bottom panel using the terminal icon (`~` or `` ` `` key) or the "Problems" icon (bug).
* **Terminal**: Execute WASM commands (type `/help` for a list).
* **Problems**: View errors and warnings, including palette validation issues.
* **Output**: General application logs.
### Browser Console
Open the browser's developer tools (F12) to access the `window.yazeDebug` API for advanced debugging:
```javascript
// Dump full application state
window.yazeDebug.dumpAll();
// Get graphics diagnostics
window.yazeDebug.graphics.getDiagnostics();
// Check memory usage
window.yazeDebug.memory.getUsage();
```
## 7. Debugging Memory Access Errors
### Quick Methods to Find Out-of-Bounds Accesses
#### Method 1: Enable Emscripten SAFE_HEAP (Easiest)
Add `-s SAFE_HEAP=1` to your Emscripten flags. This adds bounds checking to all memory accesses and will give you a precise error location.
In `CMakePresets.json`, add to `CMAKE_CXX_FLAGS`:
```json
"CMAKE_CXX_FLAGS": "... -s SAFE_HEAP=1 -s ASSERTIONS=2"
```
**Pros**: Catches all out-of-bounds accesses automatically
**Cons**: Slower execution (debugging only)
#### Method 2: Map WASM Function Number to Source
The error shows `wasm-function[3704]`. You can map this to source:
1. Build with source maps: Add `-g4 -s SOURCE_MAP_BASE='http://localhost:8080/'` to linker flags
2. Use `wasm-objdump` to list functions:
```bash
wasm-objdump -x build-wasm/bin/yaze.wasm | grep -A 5 "func\[3704\]"
```
3. Or use browser DevTools: The stack trace should show function names if source maps are enabled
#### Method 3: Add Logging Wrapper
Create a ROM access wrapper that logs all accesses:
```cpp
#ifdef __EMSCRIPTEN__
class DebugRomAccess {
public:
static bool CheckAccess(const uint8_t* data, size_t offset, size_t size,
size_t rom_size, const char* func_name) {
if (offset + size > rom_size) {
emscripten_log(EM_LOG_ERROR,
"OUT OF BOUNDS: %s accessing offset %zu + %zu (ROM size: %zu)",
func_name, offset, size, rom_size);
return false;
}
return true;
}
};
#endif
```
### Common Pitfalls When Adding Bounds Checking
#### Pitfall 1: DecompressV2 Size Parameter
The `DecompressV2` function has an early-exit when `size == 0`. Always pass `0x800` for the size parameter, not `0`.
```cpp
// CORRECT
DecompressV2(rom.data(), offset, 0x800, 1, rom.size())
// BROKEN - returns empty immediately
DecompressV2(rom.data(), offset, 0, 1, rom.size())
```
#### Pitfall 2: SMC Header Detection
The SMC header detection must use modulo 1MB, not 32KB:
```cpp
// CORRECT
size % 1048576 == 512
// BROKEN - causes false positives
size % 0x8000 == 512
```
## 8. Common Issues & Solutions
* **"SharedArrayBuffer is not defined"**: Ensure you are running the server with `serve-wasm.sh` to set the correct headers.
* **ROM not loading**: Check the browser console for errors. Ensure the file is a valid SNES ROM.
* **Canvas blank**: Try resizing the window or toggling fullscreen to force a redraw.
* **Out of bounds memory access**: Enable SAFE_HEAP (see Section 7) to get precise error locations.

View File

@@ -0,0 +1,537 @@
# WASM Development Guide
**Status:** Active
**Last Updated:** 2025-11-25
**Purpose:** Technical reference for building, debugging, and deploying WASM builds
**Audience:** AI agents and developers working on the YAZE web port
## Quick Start
### Prerequisites
1. Emscripten SDK installed and activated:
```bash
source /path/to/emsdk/emsdk_env.sh
```
2. Verify `emcmake` is available:
```bash
which emcmake
```
### Building
#### Debug Build (Local Development)
For debugging memory errors, stack overflows, and async issues:
```bash
cmake --preset wasm-debug
cmake --build build-wasm --parallel
```
**Debug flags enabled:**
- `-s SAFE_HEAP=1` - Bounds checking on all memory accesses (shows exact error location)
- `-s ASSERTIONS=2` - Verbose runtime assertions
- `-g` - Debug symbols for source mapping
**Output:** `build-wasm/bin/yaze.html`
#### Release Build (Production)
For optimized performance:
```bash
cmake --preset wasm-release
cmake --build build-wasm --parallel
```
**Optimization flags:**
- `-O3` - Maximum optimization
- `-flto` - Link-time optimization
- No debug overhead
**Output:** `build-wasm/bin/yaze.html`
### Using the Build Scripts
#### Full Build and Package
```bash
./scripts/build-wasm.sh
```
This will:
1. Build the WASM app using `wasm-release` preset
2. Package everything into `build-wasm/dist/`
3. Copy all web assets (CSS, JS, icons, etc.)
#### Serve Locally
```bash
./scripts/serve-wasm.sh [--debug] [port]
./scripts/serve-wasm.sh --dist /path/to/dist --port 9000 # custom path (rare)
./scripts/serve-wasm.sh --force --port 8080 # reclaim a busy port
```
Serves from `build-wasm/dist/` on port 8080 by default. The dist reflects the
last preset configured in `build-wasm/` (debug or release), so rebuild with the
desired preset when switching modes.
**Important:** Always serve from the `dist/` directory, not `bin/`!
### Gemini + Antigravity Extension Debugging (Browser)
These steps get Gemini (via the Antigravity browser extension) attached to your local WASM build:
1. Build + serve:
```bash
./scripts/build-wasm.sh debug # or release
./scripts/serve-wasm.sh --force 8080 # serves dist/, frees the port
```
2. In Antigravity, allow/whitelist `http://127.0.0.1:8080` (or your chosen port) and open that URL.
3. Open the Terminal tab (backtick key or bottom panel). Focus is automatic; clicking inside also focuses input.
4. Verify hooks from DevTools console:
```js
window.Module?.calledRun // should be true
window.z3edTerminal?.executeCommand('help')
toggleCollabConsole() // opens collab pane if needed
```
5. If input is stolen by global shortcuts, click inside the panel; terminal/collab inputs now stop propagation of shortcuts while focused.
6. For a clean slate between sessions: `localStorage.clear(); sessionStorage.clear(); location.reload();`
## Common Issues and Solutions
### Memory Access Out of Bounds
**Symptom:** `RuntimeError: memory access out of bounds`
**Solution:**
1. Use `wasm-debug` preset (has `SAFE_HEAP=1`)
2. Rebuild and test
3. The error will show exact function name and line number
4. Fix the bounds check in the code
5. Switch back to `wasm-release` for production
### Stack Overflow
**Symptom:** `Aborted(stack overflow (Attempt to set SP to...))`
**Solution:**
- Stack size is set to 32MB in both presets
- If still overflowing, increase `-s STACK_SIZE=32MB` to 64MB or higher
- Check for deep recursion in ROM loading code
### Async Operation Failed
**Symptom:** `Please compile your program with async support` or `can't start an async op while one is in progress`
**Solution:**
- Both presets have `-s ASYNCIFY=1` enabled
- If you see nested async errors, check for:
- `emscripten_sleep()` called during another async operation
- Multiple `emscripten_async_call()` running simultaneously
- Remove `emscripten_sleep(0)` calls if not needed (loading manager already yields)
### Icons Not Displaying
**Symptom:** Material Symbols icons show as boxes or don't appear
**Solution:**
- Check browser console for CORS errors
- Verify `icons/` directory is copied to `dist/`
- Check network tab to see if Google Fonts is loading
- Icons use Material Symbols from CDN - ensure internet connection
### ROM Loading Fails Silently
**Symptom:** ROM file is dropped/selected but nothing happens
**Solution:**
1. Check browser console for errors
2. Verify ROM file size is valid (Zelda 3 ROMs are ~1MB)
3. Check if `Module.ccall` or `Module._LoadRomFromWeb` exists:
```js
console.log(typeof Module.ccall);
console.log(typeof Module._LoadRomFromWeb);
```
4. If functions are missing, verify `EXPORTED_FUNCTIONS` in `app.cmake` includes them
5. Check `FilesystemManager.ready` is `true` before loading
### Module Initialization Fails
**Symptom:** `createYazeModule is not defined` or similar errors
**Solution:**
- Verify `MODULARIZE=1` and `EXPORT_NAME='createYazeModule'` are in `app.cmake`
- Check that `yaze.js` is loaded before `app.js` tries to call `createYazeModule()`
- Look for JavaScript errors in console during page load
### Directory Listing Instead of App
**Symptom:** Browser shows file list instead of the app
**Solution:**
- Server must run from `build-wasm/dist/` directory
- Use `scripts/serve-wasm.sh` which handles this automatically
- Or manually: `cd build-wasm/dist && python3 -m http.server 8080`
## File Structure
```
build-wasm/
├── bin/ # Raw build output (yaze.html, yaze.wasm, etc.)
└── dist/ # Packaged output for deployment
├── index.html # Main entry point (copied from bin/yaze.html)
├── yaze.js # WASM loader
├── yaze.wasm # Compiled WASM binary
├── *.css # Web stylesheets
├── *.js # Web JavaScript
├── icons/ # PWA icons
└── ...
```
## Key Files
**Build Configuration & Scripts:**
- **`CMakePresets.json`** - Build configurations (`wasm-debug`, `wasm-release`)
- **`src/app/app.cmake`** - WASM linker flags (EXPORTED_FUNCTIONS, MODULARIZE, etc.)
- **`scripts/build-wasm.sh`** - Full build and packaging script
- **`scripts/serve-wasm.sh`** - Local development server
**Web Assets:**
- **`src/web/shell.html`** - HTML shell template
- **`src/web/app.js`** - Main UI logic, module initialization
- **`src/web/core/`** - Core JavaScript functionality (agent automation, control APIs)
- **`src/web/components/`** - UI components (terminal, collaboration, etc.)
- **`src/web/styles/`** - Stylesheets and theme definitions
- **`src/web/pwa/`** - Progressive Web App files (service worker, manifest)
- **`src/web/debug/`** - Debug and development utilities
**C++ Platform Layer:**
- **`src/app/platform/wasm/wasm_control_api.cc`** - Control API implementation (JS interop)
- **`src/app/platform/wasm/wasm_control_api.h`** - Control API declarations
- **`src/app/platform/wasm/wasm_session_bridge.cc`** - Session/collaboration bridge
- **`src/app/platform/wasm/wasm_drop_handler.cc`** - File drop handler
- **`src/app/platform/wasm/wasm_loading_manager.cc`** - Loading progress UI
- **`src/app/platform/wasm/wasm_storage.cc`** - IndexedDB storage with memory-safe error handling
- **`src/app/platform/wasm/wasm_error_handler.cc`** - Error handling with callback cleanup
**GUI Utilities:**
- **`src/app/gui/core/popup_id.h`** - Session-aware ImGui popup ID generation
**CI/CD:**
- **`.github/workflows/web-build.yml`** - CI/CD for GitHub Pages
## CMake WASM Configuration
The WASM build uses specific Emscripten flags in `src/app/app.cmake`:
```cmake
# Key flags for WASM build
-s MODULARIZE=1 # Allows async initialization via createYazeModule()
-s EXPORT_NAME='createYazeModule' # Function name for module factory
-s EXPORTED_RUNTIME_METHODS='[...]' # Runtime methods available in JS
-s EXPORTED_FUNCTIONS='[...]' # C functions callable from JS
```
**Important Exports:**
- `_main`, `_SetFileSystemReady`, `_LoadRomFromWeb` - Core functions
- `_yazeHandleDroppedFile`, `_yazeHandleDropError` - Drag & drop handlers
- `_yazeHandleDragEnter`, `_yazeHandleDragLeave` - Drag state tracking
- `_malloc`, `_free` - Memory allocation for JS interop
**Runtime Methods:**
- `ccall`, `cwrap` - Function calling
- `stringToUTF8`, `UTF8ToString`, `lengthBytesUTF8` - String conversion
- `FS`, `IDBFS` - Filesystem access
- `allocateUTF8` - String allocation helper
## Debugging Tips
1. **Use Browser DevTools:**
- Console tab: WASM errors, async errors
- Network tab: Check if WASM files load
- Sources tab: Source maps (if `-g` flag used)
2. **Enable Verbose Logging:**
- Check browser console for Emscripten messages
- Look for `[symbolize_emscripten.inc]` warnings (can be ignored)
3. **Test Locally First:**
- Always test with `wasm-debug` before deploying
- Use `serve-wasm.sh` to ensure correct directory structure
4. **Memory Issues:**
- Use `wasm-debug` preset for precise error locations
- Check heap resize messages in console
- Verify `INITIAL_MEMORY` is sufficient (64MB default)
## ImGui ID Conflict Prevention
When multiple editors are docked together, ImGui popup IDs must be unique to prevent undefined behavior. The `popup_id.h` utility provides session-aware ID generation.
### Usage
```cpp
#include "app/gui/core/popup_id.h"
// Generate unique popup ID (default session)
std::string id = gui::MakePopupId(gui::EditorNames::kOverworld, "Entrance Editor");
ImGui::OpenPopup(id.c_str());
// Match in BeginPopupModal
if (ImGui::BeginPopupModal(
gui::MakePopupId(gui::EditorNames::kOverworld, "Entrance Editor").c_str(),
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
// ...
ImGui::EndPopup();
}
// With explicit session ID for multi-session support
std::string id = gui::MakePopupId(session_id, "Overworld", "Entrance Editor");
```
### ID Pattern
Pattern: `s{session_id}.{editor}::{popup_name}`
Examples:
- `s0.Overworld::Entrance Editor`
- `s0.Palette::CustomPaletteColorEdit`
- `s1.Dungeon::Room Properties`
### Available Editor Names
Predefined constants in `gui::EditorNames`:
- `kOverworld` - Overworld editor
- `kPalette` - Palette editor
- `kDungeon` - Dungeon editor
- `kGraphics` - Graphics editor
- `kSprite` - Sprite editor
### Why This Matters
Without unique IDs, clicking "Entrance Editor" popup in one docked window may open/close the popup in a different docked editor, causing confusing behavior. The session+editor prefix guarantees uniqueness.
## Deployment
### GitHub Pages
The workflow (`.github/workflows/web-build.yml`) automatically:
1. Builds using `wasm-release` preset
2. Packages to `build-wasm/dist/`
3. Deploys to GitHub Pages
**No manual steps needed** - just push to `master`/`main` branch.
### Manual Deployment
1. Build: `./scripts/build-wasm.sh`
2. Upload `build-wasm/dist/` contents to your web server
3. Ensure server serves `index.html` as default
## Performance Notes
**Debug build (`wasm-debug`):**
- 2-5x slower due to SAFE_HEAP
- 10-20% slower due to ASSERTIONS
- Use only for debugging
**Release build (`wasm-release`):**
- Optimized with `-O3` and `-flto`
- No debug overhead
- Use for production and performance testing
## When to Use Each Preset
**Use `wasm-debug` when:**
- Debugging memory access errors
- Investigating stack overflows
- Testing async operation issues
- Need source maps for debugging
**Use `wasm-release` when:**
- Testing performance
- Preparing for deployment
- CI/CD builds
- Production releases
## Performance Best Practices
Based on the November 2025 performance audit, follow these guidelines when developing for WASM:
### JavaScript Performance
**Event Handling:**
- Avoid adding event listeners to both canvas AND document for the same events
- Use WeakMap to cache processed event objects and avoid redundant work
- Only sanitize/process properties relevant to the specific event type
**Data Structures:**
- Use circular buffers instead of arrays with `shift()` for log/history buffers
- `Array.shift()` is O(n) - avoid in high-frequency code paths
- Example circular buffer pattern:
```javascript
var buffer = new Array(maxSize);
var index = 0;
function add(item) {
buffer[index] = item;
index = (index + 1) % maxSize;
}
```
**Polling/Intervals:**
- Always store interval/timeout handles for cleanup
- Clear intervals when the feature is no longer needed
- Set max retry limits to prevent infinite polling
- Use flags (e.g., `window.YAZE_MODULE_READY`) to track initialization state
### Memory Management
**Service Worker Caching:**
- Implement cache size limits with LRU eviction
- Don't cache indefinitely - set `MAX_CACHE_SIZE` constants
- Clean up old cache versions on activation
**C++ Memory in EM_JS:**
- Always `free()` allocated memory in error paths, not just success paths
- Check if pointers are non-null before freeing
- Example pattern:
```cpp
if (result != 0) {
if (data_ptr) free(data_ptr); // Always free on error
return absl::InternalError(...);
}
```
**Callback Cleanup:**
- Add timeout/expiry tracking for stored callbacks
- Register cleanup handlers for page unload events
- Periodically clean stale entries (e.g., every minute)
### Race Condition Prevention
**Module Initialization:**
- Use explicit ready flags, not just existence checks
- Set ready flag AFTER all initialization is complete
- Pattern:
```javascript
window.YAZE_MODULE_READY = false;
createModule().then(function(instance) {
window.Module = instance;
window.YAZE_MODULE_READY = true; // Set AFTER assignment
});
```
**Promise Initialization:**
- Create promises synchronously before any async operations
- Use synchronous lock patterns to prevent duplicate promises:
```javascript
if (this.initPromise) return this.initPromise;
this.initPromise = new Promise(...); // Immediate assignment
// Then do async work
```
**Redundant Operations:**
- Use flags to track completed operations
- Avoid multiple setTimeout calls for the same operation
- Check flags before executing expensive operations
### File Handling
**Avoid Double Reading:**
- When files are read via FileReader, pass the `Uint8Array` directly
- Don't re-read files in downstream handlers
- Use methods like `handleRomData(filename, data)` instead of `handleRomUpload(file)`
### C++ Mutex Best Practices
**JS Calls and Locks:**
- Always call JS functions OUTSIDE mutex locks
- JS calls can block/yield - holding a lock during JS calls risks deadlock
- Pattern:
```cpp
std::string data;
{
std::lock_guard<std::mutex> lock(mutex_);
data = operations_[handle]->data; // Copy inside lock
}
js_function(data.c_str()); // Call outside lock
```
## JavaScript APIs
The WASM build exposes JavaScript APIs for programmatic control and debugging. These are available after the module initializes.
### API Documentation
**For detailed API reference documentation:**
- **Control & GUI APIs** - See `docs/internal/wasm-yazeDebug-api-reference.md` for `window.yaze.*` API documentation
- `window.yaze.editor` - Query editor state and selection
- `window.yaze.data` - Read-only ROM data access
- `window.yaze.gui` - GUI element discovery and automation
- `window.yaze.control` - Programmatic editor control
- **Debug APIs** - See `docs/internal/wasm-yazeDebug-api-reference.md` for `window.yazeDebug.*` API documentation
- ROM reading, graphics diagnostics, arena status, emulator state
- Palette inspection, timeline analysis
- AI-formatted state dumps for Gemini/Antigravity debugging
### Quick API Check
To verify APIs are available in the browser console:
```javascript
// Check if module is ready
window.yazeDebug.isReady()
// Get ROM status
window.yazeDebug.rom.getStatus()
// Get formatted state for AI
window.yazeDebug.formatForAI()
```
### Gemini/Antigravity Debugging
For AI-assisted debugging workflows using the Antigravity browser extension, see [`docs/internal/agents/wasm-antigravity-playbook.md`](./wasm-antigravity-playbook.md) for detailed instructions on:
- Connecting Gemini to your local WASM build
- Using debug APIs with AI agents
- Common debugging workflows and examples
### Dungeon Object Rendering Debugging
For debugging dungeon object rendering issues (objects appearing at wrong positions, wrong sprites, visual discrepancies), see [`docs/internal/wasm_dungeon_debugging.md`](../wasm_dungeon_debugging.md) Section 12: "Antigravity: Debugging Dungeon Object Rendering Issues".
**Quick Reference for Antigravity:**
```javascript
// 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;
}
```
**Common Issues:**
| Symptom | Check |
|---------|-------|
| Objects invisible | `window.yazeDebug.arena.getStatus().pending_textures` |
| Wrong position | Compare `getRoomObjects()` pixel coords vs visual |
| Wrong colors | `Module.getDungeonPaletteEvents()` |
| Black squares | Wait for deferred texture loading |
## Additional Resources
### Primary WASM Documentation (3 docs total)
- **This Guide** - Building, debugging, CMake config, performance, ImGui ID conflict prevention
- [WASM API Reference](../wasm-yazeDebug-api-reference.md) - Full JavaScript API documentation, Agent Discoverability Infrastructure
- [WASM Antigravity Playbook](./wasm-antigravity-playbook.md) - AI agent workflows, Gemini integration, quick start guides
**Archived:** `archive/wasm-docs-2025/` - Historical WASM docs
### External Resources
- [Emscripten Documentation](https://emscripten.org/docs/getting_started/index.html)
- [WASM Memory Management](https://emscripten.org/docs/porting/emscripten-runtime-environment.html)
- [ASYNCIFY Guide](https://emscripten.org/docs/porting/asyncify.html)

View File

@@ -0,0 +1,838 @@
# 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](./coordination-board.md#2025-11-24-docs-janitor--wasm-docs-consolidation-for-antigravity-gemini)
---
## 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:
```bash
source /path/to/emsdk/emsdk_env.sh
which emcmake # verify it's available
```
### Build Commands
```bash
# 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:
```javascript
// 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:
```javascript
// 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**:
```javascript
// 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:**
```javascript
// 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:**
```javascript
// 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:**
```javascript
window.yazeDebug.cards.showGroup('overworld_editing');
// OR
window.yaze.control.setCardLayout('overworld_default');
```
**Query visible cards:**
```javascript
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:
```javascript
// 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:**
```json
{
"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:
```javascript
// ========== 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:
```bash
./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:
```bash
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
```bash
./scripts/build-wasm.sh debug
./scripts/serve-wasm.sh --debug --force 8080
```
2. **Subsequent C++ changes:** Incremental rebuild
```bash
./scripts/build-wasm.sh debug --incremental
# Server auto-detects new files, just refresh browser
```
3. **JS/CSS only changes:** No rebuild needed
```bash
# Copy changed files directly
cp src/web/core/filesystem_manager.js build-wasm/dist/core/
# Refresh browser
```
4. **Verify before lengthy debug session:**
```javascript
// 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:
```javascript
(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:
```javascript
// 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:
```javascript
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:
```javascript
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:
```cpp
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
```javascript
// Is module ready?
window.yazeDebug.isReady()
// Complete state dump as JSON
window.yazeDebug.dumpAll()
// Human-readable summary for AI
window.yazeDebug.formatForAI()
```
### ROM + Emulator
```javascript
// 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
```javascript
// 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
```javascript
// 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)
```javascript
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:
```javascript
// 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:
```text
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:
```javascript
// 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