backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
358
docs/internal/wasm/agent_api_testing.md
Normal file
358
docs/internal/wasm/agent_api_testing.md
Normal 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
|
||||
925
docs/internal/wasm/api_reference.md
Normal file
925
docs/internal/wasm/api_reference.md
Normal 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.
|
||||
72
docs/internal/wasm/build-guide.md
Normal file
72
docs/internal/wasm/build-guide.md
Normal 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`.
|
||||
167
docs/internal/wasm/debugging.md
Normal file
167
docs/internal/wasm/debugging.md
Normal 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.
|
||||
537
docs/internal/wasm/dev_guide.md
Normal file
537
docs/internal/wasm/dev_guide.md
Normal 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)
|
||||
838
docs/internal/wasm/playbook.md
Normal file
838
docs/internal/wasm/playbook.md
Normal 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
|
||||
Reference in New Issue
Block a user