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

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

View File

@@ -1,289 +0,0 @@
# AI API & Agentic Workflow Enhancement - Handoff Document
**Date**: 2025-01-XX
**Status**: Phase 1 Complete, Phase 2-4 Pending
**Branch**: (to be determined)
## Executive Summary
This document tracks progress on transforming Yaze into an AI-native platform with unified model management, API interface, and enhanced agentic workflows. Phase 1 (Unified Model Management) is complete. Phases 2-4 require implementation.
## Completed Work (Phase 1)
### 1. Unified AI Model Management ✅
#### Core Infrastructure
- **`ModelInfo` struct** (`src/cli/service/ai/common.h`)
- Standardized model representation across all providers
- Fields: `name`, `display_name`, `provider`, `description`, `family`, `parameter_size`, `quantization`, `size_bytes`, `is_local`
- **`ModelRegistry` class** (`src/cli/service/ai/model_registry.h/.cc`)
- Singleton pattern for managing multiple `AIService` instances
- `RegisterService()` - Add service instances
- `ListAllModels()` - Aggregate models from all registered services
- Thread-safe with mutex protection
#### AIService Interface Updates
- **`AIService::ListAvailableModels()`** - Virtual method returning `std::vector<ModelInfo>`
- **`AIService::GetProviderName()`** - Virtual method returning provider identifier
- Default implementations provided in base class
#### Provider Implementations
- **`OllamaAIService::ListAvailableModels()`**
- Queries `/api/tags` endpoint
- Maps Ollama's model structure to `ModelInfo`
- Handles size, quantization, family metadata
- **`GeminiAIService::ListAvailableModels()`**
- Queries Gemini API `/v1beta/models` endpoint
- Falls back to known defaults if API key missing
- Filters for `gemini*` models
#### UI Integration
- **`AgentChatWidget::RefreshModels()`**
- Registers Ollama and Gemini services with `ModelRegistry`
- Aggregates models from all providers
- Caches results in `model_info_cache_`
- **Header updates** (`agent_chat_widget.h`)
- Replaced `ollama_model_info_cache_` with unified `model_info_cache_`
- Replaced `ollama_model_cache_` with `model_name_cache_`
- Replaced `ollama_models_loading_` with `models_loading_`
### Files Modified
- `src/cli/service/ai/common.h` - Added `ModelInfo` struct
- `src/cli/service/ai/ai_service.h` - Added `ListAvailableModels()` and `GetProviderName()`
- `src/cli/service/ai/ollama_ai_service.h/.cc` - Implemented model listing
- `src/cli/service/ai/gemini_ai_service.h/.cc` - Implemented model listing
- `src/cli/service/ai/model_registry.h/.cc` - New registry class
- `src/app/editor/agent/agent_chat_widget.h/.cc` - Updated to use registry
## In Progress
### UI Rendering Updates (Partial)
The `RenderModelConfigControls()` function in `agent_chat_widget.cc` still references old Ollama-specific code. It needs to be updated to:
- Use unified `model_info_cache_` instead of `ollama_model_info_cache_`
- Display models from all providers in a single list
- Filter by provider when a specific provider is selected
- Show provider badges/indicators for each model
**Location**: `src/app/editor/agent/agent_chat_widget.cc:2083-2318`
**Current State**: Function still has provider-specific branches that should be unified.
## Remaining Work
### Phase 2: API Interface & Headless Mode
#### 2.1 HTTP Server Implementation
**Goal**: Expose Yaze functionality via REST API for external agents
**Tasks**:
1. Create `HttpServer` class in `src/cli/service/api/`
- Use `httplib` (already in tree)
- Start on configurable port (default 8080)
- Handle CORS if needed
2. Implement endpoints:
- `GET /api/v1/models` - List all available models (delegate to `ModelRegistry`)
- `POST /api/v1/chat` - Send prompt to agent
- Request: `{ "prompt": "...", "provider": "ollama", "model": "...", "history": [...] }`
- Response: `{ "text_response": "...", "tool_calls": [...], "commands": [...] }`
- `POST /api/v1/tool/{tool_name}` - Execute specific tool
- Request: `{ "args": {...} }`
- Response: `{ "result": "...", "status": "ok|error" }`
- `GET /api/v1/health` - Health check
- `GET /api/v1/rom/status` - ROM loading status
3. Integration points:
- Initialize server in `yaze.cc` main() or via CLI flag
- Share `Rom*` context with API handlers
- Use `ConversationalAgentService` for chat endpoint
- Use `ToolDispatcher` for tool endpoint
**Files to Create**:
- `src/cli/service/api/http_server.h`
- `src/cli/service/api/http_server.cc`
- `src/cli/service/api/api_handlers.h`
- `src/cli/service/api/api_handlers.cc`
**Dependencies**: `httplib`, `nlohmann/json` (already available)
### Phase 3: Enhanced Agentic Workflows
#### 3.1 Tool Expansion
**FileSystemTool** (`src/cli/handlers/tools/filesystem_commands.h/.cc`)
- **Purpose**: Allow agent to read/write files outside ROM (e.g., `src/` directory)
- **Safety**: Require user confirmation or explicit scope configuration
- **Commands**:
- `filesystem-read <path>` - Read file contents
- `filesystem-write <path> <content>` - Write file (with confirmation)
- `filesystem-list <directory>` - List directory contents
- `filesystem-search <pattern>` - Search for files matching pattern
**BuildTool** (`src/cli/handlers/tools/build_commands.h/.cc`)
- **Purpose**: Trigger builds from within agent
- **Commands**:
- `build-cmake <build_dir>` - Run cmake configuration
- `build-ninja <build_dir>` - Run ninja build
- `build-status` - Check build status
- `build-errors` - Parse and return compilation errors
**Integration**:
- Add to `ToolDispatcher::ToolCallType` enum
- Register in `ToolDispatcher::CreateHandler()`
- Add to `ToolDispatcher::ToolPreferences` struct
- Update UI toggles in `AgentChatWidget::RenderToolingControls()`
#### 3.2 Editor State Context
**Goal**: Feed editor state (open files, compilation errors) into agent context
**Tasks**:
1. Create `EditorState` struct capturing:
- Open file paths
- Active editor type
- Compilation errors (if any)
- Recent changes
2. Inject into agent prompts:
- Add to `PromptBuilder::BuildPromptFromHistory()`
- Include in system prompt when editor state changes
3. Update `ConversationalAgentService`:
- Add `SetEditorState(EditorState*)` method
- Pass to `PromptBuilder` when building prompts
**Files to Create/Modify**:
- `src/cli/service/agent/editor_state.h` (new)
- `src/cli/service/ai/prompt_builder.h/.cc` (modify)
### Phase 4: Refactoring
#### 4.1 ToolDispatcher Structured Output
**Goal**: Return JSON instead of capturing stdout
**Current State**: `ToolDispatcher::Dispatch()` returns `absl::StatusOr<std::string>` by capturing stdout from command handlers.
**Proposed Changes**:
1. Create `ToolResult` struct:
```cpp
struct ToolResult {
std::string output; // Human-readable output
nlohmann::json data; // Structured data (if applicable)
bool success;
std::vector<std::string> warnings;
};
```
2. Update command handlers to return `ToolResult`:
- Modify base `CommandHandler` interface
- Update each handler implementation
- Keep backward compatibility with `OutputFormatter` for CLI
3. Update `ToolDispatcher::Dispatch()`:
- Return `absl::StatusOr<ToolResult>`
- Convert to JSON for API responses
- Keep string output for CLI compatibility
**Files to Modify**:
- `src/cli/service/agent/tool_dispatcher.h/.cc`
- `src/cli/handlers/*/command_handlers.h/.cc` (all handlers)
- `src/cli/service/agent/command_handler.h` (base interface)
**Migration Strategy**:
- Add new `ExecuteStructured()` method alongside existing `Execute()`
- Gradually migrate handlers
- Keep old path for CLI until migration complete
## Technical Notes
### Model Registry Usage Pattern
```cpp
// Register services
auto& registry = cli::ModelRegistry::GetInstance();
registry.RegisterService(std::make_shared<OllamaAIService>(ollama_config));
registry.RegisterService(std::make_shared<GeminiAIService>(gemini_config));
// List all models
auto models_or = registry.ListAllModels();
// Returns unified list sorted by name
```
### API Key Management
- Gemini API key: Currently stored in `AgentConfigState::gemini_api_key`
- Consider: Environment variable fallback, secure storage
- Future: Support multiple API keys for different providers
### Thread Safety
- `ModelRegistry` uses mutex for thread-safe access
- `HttpServer` should handle concurrent requests (httplib supports this)
- `ToolDispatcher` may need locking if shared across threads
## Testing Checklist
### Phase 1 (Model Management)
- [ ] Verify Ollama models appear in unified list
- [ ] Verify Gemini models appear in unified list
- [ ] Test model refresh with multiple providers
- [ ] Test provider filtering in UI
- [ ] Test model selection and configuration
### Phase 2 (API)
- [ ] Test `/api/v1/models` endpoint
- [ ] Test `/api/v1/chat` with different providers
- [ ] Test `/api/v1/tool/*` endpoints
- [ ] Test error handling (missing ROM, invalid tool, etc.)
- [ ] Test concurrent requests
- [ ] Test CORS if needed
### Phase 3 (Tools)
- [ ] Test FileSystemTool with read operations
- [ ] Test FileSystemTool write confirmation flow
- [ ] Test BuildTool cmake/ninja execution
- [ ] Test BuildTool error parsing
- [ ] Test editor state injection into prompts
### Phase 4 (Refactoring)
- [ ] Verify all handlers return structured output
- [ ] Test API endpoints with new format
- [ ] Verify CLI still works with old format
- [ ] Performance test (no regressions)
## Known Issues
1. **UI Rendering**: `RenderModelConfigControls()` still has provider-specific code that should be unified
2. **Model Info Display**: Some fields from `ModelInfo` (like `quantization`, `modified_at`) are not displayed in unified view
3. **Error Handling**: Model listing failures are logged but don't prevent other providers from loading
## Next Steps (Priority Order)
1. **Complete UI unification** - Update `RenderModelConfigControls()` to use unified model list
2. **Implement HTTP Server** - Start with basic server and `/api/v1/models` endpoint
3. **Add chat endpoint** - Wire up `ConversationalAgentService` to API
4. **Add tool endpoint** - Expose `ToolDispatcher` via API
5. **Implement FileSystemTool** - Start with read-only operations
6. **Implement BuildTool** - Basic cmake/ninja execution
7. **Refactor ToolDispatcher** - Begin structured output migration
## References
- Plan document: `plan-yaze-api-agentic-workflow-enhancement.plan.md`
- Model Registry: `src/cli/service/ai/model_registry.h`
- AIService interface: `src/cli/service/ai/ai_service.h`
- ToolDispatcher: `src/cli/service/agent/tool_dispatcher.h`
- httplib docs: (in `ext/httplib/`)
## Questions for Next Developer
1. Should the HTTP server be enabled by default or require a flag?
2. What port should be used? (8080 suggested, but configurable?)
3. Should FileSystemTool require explicit user approval per operation or a "trusted scope"?
4. Should BuildTool be limited to specific directories (e.g., `build/`) for safety?
5. How should API authentication work? (API key? Localhost-only? None?)
---
**Last Updated**: 2025-01-XX
**Contact**: (to be filled)

View File

@@ -1,175 +0,0 @@
# CI Test Strategy
## Overview
The yaze project uses a **tiered testing strategy** to balance CI speed with comprehensive coverage. This document explains the strategy, configuration, and how to add tests.
**Key Distinction:**
- **Default Tests** (PR/Push CI): Stable, fast, no external dependencies - ALWAYS run, MUST pass
- **Optional Tests** (Nightly CI): ROM-dependent, experimental, benchmarks - Run nightly, non-blocking
Tier breakdown:
- **Tier 1 (PR/Push CI)**: Fast feedback loop with stable tests only (~5-10 minutes total)
- **Tier 2 (Nightly CI)**: Full test suite including heavy/flaky/ROM tests (~30-60 minutes total)
- **Tier 3 (Configuration Matrix)**: Weekly cross-platform configuration validation
## Test Tiers
### Tier 1: PR/Push Tests (ci.yml)
**When:** Every PR and push to master/develop
**Duration:** 5-10 minutes per platform
**Coverage:**
- Stable tests (unit + integration that don't require ROM)
- Smoke tests for GUI framework validation (Linux only)
- Basic build validation across all platforms
**Test Labels:**
- `stable`: Core functionality tests with stable contracts
- Includes both unit and integration tests that are fast and reliable
### Tier 2: Nightly Tests (nightly.yml)
**When:** Nightly at 3 AM UTC (or manual trigger)
**Duration:** 30-60 minutes total
**Coverage:**
- ROM-dependent tests (with test ROM if available)
- Experimental AI tests (with Ollama integration)
- GUI E2E tests (full workflows with ImGuiTestEngine)
- Performance benchmarks
- Extended integration tests with all features enabled
**Test Labels:**
- `rom_dependent`: Tests requiring actual Zelda3 ROM
- `experimental`: AI and unstable feature tests
- `gui`: Full GUI automation tests
- `benchmark`: Performance regression tests
### Tier 3: Configuration Matrix (matrix-test.yml)
**When:** Nightly at 2 AM UTC (or manual trigger)
**Duration:** 20-30 minutes
**Coverage:**
- Different feature combinations (minimal, gRPC-only, full AI, etc.)
- Platform-specific configurations
- Build configuration validation
## CTest Label System
Tests are organized with labels for selective execution:
```cmake
# In test/CMakeLists.txt
yaze_add_test_suite(yaze_test_stable "stable" OFF ${STABLE_TEST_SOURCES})
yaze_add_test_suite(yaze_test_rom_dependent "rom_dependent" OFF ${ROM_DEPENDENT_SOURCES})
yaze_add_test_suite(yaze_test_gui "gui;experimental" ON ${GUI_TEST_SOURCES})
yaze_add_test_suite(yaze_test_experimental "experimental" OFF ${EXPERIMENTAL_SOURCES})
yaze_add_test_suite(yaze_test_benchmark "benchmark" OFF ${BENCHMARK_SOURCES})
```
## Running Tests Locally
### Run specific test categories:
```bash
# Stable tests only (what PR CI runs)
ctest -L stable --output-on-failure
# ROM-dependent tests
ctest -L rom_dependent --output-on-failure
# Experimental tests
ctest -L experimental --output-on-failure
# GUI tests headlessly
./build/bin/yaze_test_gui -nogui
# Benchmarks
./build/bin/yaze_test_benchmark
```
### Using test executables directly:
```bash
# Run stable test suite
./build/bin/yaze_test_stable
# Run with specific filter
./build/bin/yaze_test_stable --gtest_filter="*Overworld*"
# Run GUI smoke tests only
./build/bin/yaze_test_gui -nogui --gtest_filter="*Smoke*"
```
## Test Presets
CMakePresets.json defines test presets for different scenarios:
- `stable`: Run stable tests only (no ROM dependency)
- `unit`: Run unit tests only
- `integration`: Run integration tests only
- `stable-ai`: Stable tests with AI stack enabled
- `unit-ai`: Unit tests with AI stack enabled
Example usage:
```bash
# Configure with preset
cmake --preset ci-linux
# Run tests with preset
ctest --preset stable
```
## Adding New Tests
### For PR/Push CI (Tier 1 - Default):
Add to `STABLE_TEST_SOURCES` in `test/CMakeLists.txt`:
- **Requirements**: Must not require ROM files, must complete in < 30 seconds, stable behavior (no flakiness)
- **Examples**: Unit tests, basic integration tests, framework smoke tests
- **Location**: `test/unit/`, `test/integration/` (excluding subdirs below)
- **Labels assigned**: `stable`
### For Nightly CI (Tier 2 - Optional):
Add to appropriate test suite in `test/CMakeLists.txt`:
- `ROM_DEPENDENT_TEST_SOURCES` - Tests requiring ROM
- Location: `test/e2e/rom_dependent/` or `test/integration/` (ROM-gated with `#ifdef`)
- Labels: `rom_dependent`
- `GUI_TEST_SOURCES` / `EXPERIMENTAL_TEST_SOURCES` - Experimental features
- Location: `test/integration/ai/` for AI tests
- Labels: `experimental`
- `BENCHMARK_TEST_SOURCES` - Performance tests
- Location: `test/benchmarks/`
- Labels: `benchmark`
## CI Optimization Tips
### For Faster PR CI:
1. Keep tests in STABLE_TEST_SOURCES minimal
2. Use `continue-on-error: true` for non-critical tests
3. Leverage caching (CPM, sccache, build artifacts)
4. Run platform tests in parallel
### For Comprehensive Coverage:
1. Use nightly.yml for heavy tests
2. Schedule at low-traffic times
3. Upload artifacts for debugging failures
4. Use longer timeouts for integration tests
## Monitoring and Alerts
### PR/Push Failures:
- Block merging if stable tests fail
- Immediate feedback in PR comments
- Required status checks on protected branches
### Nightly Failures:
- Summary report in GitHub Actions
- Optional Slack/email notifications for failures
- Artifacts retained for 30 days for debugging
- Non-blocking for development
## Future Improvements
1. **Test Result Trends**: Track test success rates over time
2. **Flaky Test Detection**: Automatically identify and quarantine flaky tests
3. **Performance Tracking**: Graph benchmark results over commits
4. **ROM Test Infrastructure**: Secure storage/retrieval of test ROM
5. **Parallel Test Execution**: Split test suites across multiple runners

View File

@@ -1,20 +0,0 @@
# Gemini Developer Guide
This guide serves as an index for the internal architecture documentation and improvement plans generated by Gemini agents.
## Architecture Documentation
- **[Dungeon Editor System](architecture/dungeon_editor_system.md)** - Overview of editor components
- **[Graphics System](architecture/graphics_system.md)** - Graphics resource management details
- **[Undo/Redo System](architecture/undo_redo_system.md)** - Undo/Redo command pattern details
- **[Room Data Persistence](architecture/room_data_persistence.md)** - Room loading/saving details
- **[Overworld Editor System](architecture/overworld_editor_system.md)** - Architecture of the overworld editor
- **[Overworld Map Data](architecture/overworld_map_data.md)** - Internal structure of overworld maps
- **[ZSCustomOverworld Integration](architecture/zscustomoverworld_integration.md)** - Details on ZSO support
- **[Graphics System Architecture](architecture/graphics_system_architecture.md)** - Overview of the graphics pipeline and editors
- **[Message System Architecture](architecture/message_system.md)** - Overview of the dialogue system
## Improvement Plans
- **[Graphics Improvement Plan](plans/graphics_system_improvement_plan.md)** - Roadmap for graphics system enhancements
- **[Message System Improvement Plan](plans/message_system_improvement_plan.md)** - Roadmap for dialogue/translation tools

View File

@@ -1,31 +1,42 @@
# YAZE Handbook
# YAZE Internal Documentation
Internal documentation for planning, AI agents, research, and historical build notes. These
files are intentionally excluded from the public Doxygen site so they can remain verbose and
speculative without impacting the published docs.
**Last Updated**: December 8, 2025
## Sections
- `agents/` z3ed and AI agent playbooks, command abstractions, and debugging guides.
- `blueprints/` architectural proposals, refactors, and technical deep dives.
- `roadmaps/` sequencing, feature parity analysis, and postmortems.
- `research/` emulator investigations, timing analyses, web ideas, and development trackers.
- `legacy/` superseded build guides and other historical docs kept for reference.
- `agents/` includes the coordination board, personas, GH Actions remote guide, and helper scripts
(`scripts/agents/`) for common agent workflows.
Internal documentation for planning, AI agents, research, and historical build notes. These files are intentionally excluded from the public Doxygen site so they can remain verbose and speculative.
When adding new internal docs, place them under the appropriate subdirectory here instead of
`docs/`.
## Quick Links
## Version Control & Safety Guidelines
- **Coordinate before forceful changes**: Never rewrite history on shared branches. Use dedicated
feature/bugfix branches (see `docs/public/developer/git-workflow.md`) and keep `develop/master`
clean.
- **Back up ROMs and assets**: Treat sample ROMs, palettes, and project files as irreplaceable. Work
on copies, and enable the editors automatic backup setting before testing risky changes.
- **Run scripts/verify-build-environment.* after pulling significant build changes** to avoid
drifting tooling setups.
- **Document risky operations**: When touching migrations, asset packers, or scripts that modify
files in bulk, add notes under `docs/internal/roadmaps/` or `blueprints/` so others understand the
impact.
- **Use the coordination board** for any change that affects multiple personas or large parts of the
tree; log blockers and handoffs to reduce conflicting edits.
- **Active Work**: [Coordination Board](agents/coordination-board.md)
- **Roadmap**: [roadmap.md](roadmap.md)
- **Doc Hygiene Rules**: [agents/doc-hygiene.md](agents/doc-hygiene.md)
- **Templates**: [templates/](templates/)
## Directory Structure
| Directory | Purpose |
|-----------|---------|
| `agents/` | AI agent coordination, personas, and board |
| `architecture/` | System design and architectural documentation |
| `archive/` | Retired plans, completed features, closed investigations, and maintenance logs |
| `debug/` | Debugging guides, active logs, and accuracy reports |
| `hand-off/` | Active handoff documents for in-progress work |
| `plans/` | Active implementation plans and roadmaps |
| `research/` | Exploratory notes, ideas, and technical analysis |
| `templates/` | Document templates (checklists, initiatives) |
| `testing/` | Test infrastructure configuration and strategy |
| `wasm/` | WASM/Web port documentation and guides |
| `zelda3/` | Game-specific documentation (ALTTP internals) |
| `roadmap.md` | Master project roadmap |
## Doc Hygiene
- **Single Source of Truth**: Maintain one canonical spec per initiative.
- **Templates**: Use `templates/` for new initiatives and release checklists.
- **Archiving**: Move completed specs to `archive/completed_features/` or `archive/investigations/`.
- **Coordination**: Use the [Coordination Board](agents/coordination-board.md) for active tasks.
## Version Control & Safety
- **Coordinate before forceful changes**: Never rewrite history on shared branches.
- **Back up ROMs and assets**: Work on copies, enable automatic backup.
- **Run `scripts/verify-build-environment.*`** after pulling significant build changes.

View File

@@ -0,0 +1,75 @@
# Agent Coordination Hub
Welcome to the `yaze` internal agent workspace. This directory contains the rules, roles, and records for AI agents contributing to the project.
## Quick Start
1. **Identify Your Role**: Check [personas.md](./personas.md) to choose the correct Agent ID.
2. **Load Your Prompt**: Open `.claude/agents/<agent-id>.md` for your personas system prompt (available to all agents).
3. **Follow the Protocol**: Read [AGENTS.md](../../../AGENTS.md) for rules on communication, task logging, and handoffs.
4. **Check Status**: Review the [coordination-board.md](./coordination-board.md) for active tasks and blockers.
5. **Keep docs lean**: Use [doc-hygiene.md](./doc-hygiene.md) and avoid creating duplicate specs; archive idle docs.
## Documentation Index
### Core Coordination (Keep Visible)
| File | Purpose |
|------|---------|
| [AGENTS.md](../../../AGENTS.md) | **The Core Protocol.** Read this first. Defines how to work. |
| [personas.md](./personas.md) | **Who is Who.** Canonical list of Agent IDs and their scopes. |
| [.claude/agents/*](../../../.claude/agents) | **System Prompts.** Persona-specific prompts for all agents. |
| [coordination-board.md](./coordination-board.md) | **The Live Board.** Shared state, active tasks, and requests. |
| [collaboration-framework.md](./collaboration-framework.md) | **Team Organization.** Architecture vs Automation team structure and protocols. |
### Active Projects
| File | Purpose | Status |
|------|---------|--------|
| [initiative-v040.md](./initiative-v040.md) | v0.4.0 development plan with 5 parallel workstreams | ACTIVE |
| [handoff-sidebar-menubar-sessions.md](./handoff-sidebar-menubar-sessions.md) | UI systems architecture reference | Active Reference |
| [wasm-development-guide.md](./wasm-development-guide.md) | WASM build and development guide | UPDATED |
| [wasm-antigravity-playbook.md](./wasm-antigravity-playbook.md) | WASM with Gemini integration and debugging | UPDATED |
| [web-port-handoff.md](./web-port-handoff.md) | WASM web build status and blockers | IN_PROGRESS |
### Documentation Hygiene
| File | Purpose |
|------|---------|
| [doc-hygiene.md](./doc-hygiene.md) | Rules to keep specs/notes lean and archived on time. |
## Tools
Agents have built-in CLI capabilities to assist with this workflow:
* `z3ed agent todo`: Manage your personal task list.
* `z3ed agent handoff`: Generate context bundles for the next agent.
* `z3ed agent chat`: (If enabled) Inter-agent communication channel.
## Picking the right agent
- **ai-infra-architect**: AI/agent infra, MCP/gRPC, z3ed tooling, model plumbing.
- **backend-infra-engineer**: Build/packaging/toolchains, CI reliability, release plumbing.
- **imgui-frontend-engineer**: ImGui/editor UI, renderer/backends, canvas/docking UX.
- **snes-emulator-expert**: Emulator core (CPU/APU/PPU), performance/accuracy/debugging.
- **zelda3-hacking-expert**: Gameplay/ROM logic, data formats, hacking workflows.
- **test-infrastructure-expert**: Test harnesses, CTest/gMock infra, flake/bloat triage.
- **docs-janitor**: Docs/process hygiene, onboarding, checklists.
Pick the persona that owns the dominant surface of your task; cross-surface work should add both owners to the board entry.
## Build & Test
Default builds now share `build/` (native) and `build-wasm/` (WASM). If you need isolation, set `YAZE_BUILD_DIR` or create a local `CMakeUserPresets.json`.
* **Build**: `./scripts/agent_build.sh [target]` (Default target: `yaze`)
* **Test**: `./scripts/agent_build.sh yaze_test && ./build/bin/yaze_test`
* **Directory**: `build/` by default (override via `YAZE_BUILD_DIR`).
## Archive
This directory maintains a **lean, active documentation set**. Historical or reference-only documents are organized in `archive/`:
### Archived Categories
- **large-ref-docs/** (148KB) - Large reference documents (40KB+): dungeon rendering, ZSOW, design specs
- **foundation-docs-old/** (68KB) - Older foundational docs (October) on agent architecture and CLI design
- **utility-tools/** (52KB) - Tool documentation: filesystem, dev-assist, modularity, AI dev tools
- **wasm-planning-2025/** (64KB) - Earlier WASM planning iterations
- **testing-docs-2025/** (112KB) - Test infrastructure and CI documentation archives
- **gemini-session-2025-11-23/** (40KB) - Gemini-specific session context and task planning
- **plans-2025-11/** (56KB) - Completed or archived feature/project plans
- **legacy-2025-11/** (28KB) - Completed initiatives and coordination improvements
- **session-handoffs/** (24KB) - Agent handoff and onboarding documents
- **reports/** (24KB) - Audit reports and documentation assessments
**Target: 10-15 active files in root directory. Archive completed work within 1-2 weeks of completion.**

View File

@@ -0,0 +1,182 @@
# Documentation Cleanup - November 27, 2025
## Summary
Comprehensive review and update of YAZE documentation, focusing on public-facing docs, web app support, and organizational cleanup.
## Changes Made
### 1. Web App Documentation
**Created: `docs/public/usage/web-app.md`**
- Comprehensive guide for the WASM web application
- Clearly marked as **Preview** status (not production-ready)
- Detailed feature status table showing incomplete editors
- Browser requirements and compatibility
- Performance tips and troubleshooting
- Comparison table: Web vs Desktop
- Developer tools and API references
- Deployment instructions
- Privacy and storage information
**Key Points:**
- ⚠️ Emphasized preview/experimental status throughout
- Listed editor completeness accurately (Preview/Incomplete vs Working)
- Recommended desktop build for serious ROM hacking
- Linked to internal technical docs for developers
### 2. Main README Updates
**Updated: `README.md`**
- Added web preview mention in highlights section
- Added "Web App (Preview)" to Applications & Workflows
- Clearly linked to web-app.md guide
- Maintained focus on desktop as primary platform
### 3. Public Docs Index
**Updated: `docs/public/index.md`**
- Added Web App (Preview) to Usage Guides section
- Placed at top for visibility
### 4. Directory Organization
**Moved technical implementation docs to internal:**
- `docs/web/drag-drop-rom-loading.md``docs/internal/web-drag-drop-implementation.md`
- `docs/wasm/patch_export.md``docs/internal/wasm-patch-export-implementation.md`
- Removed empty `docs/web/` and `docs/wasm/` directories
**Organized format documentation:**
Moved to `docs/public/reference/` for better discoverability:
- `SAVE_STATE_FORMAT.md`
- `SNES_COMPRESSION.md`
- `SNES_GRAPHICS.md`
- `SYMBOL_FORMAT.md`
- `ZSM_FORMAT.md`
**Updated: `docs/public/reference/rom-reference.md`**
- Added "Additional Format Documentation" section
- Linked to all format specification docs
- Updated last modified date to November 27, 2025
### 5. Documentation Accuracy
**Updated: `docs/public/build/platform-compatibility.md`**
- Updated "Last Updated" from October 9, 2025 to November 27, 2025
**Reviewed for accuracy:**
-`docs/public/build/quick-reference.md` - Accurate
-`docs/public/build/build-from-source.md` - Accurate
-`docs/public/build/presets.md` - Accurate
-`docs/public/developer/architecture.md` - Accurate (updated Nov 2025)
-`docs/public/developer/testing-quick-start.md` - Accurate
### 6. Coordination Board
**Updated: `docs/internal/agents/coordination-board.md`**
- Added entry for docs-janitor work session
- Marked status as COMPLETE
- Listed all changes made
## File Structure After Cleanup
```
docs/
├── public/
│ ├── build/ [5 docs - build system]
│ ├── deployment/ [1 doc - collaboration server]
│ ├── developer/ [18 docs - developer guides]
│ ├── examples/ [1 doc - code examples]
│ ├── guides/ [1 doc - z3ed workflows]
│ ├── overview/ [1 doc - getting started]
│ ├── reference/ [8 docs - ROM & format specs] ⭐ IMPROVED
│ │ ├── rom-reference.md
│ │ ├── SAVE_STATE_FORMAT.md ⬅️ MOVED HERE
│ │ ├── SNES_COMPRESSION.md ⬅️ MOVED HERE
│ │ ├── SNES_GRAPHICS.md ⬅️ MOVED HERE
│ │ ├── SYMBOL_FORMAT.md ⬅️ MOVED HERE
│ │ └── ZSM_FORMAT.md ⬅️ MOVED HERE
│ ├── usage/ [4 docs including web-app] ⭐ NEW
│ │ ├── web-app.md ⬅️ NEW
│ │ ├── dungeon-editor.md
│ │ ├── overworld-loading.md
│ │ └── z3ed-cli.md
│ ├── index.md
│ └── README.md
├── internal/
│ ├── agents/ [Agent coordination & playbooks]
│ ├── architecture/ [System architecture docs]
│ ├── blueprints/ [Refactoring plans]
│ ├── plans/ [Implementation plans]
│ ├── reports/ [Investigation reports]
│ ├── roadmaps/ [Feature roadmaps]
│ ├── testing/ [Test infrastructure]
│ ├── web-drag-drop-implementation.md ⬅️ MOVED HERE
│ ├── wasm-patch-export-implementation.md ⬅️ MOVED HERE
│ └── [other internal docs]
├── examples/ [Code examples]
├── GIGALEAK_INTEGRATION.md
└── index.md
```
## Removed Directories
-`docs/web/` - consolidated into internal
-`docs/wasm/` - consolidated into internal
## Documentation Principles Applied
1. **Public vs Internal Separation**
- Public: User-facing, stable, external developers
- Internal: AI agents, implementation details, planning
2. **Accuracy & Honesty**
- Web app clearly marked as preview/experimental
- Editor status accurately reflects incomplete state
- Recommended desktop for production work
3. **Organization**
- Format docs in reference section for easy discovery
- Technical implementation in internal for developers
- Clear navigation through index files
4. **Currency**
- Updated "Last Modified" dates
- Removed outdated content
- Consolidated duplicate information
## Impact
### For Users
- ✅ Clear understanding that web app is preview
- ✅ Easy access to format documentation
- ✅ Better organized public docs
- ✅ Honest feature status
### For Developers
- ✅ Technical docs in predictable locations
- ✅ Format specs easy to find in reference/
- ✅ Implementation details separated from user guides
- ✅ Clear documentation hierarchy
### For AI Agents
- ✅ Updated coordination board with session
- ✅ Clear doc hygiene maintained
- ✅ No doc sprawl in root directories
## Follow-up Actions
None required. Documentation is now:
- Organized
- Accurate
- Complete for web app preview
- Properly separated (public vs internal)
- Up to date
## Agent
**Agent ID:** docs-janitor
**Session Date:** November 27, 2025
**Duration:** Single session
**Status:** Complete

View File

@@ -0,0 +1,95 @@
# Draw Routine Fixes - Phase 2
## Status
**Owner:** ai-dungeon-specialist
**Created:** 2025-12-07
**Status:** Partial Complete - Build Verified
## Summary
This document tracks fixes for specific dungeon object draw routines and outline sizing issues identified during testing. These issues complement the Phase 1 diagonal/edge/spacing fixes.
## Issues to Fix
### Issue 1: Block 0x5E Draw Routine Inverted
**Object:** 0x5E (RoomDraw_RightwardsBlock2x2spaced2_1to16)
**Problem:** Draw routine is inverted for the simple block pattern
**ASM Reference:** bank_01.asm line 363
**Fix Required:** Review tile ordering and column-major vs row-major layout
### Issue 2: Vertical Pegs Wrong Outline Shape
**Objects:** 0x95 (DownwardsPots2x2), 0x96 (DownwardsHammerPegs2x2)
**Problem:** Selection outline shows 2x2 square but should be vertical single row (1 tile wide)
**ASM Reference:** RoomDraw_DownwardsPots2x2_1to16, RoomDraw_DownwardsHammerPegs2x2_1to16
**Analysis:** The pots/pegs are 2x2 objects that repeat vertically with 2-row spacing (0x100). However, user reports they should display as 1-tile-wide vertical strips. Need to verify actual drawn dimensions from ASM.
**Fix Required:** Update dimension calculations in CalculateObjectDimensions and ObjectDimensionTable
### Issue 3: Thick Rail Horizontal/Vertical Draw Issues
**Objects:** 0x5D (RightwardsBigRail1x3), 0x88 (DownwardsBigRail3x1)
**Problem:** Repeats far left edge rather than inner parts of the thick rail
**ASM Reference:** RoomDraw_RightwardsBigRail1x3_1to16plus5, RoomDraw_DownwardsBigRail3x1_1to16plus5
**Analysis:** ASM shows:
- First draws a 2x2 block, then advances tile pointer by 8
- Then draws middle section (1x3 tiles per iteration)
- Finally draws end cap (2 tiles)
**Fix Required:** Fix draw routine to draw: left cap → middle repeating → right cap
### Issue 4: Large Decor Outline Too Small, Draw Routine Repeats Incorrectly
**Problem:** Large decoration objects have outlines that are too small and draw routines that repeat when they shouldn't
**Fix Required:** Identify specific objects and verify repetition count logic (size+1 vs size)
### Issue 5: Ceiling 0xC0 Doesn't Draw Properly
**Object:** 0xC0 (RoomDraw_4x4BlocksIn4x4SuperSquare)
**Problem:** Object doesn't draw at all or draws incorrectly
**ASM Reference:** bank_01.asm lines 1779-1831
**Analysis:** Uses complex 4x4 super-square pattern with 8 buffer pointers ($BF through $D4). Draws 4x4 blocks in a grid pattern based on size parameters B2 and B4.
**Fix Required:** Implement proper super-square drawing routine
### Issue 6: 0xF99 Chest Outline Correct But Draw Routine Repeats
**Object:** 0xF99 (RoomDraw_Chest - Type 3 chest)
**Problem:** Outline is correct but draw routine repeats when it shouldn't
**ASM Reference:** RoomDraw_Chest at bank_01.asm line 4707
**Analysis:** Chest should only draw once (single 2x2 pattern), not repeat based on size
**Fix Required:** Remove size-based repetition from chest draw routine
### Issue 7: Pit Edges Outlines Too Thin
**Problem:** Pit edge outlines shouldn't be single tile thin based on direction
**Objects:** Various pit edge objects
**Fix Required:** Update pit edge dimension calculations to include proper width based on direction
### Issue 8: 0x3D Tall Torches Wrong Top Half Graphics
**Object:** 0x3D (mapped to RoomDraw_RightwardsPillar2x4spaced4_1to16)
**Problem:** Bottom half draws correctly but top half with fire draws pegs instead
**ASM Reference:** bank_01.asm line 330 - uses RoomDraw_RightwardsPillar2x4spaced4_1to16 (same as 0x39)
**Analysis:** Object 0x3D and 0x39 share the same draw routine but use different tile data. Need to verify tile data offset is correct.
**Fix Required:** Verify tile data loading for 0x3D or create dedicated draw routine
## Implementation Status
### Completed Fixes
1. **Block 0x5E** ✅ - Fixed tile ordering in DrawRightwardsBlock2x2spaced2_1to16 to use column-major order
2. **Thick Rails 0x5D/0x88** ✅ - Rewrote DrawRightwardsBigRail and DrawDownwardsBigRail to correctly draw cap-middle-cap pattern
3. **Chest 0xF99** ✅ - Fixed chest to draw single 2x2 pattern without size-based repetition
4. **Large Decor 0xFEB** ✅ - Created Single4x4 routine (routine 113) - no repetition, correct 32x32 outline
5. **Water Grate 0xFED** ✅ - Created Single4x3 routine (routine 114) - no repetition, correct 32x24 outline
6. **Big Chest 0xFB1** ✅ - Mapped to Single4x3 routine (routine 114) - no repetition
7. **Blue Rupees 0xF92** ✅ - Created DrawRupeeFloor routine (routine 115) - special 6x8 pattern with gaps
### Pending Investigation
8. **Rails 0x022** - Uses RoomDraw_RightwardsHasEdge1x1_1to16_plus3 - needs internal rail drawing
9. **Ceiling 0xC0** - Draw4x4BlocksIn4x4SuperSquare may have tile loading issue
10. **Vertical Pegs 0x95/0x96** - Current dimensions look correct. May be UI display issue.
11. **Pit Edges** - Need specific object IDs to investigate
12. **Torches 0x3D** - May be ROM tile data issue (verify data at 0x807A)
## New Routines Added
- Routine 113: DrawSingle4x4 - Single 4x4 block, no repetition
- Routine 114: DrawSingle4x3 - Single 4x3 block, no repetition
- Routine 115: DrawRupeeFloor - Special 6x8 pattern with gaps at rows 2 and 5
## Files Modified
- `src/zelda3/dungeon/object_drawer.cc`
- `src/zelda3/dungeon/object_drawer.h`
- `docs/internal/agents/draw_routine_fixes_phase2.md`

View File

@@ -0,0 +1,125 @@
# Draw Routine Fixes - Handoff Document
**Status:** Handoff
**Owner:** draw-routine-engineer
**Created:** 2025-12-07
**Last Session:** 2025-12-07
---
## Summary
This session made significant progress on dungeon object draw routines. Many fixes were completed, but some objects still have minor issues requiring further investigation.
---
## Completed Fixes ✅
| Object ID | Name | Fix Applied |
|-----------|------|-------------|
| 0x5E | Block | Fixed column-major tile ordering |
| 0x5D/0x88 | Thick Rails | Fixed cap-middle-cap pattern |
| 0xF99 | Chest | Changed to DrawSingle2x2 (no size-based repeat) |
| 0xFB1 | Big Chest | Changed to DrawSingle4x3 (no repeat) |
| 0xF92 | Blue Rupees | Implemented DrawRupeeFloor per ASM (3x8 pattern) |
| 0xFED | Water Grate | Changed to DrawSingle4x3 (no repeat) |
| 0x3A | Wall Decors | Fixed spacing from 6 to 8 tiles |
| 0x39/0x3D | Pillars | Fixed spacing from 6 to 4 tiles |
| 0xFEB | Large Decor | Now 64x64 (4x4 tile16s = 8x8 tile8s) |
| 0x138-0x13B | Spiral Stairs | Fixed to 4x3 pattern per ASM |
| 0xC0/0xC2 | SuperSquare Ceilings | Dimensions now use size parameter |
| 0xFE6 | Pit | Uses DrawActual4x4 (32x32, no repeat) |
| 0x55-0x56 | Wall Torches | Fixed to 1x8 column with 12-tile spacing |
| 0x22 | Small Rails | Now CORNER+MIDDLE*count+END pattern |
| 0x23-0x2E | Carpet Trim | Now CORNER+MIDDLE*count+END pattern |
---
## Known Issues - Need Further Work ⚠️
### 1. Horizontal vs Vertical Rails Asymmetry
**Objects:** 0x22 (horizontal) vs vertical counterparts
**Issue:** Horizontal rails now work with CORNER+MIDDLE+END pattern, but vertical rails may not be updated to match.
**Files to check:**
- `DrawDownwardsHasEdge1x1_*` routines
- Look for routines mapped to 0x8A-0x8E (vertical equivalents)
### 2. Diagonal Ceiling Outlines (0xA0-0xA3)
**Issue:** Draw routine is correct (triangular fill), but outline dimensions still too large for selection purposes.
**Current state:**
- Draw: `count = (size & 0x0F) + 4`
- Outline: `count = (size & 0x0F) + 2` (still too big)
**Suggestion:** May need special handling in selection code rather than dimension calculation, since triangles only fill half the bounding box.
### 3. Torch Object 0x3D
**Issue:** Top half draws pegs instead of fire graphics.
**Likely cause:** ROM tile data issue - tiles at offset 0x807A may be incorrect or tile loading is wrong.
**Uses same routine as pillars (0x39).**
### 4. Vertical Pegs 0x95/0x96
**Issue:** Outline appears square (2x2) but should be vertical single row.
**May be UI/selection display issue rather than draw routine.**
---
## Pending Tasks 📋
### High Priority
1. **0xDC Chest Platform** - Complex routine with multiple segments, not currently working
2. **Staircases audit** - Objects 0x12D-0x137, 0x21B-0x221, 0x226-0x229, 0x233
### Medium Priority
3. **Vertical rail patterns** - Match horizontal rail fixes
4. **Pit edges** - Need specific object IDs to investigate
### Low Priority
5. **Diagonal ceiling selection** - May need UI-level fix
---
## Key Formulas Reference
### Size Calculations (from ASM)
| Pattern | Formula |
|---------|---------|
| GetSize_1to16 | `count = (size & 0x0F) + 1` |
| GetSize_1to16_timesA | `count = (size & 0x0F + 1) * A` |
| GetSize_1to15or26 | `count = size; if 0, count = 26` |
| GetSize_1to15or32 | `count = size; if 0, count = 32` |
| SuperSquare | `size_x = (size & 0x0F) + 1; size_y = ((size >> 4) & 0x0F) + 1` |
### Rail Pattern Structure
```
[CORNER tile 0] -> [MIDDLE tile 1 × count] -> [END tile 2]
```
### Tile Ordering
Most routines use **COLUMN-MAJOR** order: tiles advance down each column, then right.
---
## Files Modified
- `src/zelda3/dungeon/object_drawer.cc` - Main draw routines and mappings
- `src/zelda3/dungeon/object_drawer.h` - Added DrawActual4x4 declaration
- `src/zelda3/dungeon/draw_routines/special_routines.cc` - Spiral stairs fix
- `docs/internal/agents/draw_routine_tracker.md` - Tracking document
---
## Testing Notes
- Log file at `logs/dungeon-object-draw.log` shows object draw data
- Most testing was done on limited room set - need broader room testing
- Use grep on log file to find specific object draws: `grep "obj=0xXX" logs/dungeon-object-draw.log`
---
## Next Steps for Continuation
1. Test the rail fixes in rooms with both horizontal and vertical rails
2. Find and fix the vertical rail equivalents (likely routines 23-28 or similar)
3. Investigate 0xDC chest platform ASM in detail
4. Consider UI-level fix for diagonal ceiling selection bounds

View File

@@ -0,0 +1,102 @@
# Gemini Pro 3 Build Setup Guide
Quick iteration setup for maximizing Gemini's effectiveness on YAZE.
## One-Time Setup
```bash
# Use the dedicated Gemini build script (recommended)
./scripts/gemini_build.sh
# Or manually configure with the Gemini preset
cmake --preset mac-gemini
# Verify setup succeeded
ls build_gemini/compile_commands.json
```
## Fast Rebuild Cycle (~30s-2min)
```bash
# Recommended: Use the build script
./scripts/gemini_build.sh # Build yaze (default)
./scripts/gemini_build.sh yaze_test # Build tests
./scripts/gemini_build.sh --fresh # Clean reconfigure
# Or use cmake directly
cmake --build build_gemini -j8 --target yaze
# Rebuild editor library (for src/app/editor/ changes)
cmake --build build_gemini -j8 --target yaze_editor
# Rebuild zelda3 library (for src/zelda3/ changes)
cmake --build build_gemini -j8 --target yaze_lib
```
## Quick Validation (~2-3min)
```bash
# Run stable tests (unit + integration, no ROM required)
ctest --test-dir build_gemini -L stable -j4 --output-on-failure
# Run GUI smoke tests only (~1min)
ctest --test-dir build_gemini -L gui --output-on-failure
# Run specific test by name
ctest --test-dir build_gemini -R "OverworldRegression" --output-on-failure
```
## Full Validation (when confident)
```bash
# All tests
ctest --test-dir build_gemini --output-on-failure -j4
```
## Format Check (before committing)
```bash
# Check format without modifying
cmake --build build_gemini --target format-check
# Auto-format all code
cmake --build build_gemini --target format
```
## Key Directories
| Path | Purpose |
|------|---------|
| `src/app/editor/overworld/` | Overworld editor modules (8 files) |
| `src/zelda3/overworld/` | Overworld data models |
| `src/zelda3/dungeon/` | Dungeon data models |
| `src/app/editor/dungeon/` | Dungeon editor components |
| `test/unit/zelda3/` | Unit tests for zelda3 logic |
| `test/e2e/` | GUI E2E tests |
## Iteration Strategy
1. **Make changes** to target files
2. **Rebuild** with `cmake --build build_gemini -j8 --target yaze`
3. **Test** with `ctest --test-dir build_gemini -L stable -j4`
4. **Repeat** until all tests pass
## Common Issues
### Build fails with "target not found"
```bash
# Reconfigure (CMakeLists.txt changed)
./scripts/gemini_build.sh --fresh
# Or: cmake --preset mac-gemini --fresh
```
### Tests timeout
```bash
# Run with longer timeout
ctest --test-dir build_gemini -L stable --timeout 300
```
### Need to see test output
```bash
ctest --test-dir build_gemini -L stable -V # Verbose
```

View File

@@ -0,0 +1,245 @@
# Gemini Pro 3 Antigravity - YAZE Development Session
## Context
You are working on **YAZE** (Yet Another Zelda3 Editor), a C++23 cross-platform ROM editor for The Legend of Zelda: A Link to the Past.
**Your previous session accomplished:**
- Fixed ASM version check regression using `OverworldVersionHelper` abstraction
- Improved texture queueing in `Tile16Editor`
- The insight: `0xFF >= 3` evaluates true, incorrectly treating vanilla ROMs as v3
**Reference documentation available:**
- `docs/internal/agents/gemini-overworld-system-reference.md` - Overworld architecture
- `docs/internal/agents/gemini-dungeon-system-reference.md` - Dungeon architecture
- `docs/internal/agents/gemini-build-setup.md` - Build commands
---
## Quick Start
```bash
# 1. Setup build directory (use dedicated dir, not user's build/)
cmake --preset mac-dbg -B build_gemini
# 2. Build editor
cmake --build build_gemini -j8 --target yaze
# 3. Run stable tests
ctest --test-dir build_gemini -L stable -j4 --output-on-failure
```
---
## Task Categories
### Category A: Overworld Editor Gaps
#### A1. Texture Queueing TODOs (6 locations)
**File:** `src/app/editor/overworld/overworld_editor.cc`
**Lines:** 1392, 1397, 1740, 1809, 1819, 1962
These commented-out Renderer calls need to be converted to the Arena queue system:
```cpp
// BEFORE (blocking - commented out)
// Renderer::Get().RenderBitmap(&bitmap_);
// AFTER (non-blocking)
gfx::Arena::Get().QueueTextureCommand(gfx::TextureCommand{
.operation = gfx::TextureOperation::kCreate,
.bitmap = &bitmap_,
.priority = gfx::TexturePriority::kHigh
});
```
#### A2. Unimplemented Editor Methods
**File:** `src/app/editor/overworld/overworld_editor.h` lines 82-87
| Method | Status | Complexity |
|--------|--------|------------|
| `Undo()` | Returns `UnimplementedError` | Medium |
| `Redo()` | Returns `UnimplementedError` | Medium |
| `Cut()` | Returns `UnimplementedError` | Simple |
| `Find()` | Returns `UnimplementedError` | Medium |
#### A3. Entity Popup Static Variable Bug
**File:** `src/app/editor/overworld/entity.cc`
Multiple popups use `static` variables that persist across calls, causing state contamination:
```cpp
// CURRENT (BUG)
bool DrawExitEditorPopup() {
static bool set_done = false; // Persists! Wrong entity data shown
static int doorType = ...;
}
// FIX: Use local variables or proper state management
bool DrawExitEditorPopup(ExitEditorState& state) {
// state is passed in, not static
}
```
#### A4. Exit Editor Unconnected UI
**File:** `src/app/editor/overworld/entity.cc` lines 216-264
UI elements exist but aren't connected to data:
- Door type editing (Wooden, Bombable, Sanctuary, Palace)
- Door X/Y position
- Center X/Y, Link posture, sprite/BG GFX, palette
---
### Category B: Dungeon Object Rendering
#### B1. BothBG Dual-Layer Stubs (4 locations)
**File:** `src/zelda3/dungeon/object_drawer.cc`
These routines should draw to BOTH BG1 and BG2 but only accept one buffer:
| Line | Routine | Status |
|------|---------|--------|
| 375-381 | `DrawRightwards2x4spaced4_1to16_BothBG` | STUB |
| 437-442 | `DrawDiagonalAcute_1to16_BothBG` | STUB |
| 444-449 | `DrawDiagonalGrave_1to16_BothBG` | STUB |
| 755-761 | `DrawDownwards4x2_1to16_BothBG` | STUB |
**Fix:** Change signature to accept both buffers:
```cpp
// BEFORE
void DrawRightwards2x4spaced4_1to16_BothBG(
const RoomObject& obj, gfx::BackgroundBuffer& bg, ...);
// AFTER
void DrawRightwards2x4spaced4_1to16_BothBG(
const RoomObject& obj,
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2, ...);
```
#### B2. Diagonal Routines Unclear Logic
**File:** `src/zelda3/dungeon/object_drawer.cc` lines 401-435
Issues with `DrawDiagonalAcute_1to16` and `DrawDiagonalGrave_1to16`:
- Hardcoded `size + 6` and 5 iterations (why?)
- Coordinate formula `obj.y_ + (i - s)` can produce negative Y
- No bounds checking
- Only uses 4 tiles from larger span
#### B3. CustomDraw and DoorSwitcherer Stubs
**File:** `src/zelda3/dungeon/object_drawer.cc`
- Lines 524-532: `CustomDraw` - only draws first tile
- Lines 566-575: `DrawDoorSwitcherer` - only draws first tile
#### B4. Unknown Object Names (30+ items)
**File:** `src/zelda3/dungeon/room_object.h`
See `gemini-dungeon-system-reference.md` section 7 for full list of objects needing in-game verification.
---
### Category C: Already Complete (Verification Only)
#### C1. Tile16Editor Undo/Redo - COMPLETE
**File:** `src/app/editor/overworld/tile16_editor.cc`
- `SaveUndoState()` at lines 547, 1476, 1511, 1546, 1586, 1620
- `Undo()` / `Redo()` at lines 1707-1760
- Ctrl+Z/Ctrl+Y at lines 224-231
- UI button at line 1101
**No work needed** - just verify it works.
#### C2. Entity Deletion Pattern - CORRECT
**File:** `src/app/editor/overworld/entity.cc` line 319
The TODO comment is misleading. The `deleted` flag pattern IS correct for ROM editors:
- Entities at fixed ROM offsets can't be "removed"
- `entity_operations.cc` reuses deleted slots
- Just clarify the comment if desired
---
## Prioritized Task List
### Phase 1: High Impact (45-60 min)
1. **A1** - Texture queueing (6 TODOs) - Prevents UI freezes
2. **B1** - BothBG dual-layer stubs (4 routines) - Completes dungeon rendering
### Phase 2: Medium Impact (30-45 min)
3. **A3** - Entity popup static variable bug - Fixes data corruption
4. **B2** - Diagonal routine logic - Fixes rendering artifacts
### Phase 3: Polish (30+ min)
5. **A2** - Implement Undo/Redo for OverworldEditor
6. **A4** - Connect exit editor UI to data
7. **B3** - Implement CustomDraw/DoorSwitcherer
### Stretch Goals
8. **B4** - Verify unknown object names (requires game testing)
9. E2E cinematic tests (see `docs/internal/testing/dungeon-gui-test-design.md`)
---
## Code Patterns
### Texture Queue (Use This!)
```cpp
gfx::Arena::Get().QueueTextureCommand(gfx::TextureCommand{
.operation = gfx::TextureOperation::kCreate, // or kUpdate
.bitmap = &bitmap_,
.priority = gfx::TexturePriority::kHigh
});
```
### Version-Aware Code
```cpp
auto version = OverworldVersionHelper::GetVersion(*rom_);
if (OverworldVersionHelper::SupportsAreaEnum(version)) {
// v3+ only
}
```
### Error Handling
```cpp
absl::Status MyFunction() {
ASSIGN_OR_RETURN(auto data, LoadData());
RETURN_IF_ERROR(ProcessData(data));
return absl::OkStatus();
}
```
---
## Validation
```bash
# After each change
cmake --build build_gemini -j8 --target yaze
ctest --test-dir build_gemini -L stable -j4 --output-on-failure
# Before finishing
cmake --build build_gemini --target format-check
```
---
## Success Metrics
- [ ] `grep "TODO.*texture" src/app/editor/overworld/overworld_editor.cc` returns nothing
- [ ] BothBG routines accept both buffer parameters
- [ ] Static variable bug in entity popups fixed
- [ ] `ctest -L stable` passes 100%
- [ ] Code formatted
---
## File Quick Reference
| System | Key Files |
|--------|-----------|
| Overworld Editor | `src/app/editor/overworld/overworld_editor.cc` (3,204 lines) |
| Entity UI | `src/app/editor/overworld/entity.cc` (491 lines) |
| Tile16 Editor | `src/app/editor/overworld/tile16_editor.cc` (2,584 lines) |
| Object Drawer | `src/zelda3/dungeon/object_drawer.cc` (972 lines) |
| Room Object | `src/zelda3/dungeon/room_object.h` (633 lines) |

View File

@@ -0,0 +1,376 @@
# YAZE Overworld System - Complete Technical Reference
Comprehensive reference for AI agents working on the YAZE Overworld editing system.
---
## 1. Architecture Overview
### File Structure
```
src/zelda3/overworld/
├── overworld.h/cc # Main Overworld class (2,500+ lines)
├── overworld_map.h/cc # Individual map (OverworldMap class)
├── overworld_version_helper.h # Version detection & feature gates
├── overworld_item.h/cc # Item entities
├── overworld_entrance.h # Entrance/hole entities
├── overworld_exit.h # Exit entities
src/app/editor/overworld/
├── overworld_editor.h/cc # Main editor (3,204 lines)
├── map_properties.cc # MapPropertiesSystem (1,759 lines)
├── tile16_editor.h/cc # Tile16Editor (2,584 lines)
├── entity.cc # Entity UI popups (491 lines)
├── entity_operations.cc # Entity CRUD helpers (239 lines)
├── overworld_entity_renderer.cc # Entity drawing (151 lines)
├── scratch_space.cc # Tile storage utilities (444 lines)
```
### Data Model Hierarchy
```
Rom (raw data)
└── Overworld (coordinator)
├── tiles16_[] (3,752-4,096 Tile16 definitions)
├── tiles32_unique_[] (up to 8,864 Tile32 definitions)
├── overworld_maps_[160] (individual map screens)
│ └── OverworldMap
│ ├── area_graphics_, area_palette_
│ ├── bitmap_data_[] (256x256 rendered pixels)
│ └── current_gfx_[] (graphics buffer)
├── all_entrances_[] (~129 entrance points)
├── all_holes_[] (~19 hole entrances)
├── all_exits_[] (~79 exit points)
├── all_items_[] (collectible items)
└── all_sprites_[3][] (sprites per game state)
```
---
## 2. ZSCustomOverworld Version System
### Version Detection
```cpp
// ROM address for version byte
constexpr int OverworldCustomASMHasBeenApplied = 0x140145;
// Version values
0xFF = Vanilla ROM (unpatched)
0x01 = ZSCustomOverworld v1
0x02 = ZSCustomOverworld v2
0x03 = ZSCustomOverworld v3+
```
### Feature Matrix
| Feature | Vanilla | v1 | v2 | v3 |
|---------|---------|----|----|-----|
| Basic map editing | Y | Y | Y | Y |
| Large maps (2x2) | Y | Y | Y | Y |
| Expanded pointers (0x130000+) | - | Y | Y | Y |
| Custom BG colors | - | - | Y | Y |
| Main palette per area | - | - | Y | Y |
| Wide areas (2x1) | - | - | - | Y |
| Tall areas (1x2) | - | - | - | Y |
| Custom tile GFX (8 per map) | - | - | - | Y |
| Animated GFX | - | - | - | Y |
| Subscreen overlays | - | - | - | Y |
### OverworldVersionHelper API
```cpp
// src/zelda3/overworld/overworld_version_helper.h
enum class OverworldVersion { kVanilla=0, kZSCustomV1=1, kZSCustomV2=2, kZSCustomV3=3 };
enum class AreaSizeEnum { SmallArea=0, LargeArea=1, WideArea=2, TallArea=3 };
// Detection
static OverworldVersion GetVersion(const Rom& rom);
static uint8_t GetAsmVersion(const Rom& rom);
// Feature gates (use these, not raw version checks!)
static bool SupportsAreaEnum(OverworldVersion v); // v3 only
static bool SupportsExpandedSpace(OverworldVersion v); // v1+
static bool SupportsCustomBGColors(OverworldVersion v);// v2+
static bool SupportsCustomTileGFX(OverworldVersion v); // v3 only
static bool SupportsAnimatedGFX(OverworldVersion v); // v3 only
static bool SupportsSubscreenOverlay(OverworldVersion v); // v3 only
```
---
## 3. Tile System Architecture
### Tile Hierarchy
```
Tile8 (8x8 pixels) - Base SNES tile
Tile16 (16x16 pixels) - 2x2 grid of Tile8s
Tile32 (32x32 pixels) - 2x2 grid of Tile16s
Map Screen (256x256 pixels) - 8x8 grid of Tile32s
```
### Tile16 Structure
```cpp
// src/app/gfx/types/snes_tile.h
class TileInfo {
uint16_t id_; // 9-bit tile8 ID (0-511)
uint8_t palette_; // 3-bit palette (0-7)
bool over_; // Priority flag
bool vertical_mirror_; // Y-flip
bool horizontal_mirror_; // X-flip
};
class Tile16 {
TileInfo tile0_; // Top-left
TileInfo tile1_; // Top-right
TileInfo tile2_; // Bottom-left
TileInfo tile3_; // Bottom-right
};
// ROM storage: 8 bytes per Tile16 at 0x78000 + (ID * 8)
// Total: 4,096 Tile16s (0x0000-0x0FFF)
```
### Tile16Editor Features (COMPLETE)
The Tile16Editor at `tile16_editor.cc` is **fully implemented** with:
- **Undo/Redo System** (lines 1681-1760)
- `SaveUndoState()` - captures current state
- `Undo()` / `Redo()` - restore states
- Ctrl+Z / Ctrl+Y keyboard shortcuts
- 50-state stack with rate limiting
- **Clipboard Operations**
- Copy/Paste Tile16s
- 4 scratch space slots
- **Editing Features**
- Tile8 composition into Tile16
- Flip horizontal/vertical
- Palette cycling (0-7)
- Fill with single Tile8
---
## 4. Map Organization
### Index Scheme
```
Index 0x00-0x3F: Light World (64 maps, 8x8 grid)
Index 0x40-0x7F: Dark World (64 maps, 8x8 grid)
Index 0x80-0x9F: Special World (32 maps, 8x4 grid)
Total: 160 maps
Grid position: X = index % 8, Y = index / 8
World position: X * 512 pixels, Y * 512 pixels
```
### Multi-Area Maps
```cpp
enum class AreaSizeEnum {
SmallArea = 0, // 1x1 screen (standard)
LargeArea = 1, // 2x2 screens (4 quadrants)
WideArea = 2, // 2x1 screens (v3 only)
TallArea = 3, // 1x2 screens (v3 only)
};
// IMPORTANT: Always use ConfigureMultiAreaMap() for size changes!
// Never set area_size_ directly - it handles parent IDs and ROM persistence
absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size);
```
---
## 5. Entity System
### Entity Types
| Type | Storage | Count | ROM Address |
|------|---------|-------|-------------|
| Entrances | `all_entrances_` | ~129 | 0xDB96F+ |
| Holes | `all_holes_` | ~19 | 0xDB800+ |
| Exits | `all_exits_` | ~79 | 0x15D8A+ |
| Items | `all_items_` | Variable | 0xDC2F9+ |
| Sprites | `all_sprites_[3]` | Variable | 0x4C881+ |
### Entity Deletion Pattern
Entities use a `deleted` flag pattern - this is **CORRECT** for ROM editors:
```cpp
// Entities live at fixed ROM offsets, cannot be truly "removed"
// Setting deleted = true marks them as inactive
// entity_operations.cc reuses deleted slots for new entities
item.deleted = true; // Proper pattern
// Renderer skips deleted entities (overworld_entity_renderer.cc)
if (!item.deleted) { /* render */ }
```
---
## 6. Graphics Loading Pipeline
### Load Sequence
```
1. Overworld::Load(rom)
└── LoadOverworldMaps()
└── For each map (0-159):
└── OverworldMap::ctor()
├── LoadAreaInfo()
└── LoadCustomOverworldData() [v3]
2. On map access: EnsureMapBuilt(map_index)
└── BuildMap()
├── LoadAreaGraphics()
├── BuildTileset()
├── BuildTiles16Gfx()
├── LoadPalette()
├── LoadOverlay()
└── BuildBitmap()
```
### Texture Queue System
Use deferred texture loading via `gfx::Arena`:
```cpp
// CORRECT: Non-blocking, uses queue
gfx::Arena::Get().QueueTextureCommand(gfx::TextureCommand{
.operation = gfx::TextureOperation::kCreate,
.bitmap = &some_bitmap_,
.priority = gfx::TexturePriority::kHigh
});
// WRONG: Blocking, causes UI freeze
Renderer::Get().RenderBitmap(&some_bitmap_);
```
---
## 7. ROM Addresses (Key Locations)
### Vanilla Addresses
```cpp
// Tile data
kTile16Ptr = 0x78000 // Tile16 definitions
kOverworldMapSize = 0x12844 // Map size bytes
// Graphics & Palettes
kAreaGfxIdPtr = 0x7C9C // Area graphics IDs
kOverworldMapPaletteGroup = 0x7D1C // Palette IDs
// Entities
kOverworldEntranceMap = 0xDB96F // Entrance data
kOverworldExitRooms = 0x15D8A // Exit room IDs
kOverworldItemPointers = 0xDC2F9 // Item pointers
```
### Expanded Addresses (v1+)
```cpp
// Custom data at 0x140000+
OverworldCustomASMHasBeenApplied = 0x140145 // Version byte
OverworldCustomAreaSpecificBGPalette = 0x140000 // BG colors (160*2)
OverworldCustomMainPaletteArray = 0x140160 // Main palettes (160)
OverworldCustomAnimatedGFXArray = 0x1402A0 // Animated GFX (160)
OverworldCustomTileGFXGroupArray = 0x140480 // Tile GFX (160*8)
OverworldCustomSubscreenOverlayArray = 0x140340 // Overlays (160*2)
kOverworldMapParentIdExpanded = 0x140998 // Parent IDs (160)
kOverworldMessagesExpanded = 0x1417F8 // Messages (160*2)
```
---
## 8. Known Gaps in OverworldEditor
### Critical: Texture Queueing TODOs (6 locations)
```cpp
// overworld_editor.cc - these Renderer calls need to be converted:
Line 1392: // TODO: Queue texture for later rendering
Line 1397: // TODO: Queue texture for later rendering
Line 1740: // TODO: Queue texture for later rendering
Line 1809: // TODO: Queue texture for later rendering
Line 1819: // TODO: Queue texture for later rendering
Line 1962: // TODO: Queue texture for later rendering
```
### Unimplemented Core Methods
```cpp
// overworld_editor.h lines 82-87
Undo() Returns UnimplementedError
Redo() Returns UnimplementedError
Cut() Returns UnimplementedError
Find() Returns UnimplementedError
```
### Entity Popup Static Variable Bug
```cpp
// entity.cc - Multiple popups use static variables that persist
// Causes state contamination when editing multiple entities
bool DrawExitEditorPopup() {
static bool set_done = false; // BUG: persists across calls
static int doorType = ...; // BUG: wrong entity's data shown
}
```
### Exit Editor Unimplemented Features
```cpp
// entity.cc lines 216-264
// UI exists but not connected to data:
- Door type editing (Wooden, Bombable, Sanctuary, Palace)
- Door X/Y position
- Center X/Y, Link posture, sprite/BG GFX, palette
```
---
## 9. Code Patterns to Follow
### Graphics Refresh
```cpp
// 1. Update model
map.SetProperty(new_value);
// 2. Reload from ROM
map.LoadAreaGraphics();
// 3. Queue texture update (NOT RenderBitmap!)
gfx::Arena::Get().QueueTextureCommand(gfx::TextureCommand{
.operation = gfx::TextureOperation::kUpdate,
.bitmap = &map_bitmap_,
.priority = gfx::TexturePriority::kHigh
});
```
### Version-Aware Code
```cpp
auto version = OverworldVersionHelper::GetVersion(*rom_);
// Use semantic helpers, not raw version checks
if (OverworldVersionHelper::SupportsAreaEnum(version)) {
// v3+ only code
}
```
### Entity Rendering Colors (0.85f alpha)
```cpp
ImVec4 entrance_color(1.0f, 0.85f, 0.0f, 0.85f); // Yellow-gold
ImVec4 exit_color(0.0f, 1.0f, 1.0f, 0.85f); // Cyan
ImVec4 item_color(1.0f, 0.0f, 0.0f, 0.85f); // Red
ImVec4 sprite_color(1.0f, 0.0f, 1.0f, 0.85f); // Magenta
```
---
## 10. Testing
### Run Overworld Tests
```bash
# Unit tests
ctest --test-dir build -R "Overworld" -V
# Regression tests
ctest --test-dir build -R "OverworldRegression" -V
```
### Test Files
- `test/unit/zelda3/overworld_test.cc` - Core tests
- `test/unit/zelda3/overworld_regression_test.cc` - Version helper tests

View File

@@ -0,0 +1,217 @@
# Gemini Pro 3 Task Checklist
Prioritized checklist of all identified work items for YAZE development.
---
## Phase 1: Critical Rendering Issues (HIGH PRIORITY)
### Task A1: Texture Queueing TODOs
**File:** `src/app/editor/overworld/overworld_editor.cc`
**Impact:** Prevents UI freezes, enables proper texture loading
**Time Estimate:** 25 min
- [ ] Line 1392: Convert commented Renderer call to Arena queue
- [ ] Line 1397: Convert commented Renderer call to Arena queue
- [ ] Line 1740: Convert commented Renderer call to Arena queue
- [ ] Line 1809: Convert commented Renderer call to Arena queue
- [ ] Line 1819: Convert commented Renderer call to Arena queue
- [ ] Line 1962: Convert commented Renderer call to Arena queue
- [ ] Verify: `grep "TODO.*texture" overworld_editor.cc` returns nothing
- [ ] Test: Run stable tests, no UI freezes on map load
---
### Task B1: BothBG Dual-Layer Stubs
**File:** `src/zelda3/dungeon/object_drawer.cc`
**Impact:** Completes dungeon object rendering for dual-layer objects
**Time Estimate:** 30 min
- [ ] Line 375-381: `DrawRightwards2x4spaced4_1to16_BothBG`
- Change signature to accept both `bg1` and `bg2`
- Call underlying routine for both buffers
- [ ] Line 437-442: `DrawDiagonalAcute_1to16_BothBG`
- Change signature to accept both buffers
- [ ] Line 444-449: `DrawDiagonalGrave_1to16_BothBG`
- Change signature to accept both buffers
- [ ] Line 755-761: `DrawDownwards4x2_1to16_BothBG`
- Change signature to accept both buffers
- [ ] Update `DrawObject()` call sites to pass both buffers for BothBG routines
- [ ] Test: Dungeon rooms with dual-layer objects render correctly
---
## Phase 2: Bug Fixes (MEDIUM PRIORITY)
### Task A3: Entity Popup Static Variable Bug
**File:** `src/app/editor/overworld/entity.cc`
**Impact:** Fixes data corruption when editing multiple entities
**Time Estimate:** 20 min
- [ ] `DrawExitEditorPopup()` (line 152+):
- Remove `static bool set_done`
- Remove other static variables
- Pass state via parameter or use popup ID
- [ ] `DrawItemEditorPopup()` (line 320+):
- Remove static variables
- [ ] Other popup functions:
- Audit for static state
- [ ] Test: Edit multiple exits/items in sequence, verify correct data
---
### Task B2: Diagonal Routine Logic
**File:** `src/zelda3/dungeon/object_drawer.cc` lines 401-435
**Impact:** Fixes rendering artifacts for diagonal objects
**Time Estimate:** 30 min
- [ ] `DrawDiagonalAcute_1to16`:
- Verify/document the `size + 6` constant
- Add bounds checking for negative Y coordinates
- Handle edge cases at canvas boundaries
- [ ] `DrawDiagonalGrave_1to16`:
- Same fixes as acute version
- [ ] Test: Place diagonal objects in dungeon editor, verify appearance
---
## Phase 3: Feature Completion (POLISH)
### Task A2: OverworldEditor Undo/Redo
**File:** `src/app/editor/overworld/overworld_editor.h` lines 82-87
**Impact:** Enables undo/redo for overworld edits
**Time Estimate:** 45 min
- [ ] Design undo state structure:
```cpp
struct OverworldUndoState {
int map_index;
std::vector<uint16_t> tile_data;
// Entity changes?
};
```
- [ ] Add `undo_stack_` and `redo_stack_` members
- [ ] Implement `Undo()` method
- [ ] Implement `Redo()` method
- [ ] Wire up Ctrl+Z / Ctrl+Y shortcuts
- [ ] Test: Make edits, undo, redo - verify state restoration
---
### Task A4: Exit Editor UI Connection
**File:** `src/app/editor/overworld/entity.cc` lines 216-264
**Impact:** Makes exit editor fully functional
**Time Estimate:** 30 min
- [ ] Connect door type radio buttons to `exit.door_type_`
- [ ] Connect door X/Y inputs to exit data
- [ ] Connect Center X/Y to exit scroll data
- [ ] Connect palette/GFX fields to exit properties
- [ ] Test: Edit exit properties, save ROM, verify changes persisted
---
### Task B3: CustomDraw and DoorSwitcherer
**File:** `src/zelda3/dungeon/object_drawer.cc`
**Impact:** Completes special object rendering
**Time Estimate:** 30 min
- [ ] `CustomDraw` (lines 524-532):
- Research expected behavior from ZScream/game
- Implement proper drawing logic
- [ ] `DrawDoorSwitcherer` (lines 566-575):
- Research door switching animation/logic
- Implement proper drawing
- [ ] Test: Place custom objects, verify appearance
---
## Phase 4: Stretch Goals
### Task B4: Object Name Verification
**File:** `src/zelda3/dungeon/room_object.h`
**Impact:** Improves editor usability with proper names
**Time Estimate:** 2+ hours (requires game testing)
See `gemini-dungeon-system-reference.md` section 7 for full list.
High-priority unknowns:
- [ ] Line 234: Object 0x35 "WEIRD DOOR" - investigate
- [ ] Lines 392-395: Objects 0xDE-0xE1 "Moving wall flag" - WTF IS THIS?
- [ ] Lines 350-353: Diagonal layer 2 mask B objects - verify
- [ ] Multiple "Unknown" objects in Type 2 and Type 3 ranges
---
### Task E2E: Cinematic Tests
**Reference:** `docs/internal/testing/dungeon-gui-test-design.md`
**Impact:** Visual regression testing, demo capability
**Time Estimate:** 45+ min
- [ ] Create screenshot capture utility
- [ ] Implement basic cinematic test sequence
- [ ] Add visual diff comparison
- [ ] Document test workflow
---
## Already Complete (Verification Only)
### Tile16Editor Undo/Redo
**File:** `src/app/editor/overworld/tile16_editor.cc`
**Status:** FULLY IMPLEMENTED
- [x] `SaveUndoState()` implemented
- [x] `Undo()` / `Redo()` implemented
- [x] Ctrl+Z / Ctrl+Y shortcuts working
- [x] UI button at line 1101
- [x] Stack management with limits
**Action:** Verify it works, no changes needed.
---
### Entity Deletion Pattern
**File:** `src/app/editor/overworld/entity.cc` line 319
**Status:** CORRECT (misleading TODO)
The `deleted` flag pattern IS correct for ROM editors:
- Entities at fixed ROM offsets
- `entity_operations.cc` reuses deleted slots
- Renderer skips deleted entities
**Action:** Optionally clarify the TODO comment.
---
## Quick Reference: File Locations
| Task | Primary File | Line Numbers |
|------|--------------|--------------|
| A1 | overworld_editor.cc | 1392, 1397, 1740, 1809, 1819, 1962 |
| A2 | overworld_editor.h | 82-87 |
| A3 | entity.cc | 152+, 320+ |
| A4 | entity.cc | 216-264 |
| B1 | object_drawer.cc | 375, 437, 444, 755 |
| B2 | object_drawer.cc | 401-435 |
| B3 | object_drawer.cc | 524-532, 566-575 |
| B4 | room_object.h | Multiple (see section 7 of dungeon ref) |
---
## Validation Commands
```bash
# Build
cmake --build build_gemini -j8 --target yaze
# Test
ctest --test-dir build_gemini -L stable -j4 --output-on-failure
# Format check
cmake --build build_gemini --target format-check
# Specific test
ctest --test-dir build_gemini -R "Overworld" -V
ctest --test-dir build_gemini -R "Dungeon" -V
```

View File

@@ -0,0 +1,165 @@
# Gemini 3 Pro Prompt: Overworld Regression Fix and Improvements
## Context
You are working on **yaze** (Yet Another Zelda3 Editor), a C++23 ROM editor for The Legend of Zelda: A Link to the Past. A regression has been introduced that breaks loading of custom ROMs like "Oracle of Secrets" ROM hack.
## Primary Bug: ASM Version Check Inconsistency
### Root Cause Analysis
The recent refactoring introduced `OverworldVersionHelper` for centralized ROM version detection, but **not all code paths were updated to use it consistently**. Specifically:
**In `src/zelda3/overworld/overworld.cc:71`:**
```cpp
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version >= 3) { // BUG: 0xFF (255) is >= 3!
AssignMapSizes(overworld_maps_);
} else {
FetchLargeMaps(); // Vanilla ROMs need this path
}
```
**The bug**: `asm_version >= 3` evaluates to `true` for vanilla ROMs where `asm_version == 0xFF` (255), causing vanilla ROMs and custom ROMs without ZScream ASM patches to incorrectly call `AssignMapSizes()` instead of `FetchLargeMaps()`.
**Other places correctly check**:
```cpp
if (asm_version >= 3 && asm_version != 0xFF) { ... } // Correct
```
### Inconsistent Locations Found
Search results showing mixed patterns:
- `overworld.cc:71` - **BUG**: `if (asm_version >= 3)` - missing `&& asm_version != 0xFF`
- `overworld.cc:449` - **BUG**: `if (expanded_flag != 0x04 || asm_version >= 3)` - missing check
- `overworld.cc:506` - **BUG**: similar pattern
- `overworld.cc:281` - **CORRECT**: `(asm_version < 3 || asm_version == 0xFF)`
- `overworld.cc:373` - **CORRECT**: `if (asm_version >= 3 && asm_version != 0xFF)`
- Other files also have inconsistencies
## Your Task
### Phase 1: Fix the Regression (CRITICAL)
1. **Update all ASM version checks** in overworld code to either:
- Use `OverworldVersionHelper::GetVersion()` and semantic checks like `SupportsAreaEnum()`, OR
- Consistently use `asm_version >= 3 && asm_version != 0xFF` pattern
2. **Key files to fix**:
- `src/zelda3/overworld/overworld.cc`
- `src/zelda3/overworld/overworld_map.cc`
- `src/zelda3/overworld/overworld_item.cc`
3. **Priority fixes in `overworld.cc`**:
- Line 71: Change to `if (asm_version >= 3 && asm_version != 0xFF)`
- Line 449: Add `&& asm_version != 0xFF` check
- Line 506: Add `&& asm_version != 0xFF` check
- Review all other locations from the grep results
### Phase 2: Standardize Version Checking (Recommended)
Replace all raw `asm_version` checks with `OverworldVersionHelper`:
**Instead of:**
```cpp
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version >= 3 && asm_version != 0xFF) {
```
**Use:**
```cpp
auto version = OverworldVersionHelper::GetVersion(*rom_);
if (OverworldVersionHelper::SupportsAreaEnum(version)) {
```
This centralizes the logic and prevents future inconsistencies.
### Phase 3: Add Unit Tests
Create tests in `test/unit/zelda3/overworld_test.cc` to verify:
1. Vanilla ROM (0xFF) uses `FetchLargeMaps()` path
2. ZScream v3 ROM (0x03) uses `AssignMapSizes()` path
3. Custom ROMs with other values behave correctly
## Key Files Reference
```
src/zelda3/overworld/
├── overworld.cc # Main loading logic
├── overworld.h
├── overworld_map.cc # Individual map handling
├── overworld_map.h
├── overworld_item.cc # Item loading
├── overworld_item.h
├── overworld_entrance.h # Entrance/Exit data
├── overworld_exit.cc
├── overworld_exit.h
├── overworld_version_helper.h # Version detection helper
```
## OverworldVersionHelper API
```cpp
enum class OverworldVersion {
kVanilla = 0, // 0xFF in ROM - no ZScream ASM
kZSCustomV1 = 1,
kZSCustomV2 = 2,
kZSCustomV3 = 3 // Area enum system
};
class OverworldVersionHelper {
static OverworldVersion GetVersion(const Rom& rom);
static bool SupportsAreaEnum(OverworldVersion v); // v3 only
static bool SupportsExpandedSpace(OverworldVersion v); // v1+
static bool SupportsCustomBGColors(OverworldVersion v); // v2+
// ...
};
```
## Commits That Introduced the Regression
1. `1e39df88a3` - "refactor: enhance overworld entity properties and version handling"
- Introduced `OverworldVersionHelper`
- 15 files changed, +546 -282 lines
2. `5894809aaf` - "refactor: improve overworld map version handling and code organization"
- Updated `OverworldMap` to use version helper
- 4 files changed, +145 -115 lines
## Build & Test Commands
```bash
# Configure
cmake --preset mac-dbg
# Build
cmake --build build --target yaze -j8
# Run unit tests
ctest --test-dir build -L stable -R overworld
# Run the app to test loading
./build/bin/Debug/yaze.app/Contents/MacOS/yaze --rom_file=/path/to/oracle_of_secrets.sfc
```
## Success Criteria
1. Oracle of Secrets ROM loads correctly in the Overworld Editor
2. Vanilla ALTTP ROMs continue to work
3. ZScream v3 patched ROMs continue to work
4. All existing unit tests pass
5. No new compiler warnings
## Additional Context
- The editor supports multiple ROM types: Vanilla, ZScream v1/v2/v3 patched ROMs, and custom hacks
- `OverworldCustomASMHasBeenApplied` address (0x130000) stores the version byte
- 0xFF = vanilla (no patches), 1-2 = legacy ZScream, 3 = current ZScream
- "Oracle of Secrets" is a popular ROM hack that may use 0xFF or a custom value
## Code Quality Requirements
- Follow Google C++ Style Guide
- Use `absl::Status` for error handling
- Run clang-format before committing
- Update CLAUDE.md coordination board when done

View File

@@ -0,0 +1,384 @@
# Gemini 3 Pro - v0.4.0 Development Session
## Context
You are working on **YAZE** (Yet Another Zelda3 Editor), a C++23 cross-platform ROM editor for The Legend of Zelda: A Link to the Past.
**Current Situation:**
- **v0.3.9**: CI/CD hotfix running (another agent handling - DO NOT TOUCH)
- **v0.4.0**: Your focus - "Editor Stability & OOS Support"
- **Goal**: Unblock Oracle of Secrets (OOS) ROM hack development
---
## Quick Start
```bash
# Use dedicated build directory (DO NOT use build/ or build_agent/)
cmake --preset mac-dbg -B build_gemini
cmake --build build_gemini -j8 --target yaze
# Run stable tests
ctest --test-dir build_gemini -L stable -j4 --output-on-failure
```
---
## Completed Work
### Priority 2: Message Editor - Expanded BIN Export [COMPLETE]
**Completed by Gemini 3 Pro:**
1. **JSON Export Implementation** (`message_data.h/cc`):
- Added `SerializeMessagesToJson()` - Converts `MessageData` vector to JSON array
- Added `ExportMessagesToJson()` - Writes JSON file with error handling
- Uses nlohmann/json library with 2-space pretty printing
2. **File Dialog Integration** (`message_editor.cc:317-376`):
- "Load Expanded Message" - Opens file dialog to load BIN
- "Save Expanded Messages" - Saves to remembered path
- "Save As..." - Prompts for new path
- "Export to JSON" - JSON export with file dialog
3. **Path Persistence**:
- `expanded_message_path_` member stores last used path
- Reuses path for subsequent saves
4. **SaveExpandedMessages() Implementation** (`message_editor.cc:521-571`):
- Uses `expanded_message_bin_` member for ROM-like storage
- Handles buffer expansion for new messages
- Writes terminator byte (0xFF) after data
### Priority 1: Dungeon Editor - SaveDungeon() [IN PROGRESS]
**Completed by Gemini 3 Pro:**
1. **SaveDungeon() Implementation** (`dungeon_editor_system.cc:44-59`):
- No longer a stub! Loops through all 296 rooms
- Calls `SaveRoomData()` for each room
- Tracks dirty state and last save time
2. **SaveObjects() Implementation** (`room.cc:873-925`):
- Properly calculates available space via `CalculateRoomSize()`
- Validates encoded size fits before writing
- Returns `OutOfRangeError` if data too large
3. **SaveSprites() Implementation** (`room.cc:927-999`):
- Calculates sprite space from pointer table
- Handles sortsprites byte
- Returns `OutOfRangeError` if data too large
4. **New Tests** (`test/unit/zelda3/dungeon/dungeon_save_test.cc`):
- `SaveObjects_FitsInSpace` - verifies normal save works
- `SaveObjects_TooLarge` - verifies overflow detection
- `SaveSprites_FitsInSpace` - verifies normal save works
- `SaveSprites_TooLarge` - verifies overflow detection
**Tests Pass:**
```bash
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="*DungeonSave*"
# [ PASSED ] 4 tests.
```
---
## Testing Instructions
### Build and Run Tests
```bash
# Build test target (uses existing build_gemini)
cmake --build build_gemini --target yaze_test_stable -j8
# Run ALL stable tests
./build_gemini/bin/Debug/yaze_test_stable
# Run specific test pattern
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="*DungeonSave*"
# Run dungeon-related tests
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="*Dungeon*"
# Run with verbose output
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="*YourTest*" --gtest_print_time=1
```
### Known Test Issues (Pre-existing)
**FAILING:** `RoomObjectEncodingTest.DetermineObjectTypeType2`
- Location: `test/unit/zelda3/dungeon/room_object_encoding_test.cc:29-31`
- Issue: `DetermineObjectType()` returns 1 instead of 2 for bytes 0xFC, 0xFD, 0xFF
- Status: Pre-existing failure, NOT caused by your changes
- Action: Ignore unless you're specifically working on object type detection
### Test File Locations
| Test Type | Location | Filter Pattern |
|-----------|----------|----------------|
| Dungeon Save | `test/unit/zelda3/dungeon/dungeon_save_test.cc` | `*DungeonSave*` |
| Room Encoding | `test/unit/zelda3/dungeon/room_object_encoding_test.cc` | `*RoomObjectEncoding*` |
| Room Manipulation | `test/unit/zelda3/dungeon/room_manipulation_test.cc` | `*RoomManipulation*` |
| Dungeon Integration | `test/integration/dungeon_editor_test.cc` | `*DungeonEditorIntegration*` |
| Overworld | `test/unit/zelda3/overworld_test.cc` | `*Overworld*` |
---
## Your Priorities (Pick One)
### Priority 1: Dungeon Editor - Save Infrastructure [COMPLETE] ✅
**Completed by Gemini 3 Pro:**
1. **SaveRoomData() Implementation** (`dungeon_editor_system.cc:914-973`):
- ✅ Detects if room is currently being edited in UI
- ✅ Uses editor's in-memory `Room` object (contains unsaved changes)
- ✅ Syncs sprites from `DungeonEditorSystem` to `Room` before saving
- ✅ Selectively saves objects only for current room (optimization)
2. **UI Integration** (`dungeon_editor_v2.cc:244-291`):
-`Save()` method calls `SaveDungeon()` correctly
- ✅ Palette saving via `PaletteManager`
- ✅ Room objects saved via `Room::SaveObjects()`
- ✅ Sprites saved via `DungeonEditorSystem::SaveRoom()`
3. **Edge Cases Verified:**
- ✅ Current room with unsaved changes
- ✅ Non-current rooms (sprite-only save)
- ✅ Multiple rooms open in tabs
- ✅ Empty sprite lists
**Audit Report:** `zscow_audit_report.md` (artifact)
**Minor Improvements Recommended:**
- Add integration tests for `DungeonEditorSystem` save flow
- Remove redundant `SaveObjects()` call in `DungeonEditorV2::Save()`
- Document stub methods
**Test your changes:**
```bash
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="*DungeonSave*:*RoomManipulation*"
```
---
### Priority 3: ZSCOW Audit [COMPLETE] ✅
**Completed by Gemini 3 Pro:**
#### 1. Version Detection - VERIFIED ✅
**Implementation:** `overworld_version_helper.h:51-71`
| ASM Byte | Version | Status |
|----------|---------|--------|
| `0xFF` | Vanilla | ✅ Correct |
| `0x00` | Vanilla | ✅ **CORRECT** - Expanded ROMs are zero-filled |
| `0x01` | v1 | ✅ Verified |
| `0x02` | v2 | ⚠️ Not tested (no v2 ROM available) |
| `0x03+` | v3 | ✅ Verified |
**Task 1: Version 0x00 Edge Case - RESOLVED ✅**
- **Answer:** Treating `0x00` as vanilla is **CORRECT**
- **Rationale:** When vanilla ROM is expanded to 2MB, new space is zero-filled
- **Address:** `0x140145` is in expanded space (beyond 1MB)
- **Tests Added:** 5 comprehensive tests in `overworld_version_helper_test.cc`
- **Bounds Check Added:** Prevents crashes on small ROMs
**Task 2: Death Mountain GFX TODO - DOCUMENTED ⚠️**
**Location:** `overworld_map.cc:595-602`
- **Current logic:** Checks parent ranges `0x03-0x07`, `0x0B-0x0E` (LW), `0x43-0x47`, `0x4B-0x4E` (DW)
- **Recommended:** Only check `0x03`, `0x05`, `0x07` (LW) and `0x43`, `0x45`, `0x47` (DW)
- **Rationale:** Matches ZScream 3.0.4+ behavior, handles non-large DM areas correctly
- **Impact:** Low risk improvement
- **Status:** Documented in audit report, not implemented yet
**Task 3: ConfigureMultiAreaMap - FULLY VERIFIED ✅**
**Location:** `overworld.cc:267-422`
- ✅ Vanilla ROM: Correctly rejects Wide/Tall areas
- ✅ v1/v2 ROM: Same as vanilla (no area enum support)
- ✅ v3 ROM: All 4 sizes work (Small, Large, Wide, Tall)
- ✅ Sibling cleanup: Properly resets all quadrants when shrinking Large→Small
- ✅ Edge maps: Boundary conditions handled safely
**Task 4: Special World Hardcoded Cases - VERIFIED ✅**
**Location:** `overworld_map.cc:202-293`
- ✅ Triforce room (`0x88`, `0x93`): Special graphics `0x51`, palette `0x00`
- ✅ Master Sword area (`0x80`): Special GFX group
- ✅ Zora's Domain (`0x81`, `0x82`, `0x89`, `0x8A`): Sprite GFX `0x0E`
- ✅ Version-aware logic for v3 vs vanilla/v2
**Audit Report:** `zscow_audit_report.md` (artifact)
**Test Results:**
```bash
./build_gemini/bin/Debug/yaze_test_stable --gtest_filter="OverworldVersionHelperTest*"
# [ PASSED ] 5 tests.
```
**Overall Assessment:** ZSCOW implementation is production-ready with one low-priority enhancement (Death Mountain GFX logic).
---
### Priority 4: Agent Inspection - Wire Real Data [MEDIUM] - DETAILED
**Problem:** CLI inspection commands return stub output. Helper functions exist but aren't connected.
#### Overworld Command Handlers (`src/cli/handlers/game/overworld.cc`)
| Line | Handler | Status | Helper to Use |
|------|---------|--------|---------------|
| 33 | `OverworldGetTileCommandHandler` | TODO | Manual ROM read |
| 84 | `OverworldSetTileCommandHandler` | TODO | Manual ROM write |
| 162 | `OverworldFindTileCommandHandler` | TODO | `FindTileMatches()` |
| 325 | `OverworldDescribeMapCommandHandler` | TODO | `BuildMapSummary()` |
| 508 | `OverworldListWarpsCommandHandler` | TODO | `CollectWarpEntries()` |
| 721 | `OverworldSelectRectCommandHandler` | TODO | N/A (GUI) |
| 759 | `OverworldScrollToCommandHandler` | TODO | N/A (GUI) |
| 794 | `OverworldSetZoomCommandHandler` | TODO | N/A (GUI) |
| 822 | `OverworldGetVisibleRegionCommandHandler` | TODO | N/A (GUI) |
#### Dungeon Command Handlers (`src/cli/handlers/game/dungeon_commands.cc`)
| Line | Handler | Status |
|------|---------|--------|
| 36 | Sprite listing | TODO - need `Room::sprites()` |
| 158 | Object listing | TODO - need `Room::objects()` |
| 195 | Tile data | TODO - need `Room::floor1()`/`floor2()` |
| 237 | Property setting | TODO - need `Room::set_*()` methods |
#### Available Helper Functions (`overworld_inspect.h`)
These are fully implemented and ready to use:
```cpp
// Build complete map metadata summary
absl::StatusOr<MapSummary> BuildMapSummary(zelda3::Overworld& overworld, int map_id);
// Find all warps matching query (entrances, exits, holes)
absl::StatusOr<std::vector<WarpEntry>> CollectWarpEntries(
const zelda3::Overworld& overworld, const WarpQuery& query);
// Find all occurrences of a tile16 ID
absl::StatusOr<std::vector<TileMatch>> FindTileMatches(
zelda3::Overworld& overworld, uint16_t tile_id, const TileSearchOptions& options);
// Get sprites on overworld maps
absl::StatusOr<std::vector<OverworldSprite>> CollectOverworldSprites(
const zelda3::Overworld& overworld, const SpriteQuery& query);
// Get entrance details by ID
absl::StatusOr<EntranceDetails> GetEntranceDetails(
const zelda3::Overworld& overworld, uint8_t entrance_id);
// Analyze how often a tile is used
absl::StatusOr<TileStatistics> AnalyzeTileUsage(
zelda3::Overworld& overworld, uint16_t tile_id, const TileSearchOptions& options);
```
#### Example Fix Pattern
```cpp
// BEFORE (broken):
absl::Status OverworldFindTileCommandHandler::Execute(
CommandContext& context, std::ostream& output) {
output << "Placeholder: would find tile..."; // STUB!
return absl::OkStatus();
}
// AFTER (working):
absl::Status OverworldFindTileCommandHandler::Execute(
CommandContext& context, std::ostream& output) {
auto tile_id_str = context.GetArgument("tile_id");
ASSIGN_OR_RETURN(auto tile_id, ParseHexOrDecimal(tile_id_str));
TileSearchOptions options;
if (auto world = context.GetOptionalArgument("world")) {
ASSIGN_OR_RETURN(options.world, ParseWorldSpecifier(*world));
}
ASSIGN_OR_RETURN(auto matches,
overworld::FindTileMatches(context.overworld(), tile_id, options));
output << "Found " << matches.size() << " occurrences:\n";
for (const auto& match : matches) {
output << absl::StrFormat(" Map %d (%s): (%d, %d)\n",
match.map_id, WorldName(match.world), match.local_x, match.local_y);
}
return absl::OkStatus();
}
```
#### Priority Order
1. `OverworldDescribeMapCommandHandler` - Most useful for agents
2. `OverworldFindTileCommandHandler` - Common query
3. `OverworldListWarpsCommandHandler` - Needed for navigation
4. Dungeon sprite/object listing - For dungeon editing support
---
## DO NOT TOUCH
- `.github/workflows/` - CI/CD hotfix in progress
- `build/`, `build_agent/`, `build_test/` - User's build directories
- `src/util/crash_handler.cc` - Being patched for Windows
---
## Code Style
- Use `absl::Status` and `absl::StatusOr<T>` for error handling
- Macros: `RETURN_IF_ERROR()`, `ASSIGN_OR_RETURN()`
- Format: `cmake --build build_gemini --target format`
- Follow Google C++ Style Guide
---
## Reference Documentation
- **CLAUDE.md** - Project conventions and patterns
- **Roadmap:** `docs/internal/roadmaps/2025-11-23-refined-roadmap.md`
- **Message Editor Plans:** `docs/internal/plans/message_editor_implementation_roadmap.md`
- **Test Guide:** `docs/public/build/quick-reference.md`
---
## Recommended Approach
1. **Pick ONE priority** to focus on
2. **Read the relevant files** before making changes
3. **Run tests frequently** (`ctest --test-dir build_gemini -L stable`)
4. **Commit with clear messages** following conventional commits (`fix:`, `feat:`)
5. **Don't touch CI/CD** - that's being handled separately
---
## Current State of Uncommitted Work
The working tree has changes you should be aware of:
- `tile16_editor.cc` - Texture queueing improvements
- `entity.cc/h` - Sprite movement fixes
- `overworld_editor.cc` - Entity rendering
- `overworld_map.cc` - Map rendering
- `object_drawer.cc/h` - Dungeon objects
Review these before making overlapping changes.
---
## Success Criteria
When you're done, one of these should be true:
- [x] ~~Dungeon save actually persists changes to ROM~~ **COMPLETE**
- [x] ~~Message editor can export expanded BIN files~~ **COMPLETE**
- [x] ~~ZSCOW loads correctly for vanilla + v1/v2/v3 ROMs~~ **COMPLETE**
- [ ] Agent inspection returns real data
Good luck!

View File

@@ -0,0 +1,482 @@
# YAZE Dungeon System - Complete Technical Reference
Comprehensive reference for AI agents working on the YAZE Dungeon editing system.
---
## 1. Architecture Overview
### File Structure
```
src/zelda3/dungeon/
├── dungeon.h/cc # Main Dungeon class
├── room.h/cc # Room class (1,337 lines)
├── room_object.h/cc # RoomObject encoding (633+249 lines)
├── object_drawer.h/cc # Object rendering (210+972 lines)
├── object_parser.h/cc # ROM tile parsing (172+387 lines)
├── room_entrance.h # Entrance data (367 lines)
├── dungeon_rom_addresses.h # ROM address constants (108 lines)
src/app/editor/dungeon/
├── dungeon_editor_v2.h/cc # Main editor (card-based)
├── dungeon_room_loader.h/cc # ROM data loading
├── dungeon_room_selector.h/cc # Room selection UI
├── dungeon_canvas_viewer.h/cc # Canvas rendering
├── dungeon_object_selector.h/cc # Object palette
├── dungeon_object_interaction.h/cc # Mouse interactions
```
### Data Model
```
Dungeon
└── rooms_[296]
└── Room
├── tile_objects_[] (RoomObject instances)
├── sprites_[]
├── chests_in_room_[]
├── z3_staircases_[]
├── bg1_buffer_ (512x512 pixels)
├── bg2_buffer_ (512x512 pixels)
└── current_gfx16_[] (16KB graphics)
```
---
## 2. Room Structure
### Room Count & Organization
- **Total Rooms:** 296 (indices 0x00-0x127)
- **Canvas Size:** 512x512 pixels (64x64 tiles)
- **Layers:** BG1, BG2, BG3
### Room Properties
```cpp
// room.h
int room_id_; // Room index (0-295)
uint8_t blockset; // Graphics blockset ID
uint8_t spriteset; // Sprite set ID
uint8_t palette; // Palette ID (0-63)
uint8_t layout; // Layout template (0-7)
uint8_t floor1, floor2; // Floor graphics (nibbles)
uint16_t message_id_; // Associated message
// Behavioral
CollisionKey collision_type; // Collision enum
EffectKey effect_type; // Visual effect enum
TagKey tag1, tag2; // Special condition tags
LayerMergeType layer_merge; // BG1/BG2 blend mode
```
### Layer Merge Types
```cpp
enum LayerMergeType {
LayerMerge00 = 0x00, // Off - Layer 2 invisible
LayerMerge01 = 0x01, // Parallax scrolling
LayerMerge02 = 0x02, // Dark overlay
LayerMerge03 = 0x03, // On top (translucent)
LayerMerge04 = 0x04, // Translucent blend
LayerMerge05 = 0x05, // Addition blend
LayerMerge06 = 0x06, // Normal overlay
LayerMerge07 = 0x07, // Transparent
LayerMerge08 = 0x08, // Dark room effect
};
```
---
## 3. Object Encoding System
### 3-Byte Object Format
Objects are stored as 3 bytes in ROM with three distinct encoding types:
#### Type 1: Standard Objects (ID 0x00-0xFF)
```
Byte format: xxxxxxss | yyyyyyss | iiiiiiii
b1 b2 b3
Decoding:
x = (b1 & 0xFC) >> 2 // 6 bits (0-63)
y = (b2 & 0xFC) >> 2 // 6 bits (0-63)
size = ((b1 & 0x03) << 2) | (b2 & 0x03) // 4 bits (0-15)
id = b3 // 8 bits
```
#### Type 2: Extended Objects (ID 0x100-0x1FF)
```
Indicator: b1 >= 0xFC
Byte format: 111111xx | xxxxyyyy | yyiiiiii
b1 b2 b3
Decoding:
id = (b3 & 0x3F) | 0x100
x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4)
y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6)
size = 0 // No size parameter
```
#### Type 3: Rare Objects (ID 0xF00-0xFFF)
```
Indicator: b3 >= 0xF8
Byte format: xxxxxxii | yyyyyyii | 11111iii
b1 b2 b3
Decoding:
id = (b3 << 4) | 0x80 | ((b2 & 0x03) << 2) | (b1 & 0x03)
x = (b1 & 0xFC) >> 2
y = (b2 & 0xFC) >> 2
size = ((b1 & 0x03) << 2) | (b2 & 0x03)
```
### Object Categories
| Type | ID Range | Examples |
|------|----------|----------|
| Type 1 | 0x00-0xFF | Walls, floors, decorations |
| Type 2 | 0x100-0x1FF | Corners, stairs, furniture |
| Type 3 | 0xF00-0xFFF | Chests, pipes, special objects |
---
## 4. ObjectDrawer Rendering System
### Class Structure
```cpp
// object_drawer.h
class ObjectDrawer {
// Entry point
absl::Status DrawObject(const RoomObject& object,
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group);
// Data
Rom* rom_;
const uint8_t* room_gfx_buffer_; // current_gfx16_
std::unordered_map<int16_t, int> object_to_routine_map_;
std::vector<DrawRoutine> draw_routines_;
};
```
### Draw Routine Status
| # | Routine Name | Status | Lines |
|---|--------------|--------|-------|
| 0 | DrawRightwards2x2_1to15or32 | COMPLETE | 302-321 |
| 1 | DrawRightwards2x4_1to15or26 | COMPLETE | 323-348 |
| 2 | DrawRightwards2x4spaced4_1to16 | COMPLETE | 350-373 |
| 3 | DrawRightwards2x4spaced4_BothBG | **STUB** | 375-381 |
| 4 | DrawRightwards2x2_1to16 | COMPLETE | 383-399 |
| 5 | DrawDiagonalAcute_1to16 | **UNCLEAR** | 401-417 |
| 6 | DrawDiagonalGrave_1to16 | **UNCLEAR** | 419-435 |
| 7 | DrawDownwards2x2_1to15or32 | COMPLETE | 689-708 |
| 8 | DrawDownwards4x2_1to15or26 | COMPLETE | 710-753 |
| 9 | DrawDownwards4x2_BothBG | **STUB** | 755-761 |
| 10 | DrawDownwardsDecor4x2spaced4 | COMPLETE | 763-782 |
| 11 | DrawDownwards2x2_1to16 | COMPLETE | 784-799 |
| 12 | DrawDownwardsHasEdge1x1 | COMPLETE | 801-813 |
| 13 | DrawDownwardsEdge1x1 | COMPLETE | 815-827 |
| 14 | DrawDownwardsLeftCorners | COMPLETE | 829-842 |
| 15 | DrawDownwardsRightCorners | COMPLETE | 844-857 |
| 16 | DrawRightwards4x4_1to16 | COMPLETE | 534-550 |
| - | CustomDraw | **STUB** | 524-532 |
| - | DrawDoorSwitcherer | **STUB** | 566-575 |
### INCOMPLETE: BothBG Routines (4 locations)
These routines should draw to BOTH BG1 and BG2 but currently only call single-layer version:
```cpp
// Line 375-381: DrawRightwards2x4spaced4_1to16_BothBG
// Line 437-442: DrawDiagonalAcute_1to16_BothBG
// Line 444-449: DrawDiagonalGrave_1to16_BothBG
// Line 755-761: DrawDownwards4x2_1to16_BothBG
// Current (WRONG):
void DrawRightwards2x4spaced4_1to16_BothBG(
const RoomObject& obj, gfx::BackgroundBuffer& bg, // Only 1 buffer!
std::span<const gfx::TileInfo> tiles) {
// Just calls single-layer version - misses BG2
DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
}
// Should be:
void DrawRightwards2x4spaced4_1to16_BothBG(
const RoomObject& obj,
gfx::BackgroundBuffer& bg1, // Both buffers
gfx::BackgroundBuffer& bg2,
std::span<const gfx::TileInfo> tiles) {
DrawRightwards2x4spaced4_1to16(obj, bg1, tiles);
DrawRightwards2x4spaced4_1to16(obj, bg2, tiles);
}
```
### UNCLEAR: Diagonal Routines
```cpp
// Lines 401-417, 419-435
// Issues:
// - Hardcoded +6 and 5 iterations (why?)
// - Coordinate formula may produce negative Y
// - Only uses 4 tiles from larger span
// - No bounds checking
for (int s = 0; s < size + 6; s++) { // Why +6?
for (int i = 0; i < 5; i++) { // Why 5?
WriteTile8(bg, obj.x_ + s, obj.y_ + (i - s), tiles[i % 4]);
// ^^ (i - s) can be negative when s > i
}
}
```
---
## 5. Tile Rendering Pipeline
### WriteTile8() - Tile to Pixel Conversion
```cpp
// object_drawer.cc lines 863-883
void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
const gfx::TileInfo& tile_info) {
// tile coords → pixel coords: tile_x * 8, tile_y * 8
DrawTileToBitmap(bitmap, tile_info, tile_x * 8, tile_y * 8, room_gfx_buffer_);
}
```
### DrawTileToBitmap() - Pixel Rendering
```cpp
// object_drawer.cc lines 890-970
// Key steps:
// 1. Graphics sheet lookup: tile_info.id_ → (sheet_x, sheet_y)
// 2. Palette offset: (palette & 0x0F) * 8
// 3. Per-pixel with mirroring support
// 4. Color 0 = transparent (skipped)
int tile_sheet_x = (tile_info.id_ % 16) * 8; // 0-127 pixels
int tile_sheet_y = (tile_info.id_ / 16) * 8; // 0-127 pixels
uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8;
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px;
int src_y = tile_info.vertical_mirror_ ? (7 - py) : py;
// Read pixel, apply palette, write to bitmap
}
}
```
### Palette Application (CRITICAL)
```cpp
// object_drawer.cc lines 71-115
// Palette must be applied AFTER drawing, BEFORE SDL sync
// 1. Draw all objects (writes palette indices 0-255)
for (auto& obj : objects) {
DrawObject(obj, bg1, bg2, palette_group);
}
// 2. Apply dungeon palette to convert indices → RGB
bg1_bmp.SetPalette(dungeon_palette);
bg2_bmp.SetPalette(dungeon_palette);
// 3. Sync to SDL surfaces
SDL_LockSurface(bg1_bmp.surface());
memcpy(bg1_bmp.surface()->pixels, bg1_bmp.mutable_data().data(), ...);
SDL_UnlockSurface(bg1_bmp.surface());
```
---
## 6. ROM Addresses
### Room Data
```cpp
kRoomObjectLayoutPointer = 0x882D // Layout pointer table
kRoomObjectPointer = 0x874C // Object data pointer
kRoomHeaderPointer = 0xB5DD // Room headers (3-byte long)
kRoomHeaderPointerBank = 0xB5E7 // Bank byte
```
### Palette & Graphics
```cpp
kDungeonsMainBgPalettePointers = 0xDEC4B
kDungeonsPalettes = 0xDD734
kGfxGroupsPointer = 0x6237
kTileAddress = 0x1B52 // Main tile graphics
kTileAddressFloor = 0x1B5A // Floor tile graphics
```
### Object Subtypes
```cpp
kRoomObjectSubtype1 = 0x0F8000 // Standard objects
kRoomObjectSubtype2 = 0x0F83F0 // Extended objects
kRoomObjectSubtype3 = 0x0F84F0 // Rare objects
kRoomObjectTileAddress = 0x091B52 // Tile data
```
### Special Objects
```cpp
kBlocksPointer[1-4] = 0x15AFA-0x15B0F
kChestsDataPointer1 = 0xEBFB
kTorchData = 0x2736A
kPitPointer = 0x394AB
kDoorPointers = 0xF83C0
```
---
## 7. TODOs in room_object.h (30+ items)
### Unknown Objects Needing Verification
| Line | ID | Description |
|------|-----|-------------|
| 234 | 0x35 | "WEIRD DOOR" - needs investigation |
| 252-255 | 0x49-0x4C | "Unknown" Type 1 objects |
| 350-353 | 0xC4-0xC7 | "Diagonal layer 2 mask B" - needs verify |
| 392-395 | 0xDE-0xE1 | "Moving wall flag" - WTF IS THIS? |
| 466-476 | Type 2 | Multiple "Unknown" objects |
| 480 | 0x30 | "Intraroom stairs north B" - verify layer |
| 486 | 0x36 | "Water ladder (south)" - needs verify |
| 512-584 | Type 3 | Multiple "Unknown" objects |
---
## 8. DungeonEditorV2 Architecture
### Card-Based Component System
```cpp
DungeonEditorV2 (Coordinator)
DungeonRoomLoader // ROM data loading
DungeonRoomSelector // Room list/selection
DungeonCanvasViewer // 512x512 canvas
DungeonObjectSelector // Object palette
DungeonObjectInteraction // Mouse handling
ObjectEditorCard // Property editing
PaletteEditorWidget // Color editing
```
### Card Types
```cpp
show_control_panel_ // Room/entrance selection
show_room_selector_ // Room list
show_room_matrix_ // 16x19 visual layout
show_entrances_list_ // Entrance/spawn list
show_room_graphics_ // Blockset/palette
show_object_editor_ // Object placement
show_palette_editor_ // Palette colors
show_debug_controls_ // Debug options
```
### Undo/Redo System
```cpp
// Per-room object snapshots
std::unordered_map<int, std::vector<std::vector<RoomObject>>> undo_history_;
std::unordered_map<int, std::vector<std::vector<RoomObject>>> redo_history_;
```
---
## 9. Room Loading Flow
```
LoadRoomFromRom(room_id)
├── Resolve room header pointer (0xB5DD + room_id * 3)
├── Parse header bytes:
│ ├── BG2 type, collision, light flag
│ ├── Palette, blockset, spriteset
│ ├── Effect type, tags
│ └── Staircase data
├── Load graphics sheets (16 blocks)
└── LoadObjects()
├── Read floor/layout header (2 bytes)
├── Parse object stream:
│ ├── 3 bytes per object
│ ├── 0xFF 0xFF = layer boundary
│ └── 0xF0 0xFF = door section
└── Handle special objects:
├── Staircases
├── Chests
├── Doors
├── Torches
└── Blocks
```
---
## 10. Rendering Pipeline
```
1. LoadRoomGraphics()
└── Build graphics sheet list from blockset
2. CopyRoomGraphicsToBuffer()
└── Copy ROM sheets → current_gfx16_[]
3. RenderRoomGraphics()
├── Check dirty flags
├── LoadLayoutTilesToBuffer()
├── Draw floor to bg1/bg2 buffers
└── RenderObjectsToBackground()
└── ObjectDrawer::DrawObjectList()
4. Present (Canvas Viewer)
├── Process deferred texture queue
├── Create/update GPU textures
└── Render to ImGui canvas
```
---
## 11. Known Issues Summary
### BothBG Support (4 stubs)
- Line 380: `DrawRightwards2x4spaced4_1to16_BothBG`
- Line 441: `DrawDiagonalAcute_1to16_BothBG`
- Line 448: `DrawDiagonalGrave_1to16_BothBG`
- Line 760: `DrawDownwards4x2_1to16_BothBG`
**Fix:** Change signature to accept both `bg1` and `bg2` buffers.
### Diagonal Logic (2 routines)
- Lines 401-435: Hardcoded constants, potential negative coords
- **Needs:** Game verification or ZScream reference
### Custom/Door Stubs (2 routines)
- Line 524-532: `CustomDraw` - only draws first tile
- Line 566-575: `DrawDoorSwitcherer` - only draws first tile
### Object Names (30+ unknowns)
- Multiple objects need in-game verification
- See section 7 for full list
---
## 12. Testing
### Run Dungeon Tests
```bash
# Unit tests
ctest --test-dir build -R "dungeon\|Dungeon" -V
# E2E tests
ctest --test-dir build -R "DungeonEditor" -V
```
### E2E Test Files
- `test/e2e/dungeon_editor_smoke_test.cc`
- `test/e2e/dungeon_canvas_interaction_test.cc`
- `test/e2e/dungeon_layer_rendering_test.cc`
- `test/e2e/dungeon_object_drawing_test.cc`
### Test Design Doc
`docs/internal/testing/dungeon-gui-test-design.md` (1000+ lines)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
# Gemini Pro 3 Overworld Architecture Reference
Compact reference for YAZE Overworld/Dungeon systems. Use this to quickly locate code and understand patterns.
---
## 1. Overworld Editor Architecture (~8,900 lines across 7 modules)
### Main Editor
| File | Lines | Purpose |
|------|-------|---------|
| `src/app/editor/overworld/overworld_editor.cc` | 3,204 | Main coordinator, canvas, menus |
| `src/app/editor/overworld/overworld_editor.h` | 350 | Class definition |
### Sub-Modules
| File | Lines | Purpose |
|------|-------|---------|
| `map_properties.cc` | 1,759 | `MapPropertiesSystem` - property panels |
| `tile16_editor.cc` | 2,584 | `Tile16Editor` - tile editing popup |
| `entity.cc` | 491 | `OverworldEntity` - entity containers |
| `entity_operations.cc` | 239 | Entity CRUD helpers |
| `overworld_entity_renderer.cc` | 151 | Entity drawing |
| `scratch_space.cc` | 444 | Tile16 storage utilities |
### Data Models
| File | Purpose |
|------|---------|
| `src/zelda3/overworld/overworld.cc` | Main overworld class, loading logic |
| `src/zelda3/overworld/overworld_map.cc` | Individual map data |
| `src/zelda3/overworld/overworld_item.cc` | Item entities |
| `src/zelda3/overworld/overworld_version_helper.h` | Version detection API |
---
## 2. Completed Work (Gemini's Previous Session)
### ASM Version Check Standardization
- Replaced raw `asm_version >= 3` with `OverworldVersionHelper::SupportsAreaEnum()`
- Fixed critical bug: vanilla ROMs (0xFF) incorrectly treated as v3+
- Applied consistently across: `overworld.cc`, `overworld_item.cc`, `overworld_map.cc`
### Tile16Editor Texture Queueing
- Fixed `tile16_editor.cc` lines 37-38, 44-45, 56-57
- Pattern: `QueueTextureCommand()` instead of `RenderBitmap()` during init
### Test Infrastructure
- Created `test/unit/zelda3/overworld_regression_test.cc`
- Tests `OverworldVersionHelper` logic (passing)
---
## 3. Remaining Work Checklist
### P0 - Must Complete
#### Texture Queueing TODOs in overworld_editor.cc
6 locations still use inline rendering instead of deferred queueing:
| Line | Current Code | Fix Pattern |
|------|--------------|-------------|
| 1392 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
| 1397 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
| 1740 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
| 1809 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
| 1819 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
| 1962 | `// TODO: Queue texture` | Use `QueueTextureCommand()` |
**Pattern to follow** (from tile16_editor.cc):
```cpp
// BEFORE (blocking)
Renderer::Get().RenderBitmap(&some_bitmap_);
// AFTER (non-blocking)
gfx::Arena::Get().QueueTextureCommand(gfx::TextureCommand{
.operation = gfx::TextureOperation::kCreate,
.bitmap = &some_bitmap_,
.priority = gfx::TexturePriority::kHigh
});
```
#### Entity Deletion Pattern (entity.cc:319) - WORKING CORRECTLY
- **Note:** The TODO comment is misleading. The `deleted` flag pattern IS CORRECT for ROM editors
- Entities live at fixed ROM offsets, so marking `deleted = true` is the proper approach
- Renderer correctly skips deleted entities (see `overworld_entity_renderer.cc`)
- `entity_operations.cc` reuses deleted slots when creating new entities
- **No fix needed** - just a cleanup of the misleading TODO comment
### P1 - Should Complete
#### Tile16Editor Undo/Redo - ALREADY COMPLETE
- Location: `tile16_editor.cc:1681-1760`
- `SaveUndoState()` called before all edit operations
- `Undo()` and `Redo()` fully implemented with `absl::Status` returns
- Ctrl+Z/Ctrl+Y handling at lines 224-231
- Stack management with `kMaxUndoStates_` limit
- **No additional work needed**
#### Overworld Regression Test Completion
- Location: `test/unit/zelda3/overworld_regression_test.cc`
- Current: Only tests version helper
- Need: Add more comprehensive mock ROM data
### P2 - Stretch Goals
#### Dungeon Downwards Draw Routines
- Location: `src/zelda3/dungeon/object_drawer.cc` lines 160-185
- Missing implementations marked with stubs
- Need: Implement based on game ROM patterns
#### E2E Cinematic Tests
- Design doc: `docs/internal/testing/dungeon-gui-test-design.md`
- Framework: ImGuiTestEngine integration ready
- Need: Screenshot capture, visual verification
---
## 4. Key Patterns
### Bitmap/Surface Synchronization
```cpp
// CORRECT: Use set_data() for bulk replacement
bitmap.set_data(new_data); // Syncs both data_ and surface_->pixels
// WRONG: Direct assignment breaks sync
bitmap.mutable_data() = new_data; // NEVER DO THIS
```
### Graphics Refresh Order
```cpp
// 1. Update model
map.SetProperty(new_value);
// 2. Reload from ROM
map.LoadAreaGraphics();
// 3. Force render
Renderer::Get().RenderBitmap(&map_bitmap_); // Immediate
// OR
gfx::Arena::Get().QueueTextureCommand(...); // Deferred (preferred)
```
### Version Helper Usage
```cpp
#include "zelda3/overworld/overworld_version_helper.h"
auto version = OverworldVersionHelper::GetVersion(*rom_);
// Feature gates
if (OverworldVersionHelper::SupportsAreaEnum(version)) {
// v3+ only features
}
if (OverworldVersionHelper::SupportsExpandedSpace(version)) {
// v1+ features
}
```
### Entity Rendering Colors (0.85f alpha)
```cpp
ImVec4 entrance_color(1.0f, 0.85f, 0.0f, 0.85f); // Bright yellow-gold
ImVec4 exit_color(0.0f, 1.0f, 1.0f, 0.85f); // Cyan
ImVec4 item_color(1.0f, 0.0f, 0.0f, 0.85f); // Bright red
ImVec4 sprite_color(1.0f, 0.0f, 1.0f, 0.85f); // Bright magenta
```
---
## 5. File Quick Reference
### Overworld Editor Entry Points
- `Initialize()` → overworld_editor.cc:64
- `Load()` → overworld_editor.cc:150
- `Update()` → overworld_editor.cc:203
- `DrawFullscreenCanvas()` → overworld_editor.cc:472
- `ProcessDeferredTextures()` → overworld_editor.cc:899
### Tile16Editor Entry Points
- `Initialize()` → tile16_editor.cc:30
- `Update()` → tile16_editor.cc:100
- `DrawTile16Editor()` → tile16_editor.cc:200
### Entity System Entry Points
- `OverworldEntity::Draw()` → entity.cc:50
- `DeleteSelectedEntity()` → entity.cc:319
- Entity containers: `entrances_`, `exits_`, `items_`, `sprites_`
### Dungeon System Entry Points
- `ObjectDrawer::DrawObject()` → object_drawer.cc:50
- Draw routines: lines 100-300
- Downwards stubs: lines 160-185
---
## 6. Test Commands
```bash
# Run overworld regression test specifically
ctest --test-dir build_gemini -R "OverworldRegression" -V
# Run all zelda3 unit tests
ctest --test-dir build_gemini -R "zelda3" -L unit
# Run GUI E2E tests
ctest --test-dir build_gemini -L gui -V
```
---
## 7. Validation Criteria
### For Texture Queueing Fix
- [ ] No `// TODO` comments remain at specified lines
- [ ] `QueueTextureCommand()` used consistently
- [ ] UI doesn't freeze when loading maps
- [ ] `ctest -L stable` passes
### For Entity Deletion Fix
- [ ] Items actually removed from vector
- [ ] No memory leaks (items properly destroyed)
- [ ] Undo can restore deleted items (if implementing undo)
### For Dungeon Draw Routines
- [ ] Downwards objects render correctly
- [ ] Layer ordering maintained (BG1 → BG2 → BG3)
- [ ] Palette applied correctly after render

View File

@@ -0,0 +1,859 @@
# Z3ED CLI & Agent API Enhancement Design
## Executive Summary
This document outlines comprehensive enhancements to the z3ed CLI and agent APIs to significantly improve AI agent interaction with YAZE. The design focuses on enabling better automation, testing, and feature development through a robust command interface, programmatic editor access, and enhanced collaboration features.
## Current Architecture Analysis
### Existing Components
- **CLI Framework**: ModernCLI with CommandRegistry pattern
- **Command Handlers**: 70+ specialized handlers (hex, palette, sprite, music, dialogue, dungeon, overworld, gui, emulator)
- **Canvas Automation API**: Programmatic interface for tile operations, selection, and view control
- **Network Client**: WebSocket/HTTP fallback for collaborative editing
- **HTTP API**: REST endpoints for health, models, and basic operations
- **Model Integration**: Ollama and Gemini support through ModelRegistry
### Key Strengths
- Clean command handler abstraction with consistent execution pattern
- Canvas automation already supports tile operations and coordinate conversion
- Network infrastructure in place for collaboration
- Extensible model registry for multiple AI providers
### Gaps to Address
- Limited ROM direct manipulation commands
- No session persistence or REPL mode
- Minimal test generation capabilities
- Limited agent coordination features
- No batch operation support for complex workflows
- Missing introspection and discovery APIs
## 1. Z3ED CLI Enhancements
### 1.1 ROM Operations Commands
```bash
# Direct ROM manipulation
z3ed rom read --address <hex> [--length <bytes>] [--format hex|ascii|binary]
z3ed rom write --address <hex> --data <hex_string> [--verify]
z3ed rom validate [--checksums] [--headers] [--regions]
z3ed rom diff --base <rom1> --compare <rom2> [--output patch]
z3ed rom patch --input <rom> --patch <ips|bps> --output <patched_rom>
z3ed rom export --region <name> --start <hex> --end <hex> --output <file>
z3ed rom import --region <name> --address <hex> --input <file>
# ROM state management
z3ed rom snapshot --name <snapshot_name> [--compress]
z3ed rom restore --snapshot <name> [--verify]
z3ed rom list-snapshots [--details]
z3ed rom compare-snapshot --current --snapshot <name>
```
#### Implementation Details
```cpp
class RomReadCommandHandler : public CommandHandler {
protected:
absl::Status ValidateArgs(const ArgumentParser& parser) override {
RETURN_IF_ERROR(parser.RequireArgs({"address"}));
if (auto len = parser.GetInt("length")) {
if (*len <= 0 || *len > 0x10000) {
return absl::InvalidArgumentError("Length must be 1-65536 bytes");
}
}
return absl::OkStatus();
}
absl::Status Execute(Rom* rom, const ArgumentParser& parser,
OutputFormatter& formatter) override {
uint32_t address = parser.GetHex("address").value();
int length = parser.GetInt("length").value_or(16);
std::string format = parser.GetString("format").value_or("hex");
std::vector<uint8_t> data;
for (int i = 0; i < length; i++) {
data.push_back(rom->ReadByte(address + i));
}
formatter.AddField("address", absl::StrFormat("0x%06X", address));
formatter.AddField("data", FormatData(data, format));
return absl::OkStatus();
}
};
```
### 1.2 Editor Automation Commands
```bash
# Dungeon editor automation
z3ed editor dungeon place-object --room <id> --type <object_id> --x <x> --y <y>
z3ed editor dungeon remove-object --room <id> --object-index <idx>
z3ed editor dungeon set-property --room <id> --property <name> --value <val>
z3ed editor dungeon list-objects --room <id> [--filter-type <type>]
z3ed editor dungeon validate-room --room <id> [--fix-issues]
# Overworld editor automation
z3ed editor overworld set-tile --map <id> --x <x> --y <y> --tile <tile_id>
z3ed editor overworld place-entrance --map <id> --x <x> --y <y> --target <room>
z3ed editor overworld modify-sprite --map <id> --sprite-index <idx> --property <prop> --value <val>
z3ed editor overworld generate-minimap --map <id> --output <file>
# Graphics editor automation
z3ed editor graphics import-sheet --sheet <id> --file <png> [--palette <id>]
z3ed editor graphics export-sheet --sheet <id> --output <png>
z3ed editor graphics modify-palette --palette <id> --color <idx> --rgb <#RRGGBB>
# Batch operations
z3ed editor batch --script <file> [--dry-run] [--parallel]
```
#### Batch Script Format (JSON)
```json
{
"operations": [
{
"editor": "dungeon",
"action": "place-object",
"params": {
"room": 1,
"type": 0x22,
"x": 10,
"y": 15
}
},
{
"editor": "overworld",
"action": "set-tile",
"params": {
"map": 0x00,
"x": 20,
"y": 30,
"tile": 0x142
}
}
],
"options": {
"stop_on_error": false,
"validate_after": true
}
}
```
### 1.3 Testing Commands
```bash
# Test execution
z3ed test run --category <unit|integration|e2e> [--filter <pattern>]
z3ed test validate-feature --feature <name> [--rom <file>]
z3ed test generate --target <class|file> --output <test_file>
z3ed test coverage --report <html|json|text>
# Test recording
z3ed test record --name <test_name> --start
z3ed test record --stop [--save-as <file>]
z3ed test playback --file <test_file> [--speed <1-10>]
# Regression testing
z3ed test baseline --create --name <baseline>
z3ed test baseline --compare --name <baseline> [--threshold <percent>]
```
### 1.4 Build & Deploy Commands
```bash
# Build management
z3ed build --preset <preset> [--verbose] [--parallel <jobs>]
z3ed build clean [--all]
z3ed build test [--preset <preset>]
z3ed build package --platform <win|mac|linux> --output <dir>
# CI/CD integration
z3ed ci status [--workflow <name>]
z3ed ci trigger --workflow <name> [--branch <branch>]
z3ed ci logs --run-id <id> [--follow]
z3ed ci artifacts --run-id <id> --download <path>
```
### 1.5 Query & Introspection Interface
```bash
# System queries
z3ed query rom-info [--detailed]
z3ed query test-status [--failures-only]
z3ed query build-status [--preset <preset>]
z3ed query available-commands [--category <cat>] [--format tree|list|json]
# Data queries
z3ed query find-tiles --pattern <hex> [--context <bytes>]
z3ed query find-sprites --type <id> [--map <id>]
z3ed query find-text --search <string> [--case-sensitive]
z3ed query dependencies --entity <type:id>
# Statistics
z3ed query stats --type <rom|dungeon|overworld|sprites>
z3ed query usage --command <name> [--since <date>]
```
### 1.6 Interactive REPL Mode
```bash
# Start REPL
z3ed repl [--rom <file>] [--history <file>]
# REPL Features:
# - Persistent ROM state across commands
# - Command history with arrow keys
# - Tab completion for commands and parameters
# - Context-aware suggestions
# - Session recording/playback
# - Variable assignment ($var = command output)
# - Pipes and filters (command1 | command2)
```
#### REPL Implementation
```cpp
class ReplSession {
Rom* rom_;
std::map<std::string, json> variables_;
std::vector<std::string> history_;
public:
absl::Status ProcessLine(const std::string& line) {
// Parse for variable assignment
if (auto var_match = ParseVariableAssignment(line)) {
auto result = ExecuteCommand(var_match->command);
variables_[var_match->var_name] = result;
return absl::OkStatus();
}
// Parse for pipes
if (auto pipe_commands = ParsePipe(line)) {
json previous_output;
for (const auto& cmd : *pipe_commands) {
auto expanded = ExpandVariables(cmd, previous_output);
previous_output = ExecuteCommand(expanded);
}
return absl::OkStatus();
}
// Simple command
return ExecuteCommand(line);
}
};
```
## 2. Agent API Improvements
### 2.1 Enhanced Canvas Automation API
```cpp
namespace yaze {
namespace gui {
class EnhancedCanvasAutomationAPI : public CanvasAutomationAPI {
public:
// Object selection by properties
struct ObjectQuery {
std::optional<int> type_id;
std::optional<ImVec2> position_min;
std::optional<ImVec2> position_max;
std::optional<std::string> name_pattern;
std::map<std::string, std::any> properties;
};
std::vector<ObjectHandle> FindObjects(const ObjectQuery& query) const;
// Batch operations
struct BatchOperation {
enum Type { MOVE, MODIFY, DELETE, DUPLICATE };
Type type;
std::vector<ObjectHandle> objects;
std::map<std::string, std::any> parameters;
};
absl::Status ExecuteBatch(const std::vector<BatchOperation>& ops);
// Validation queries
bool IsValidPlacement(ObjectHandle obj, ImVec2 position) const;
std::vector<std::string> GetPlacementErrors(ObjectHandle obj, ImVec2 pos) const;
// Event simulation
void SimulateClick(ImVec2 position, int button = 0);
void SimulateDrag(ImVec2 from, ImVec2 to);
void SimulateKeyPress(ImGuiKey key, bool shift = false, bool ctrl = false);
void SimulateContextMenu(ImVec2 position);
// Advanced queries
struct CanvasStatistics {
int total_objects;
std::map<int, int> objects_by_type;
float canvas_coverage_percent;
ImVec2 bounding_box_min;
ImVec2 bounding_box_max;
};
CanvasStatistics GetStatistics() const;
// Undo/Redo support
bool CanUndo() const;
bool CanRedo() const;
void Undo();
void Redo();
std::vector<std::string> GetUndoHistory(int count = 10) const;
};
} // namespace gui
} // namespace yaze
```
### 2.2 Programmatic Editor Access
```cpp
namespace yaze {
namespace app {
class EditorAutomationAPI {
public:
// Editor lifecycle
absl::Status OpenEditor(EditorType type, const std::string& params = "");
absl::Status CloseEditor(EditorHandle handle);
std::vector<EditorHandle> GetOpenEditors() const;
// State snapshots
absl::StatusOr<EditorSnapshot> CaptureState(EditorHandle editor);
absl::Status RestoreState(EditorHandle editor, const EditorSnapshot& snapshot);
absl::Status CompareStates(const EditorSnapshot& s1, const EditorSnapshot& s2);
// Query current state
struct EditorState {
EditorType type;
std::string name;
bool has_unsaved_changes;
std::map<std::string, std::any> properties;
std::vector<std::string> available_actions;
};
EditorState GetState(EditorHandle editor) const;
// Execute operations
absl::Status ExecuteAction(EditorHandle editor,
const std::string& action,
const json& parameters);
// Event subscription
using EventCallback = std::function<void(const EditorEvent&)>;
void Subscribe(EditorHandle editor, EventType type, EventCallback cb);
void Unsubscribe(EditorHandle editor, EventType type);
// Validation
absl::Status ValidateEditor(EditorHandle editor);
std::vector<ValidationIssue> GetValidationIssues(EditorHandle editor);
};
} // namespace app
} // namespace yaze
```
### 2.3 Test Generation API
```cpp
namespace yaze {
namespace test {
class TestGenerationAPI {
public:
// Record interactions
void StartRecording(const std::string& test_name);
void StopRecording();
void PauseRecording();
void ResumeRecording();
// Generate tests from recordings
absl::StatusOr<std::string> GenerateTestCode(
const std::string& test_name,
TestFramework framework = TestFramework::GTEST);
// Generate tests from specifications
struct TestSpecification {
std::string class_under_test;
std::vector<std::string> methods_to_test;
bool include_edge_cases = true;
bool include_error_cases = true;
bool generate_mocks = true;
};
absl::StatusOr<std::string> GenerateTests(const TestSpecification& spec);
// Test fixtures from state
absl::StatusOr<std::string> GenerateFixture(EditorHandle editor);
// Regression test generation
absl::StatusOr<std::string> GenerateRegressionTest(
const std::string& bug_description,
const std::vector<std::string>& repro_steps);
// Test execution
struct TestResult {
bool passed;
std::string output;
double execution_time_ms;
std::vector<std::string> failures;
};
absl::StatusOr<TestResult> RunGeneratedTest(const std::string& test_code);
};
} // namespace test
} // namespace yaze
```
## 3. Agent UI Enhancements
### 3.1 Status Dashboard
```cpp
class AgentStatusDashboard : public Panel {
public:
void Draw() override {
// Real-time agent activity
DrawAgentActivity();
// Test execution progress
DrawTestProgress();
// Build/CI status
DrawBuildStatus();
// Recent changes
DrawRecentChanges();
// Performance metrics
DrawPerformanceMetrics();
}
private:
struct AgentActivity {
std::string agent_name;
std::string current_task;
float progress_percent;
std::chrono::steady_clock::time_point started_at;
};
std::vector<AgentActivity> active_agents_;
void DrawAgentActivity() {
ImGui::Text("Active Agents");
for (const auto& agent : active_agents_) {
ImGui::ProgressBar(agent.progress_percent / 100.0f,
ImVec2(-1, 0),
agent.current_task.c_str());
}
}
};
```
### 3.2 Agent Control Panel
```cpp
class AgentControlPanel : public Panel {
public:
void Draw() override {
// Agent task management
if (ImGui::Button("Start New Task")) {
ShowTaskDialog();
}
// Active tasks
DrawActiveTasks();
// Agent logs
DrawAgentLogs();
// Manual intervention
DrawInterventionControls();
// Collaboration coordination
DrawCollaborationStatus();
}
private:
void ShowTaskDialog() {
ImGui::OpenPopup("New Agent Task");
if (ImGui::BeginPopupModal("New Agent Task")) {
static char task_name[256];
ImGui::InputText("Task Name", task_name, sizeof(task_name));
static int selected_agent = 0;
ImGui::Combo("Agent", &selected_agent, available_agents_);
if (ImGui::Button("Start")) {
StartAgentTask(task_name, selected_agent);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
};
```
## 4. Network/Collaboration Features
### 4.1 Multi-Agent Coordination
```cpp
namespace yaze {
namespace agent {
class MultiAgentCoordinator {
public:
// Agent registration
absl::Status RegisterAgent(const AgentInfo& info);
absl::Status UnregisterAgent(const std::string& agent_id);
// Work queue management
absl::Status QueueTask(const Task& task);
absl::StatusOr<Task> ClaimTask(const std::string& agent_id);
absl::Status CompleteTask(const std::string& task_id, const TaskResult& result);
// Shared state
absl::Status UpdateSharedState(const std::string& key, const json& value);
absl::StatusOr<json> GetSharedState(const std::string& key);
absl::Status SubscribeToState(const std::string& key, StateCallback cb);
// Conflict resolution
enum ConflictStrategy {
LAST_WRITE_WINS,
MERGE,
MANUAL_RESOLUTION,
QUEUE_SEQUENTIAL
};
absl::Status SetConflictStrategy(ConflictStrategy strategy);
absl::StatusOr<Resolution> ResolveConflict(const Conflict& conflict);
// Agent discovery
std::vector<AgentInfo> DiscoverAgents(const AgentQuery& query);
absl::StatusOr<AgentCapabilities> GetCapabilities(const std::string& agent_id);
};
} // namespace agent
} // namespace yaze
```
### 4.2 Remote z3ed Access
```yaml
# OpenAPI 3.0 Specification
openapi: 3.0.0
info:
title: Z3ED Remote API
version: 1.0.0
paths:
/api/v1/command:
post:
summary: Execute z3ed command
requestBody:
content:
application/json:
schema:
type: object
properties:
command:
type: string
example: "rom read --address 0x1000 --length 16"
session_id:
type: string
timeout_ms:
type: integer
default: 30000
responses:
200:
description: Command executed successfully
content:
application/json:
schema:
type: object
properties:
result:
type: object
execution_time_ms:
type: number
/api/v1/session:
post:
summary: Create new z3ed session
requestBody:
content:
application/json:
schema:
type: object
properties:
rom_path:
type: string
persist:
type: boolean
default: false
responses:
200:
description: Session created
content:
application/json:
schema:
type: object
properties:
session_id:
type: string
expires_at:
type: string
format: date-time
/api/v1/websocket:
get:
summary: WebSocket endpoint for real-time updates
responses:
101:
description: Switching Protocols
```
### 4.3 WebSocket Protocol
```typescript
// WebSocket message types
interface Z3edWebSocketMessage {
type: 'command' | 'event' | 'subscribe' | 'unsubscribe';
id: string;
payload: any;
}
// Command execution
interface CommandMessage {
type: 'command';
id: string;
payload: {
command: string;
args: string[];
stream: boolean; // Stream output as it's generated
};
}
// Event subscription
interface SubscribeMessage {
type: 'subscribe';
id: string;
payload: {
events: Array<'editor.changed' | 'test.completed' | 'build.status'>;
};
}
// Server events
interface EventMessage {
type: 'event';
id: string;
payload: {
event: string;
data: any;
timestamp: string;
};
}
```
## 5. Implementation Plan
### Phase 1: Foundation (Weeks 1-2)
1. Implement core ROM operations commands
2. Add REPL infrastructure
3. Enhance Canvas Automation API with batch operations
4. Create command discovery/introspection system
### Phase 2: Editor Integration (Weeks 3-4)
1. Implement editor automation commands
2. Add programmatic editor access API
3. Create test recording infrastructure
4. Build event subscription system
### Phase 3: Testing & CI (Weeks 5-6)
1. Implement test generation API
2. Add test execution commands
3. Create CI/CD integration commands
4. Build regression test framework
### Phase 4: Collaboration (Weeks 7-8)
1. Implement multi-agent coordinator
2. Add REST API endpoints
3. Create WebSocket real-time protocol
4. Build conflict resolution system
### Phase 5: UI & Polish (Weeks 9-10)
1. Create Agent Status Dashboard
2. Build Agent Control Panel
3. Add comprehensive documentation
4. Create example workflows
## 6. Example Workflows
### Workflow 1: Automated Dungeon Testing
```bash
# Start REPL session
z3ed repl --rom zelda3.sfc
# Record baseline
> rom snapshot --name baseline
> editor dungeon --room 0
# Start test recording
> test record --name dungeon_placement_test --start
# Perform operations
> editor dungeon place-object --room 0 --type 0x22 --x 10 --y 15
> editor dungeon place-object --room 0 --type 0x23 --x 20 --y 15
> query stats --type dungeon
# Stop and generate test
> test record --stop
> test generate --from-recording dungeon_placement_test --output test_dungeon.cc
```
### Workflow 2: Multi-Agent ROM Editing
```python
import z3ed_client
# Agent 1: Overworld specialist
agent1 = z3ed_client.Agent("overworld_agent")
agent1.connect("localhost:8080")
# Agent 2: Dungeon specialist
agent2 = z3ed_client.Agent("dungeon_agent")
agent2.connect("localhost:8080")
# Coordinator assigns tasks
coordinator = z3ed_client.Coordinator()
coordinator.queue_task({
"type": "overworld",
"action": "optimize_tilemap",
"map_id": 0x00
})
coordinator.queue_task({
"type": "dungeon",
"action": "validate_rooms",
"rooms": range(0, 296)
})
# Agents work in parallel
results = coordinator.wait_for_completion()
```
### Workflow 3: AI-Powered Test Generation
```bash
# Analyze class for test generation
z3ed test analyze --class OverworldEditor
# Generate comprehensive tests
z3ed test generate \
--target OverworldEditor \
--include-edge-cases \
--include-mocks \
--framework gtest \
--output overworld_editor_test.cc
# Run generated tests
z3ed test run --file overworld_editor_test.cc --verbose
# Create regression test from bug
z3ed test regression \
--bug "Tiles corrupt when placing entrance at map boundary" \
--repro-steps "1. Open map 0x00" "2. Place entrance at x=511,y=511" \
--output regression_boundary_test.cc
```
## 7. Security Considerations
### Authentication & Authorization
- API key authentication for remote access
- Role-based permissions (read-only, editor, admin)
- Session management with expiration
- Rate limiting per API key
### Input Validation
- Command injection prevention
- Path traversal protection
- Memory address validation
- File size limits for imports
### Audit Logging
- All commands logged with timestamp and user
- ROM modifications tracked
- Rollback capability for destructive operations
- Export audit trail for compliance
## 8. Performance Optimizations
### Caching
- Command result caching for repeated queries
- ROM state caching for snapshots
- Compiled test cache
- WebSocket connection pooling
### Batch Processing
- Aggregate multiple operations into transactions
- Parallel execution for independent commands
- Lazy loading for large data sets
- Progressive streaming for long operations
### Resource Management
- Connection limits per client
- Memory quotas for sessions
- CPU throttling for intensive operations
- Graceful degradation under load
## 9. Documentation Requirements
### API Reference
- Complete command reference with examples
- REST API OpenAPI specification
- WebSocket protocol documentation
- Error code reference
### Tutorials
- "Getting Started with z3ed REPL"
- "Automating ROM Testing"
- "Multi-Agent Collaboration"
- "Building Custom Commands"
### Integration Guides
- Python client library
- JavaScript/TypeScript SDK
- CI/CD integration examples
- VS Code extension
### Best Practices
- Command naming conventions
- Error handling patterns
- Performance optimization tips
- Security guidelines
## 10. Success Metrics
### Functionality
- 100% coverage of editor operations via CLI
- < 100ms command execution for simple operations
- < 1s for complex batch operations
- 99.9% API availability
### Developer Experience
- Tab completion for all commands
- Comprehensive error messages
- Interactive help system
- Example for every command
### Testing
- 90% code coverage for new components
- Automated regression tests for all commands
- Performance benchmarks for critical paths
- Integration tests for multi-agent scenarios
## Conclusion
These enhancements will transform z3ed from a basic CLI tool into a comprehensive automation platform for YAZE. The design prioritizes developer experience, AI agent capabilities, and robust testing infrastructure while maintaining backwards compatibility and performance.
The modular implementation plan allows for incremental delivery of value, with each phase providing immediately useful functionality. The foundation laid here will enable future innovations in ROM hacking automation and collaborative editing.

View File

@@ -0,0 +1,36 @@
# Agent Coordination & Documentation Improvement Plan
## Findings
1. **Persona Inconsistency**: `coordination-board.md` uses a mix of legacy IDs (`CLAUDE_AIINF`) and new canonical IDs (`ai-infra-architect`).
2. **Tool Underutilization**: The protocol in `AGENTS.md` relies entirely on manual Markdown edits, ignoring the built-in `z3ed agent` CLI tools (todo, handoff) described in `agent-architecture.md`.
3. **Fragmented Docs**: There is no central entry point (`README.md`) for agents entering the directory.
4. **Undefined Systems**: `claude-gemini-collaboration.md` references a "challenge system" and "leaderboard" that do not exist.
## Proposed Actions
### 1. Update `AGENTS.md` (The Protocol)
* **Mandate CLI Tools**: Update the "Quick tasks" and "Substantial work" sections to recommend using `z3ed agent todo` for personal task tracking.
* **Clarify Handoffs**: Explicitly mention using `z3ed agent handoff` for transferring context, with the Markdown board used for *public signaling*.
* **Strict Persona Usage**: Remove "Legacy aliases" mapping and simply link to `personas.md` as the source of truth.
### 2. Cleanup `coordination-board.md` (The Board)
* **Header Update**: Add a bold warning to use only IDs from `personas.md`.
* **Retroactive Fix**: Update recent active entries to use the correct new IDs (e.g., convert `CLAUDE_AIINF` -> `ai-infra-architect` where appropriate).
### 3. Create `docs/internal/agents/README.md` (The Hub)
* Create a simple index file that links to:
* **Protocol**: `AGENTS.md`
* **Roles**: `personas.md`
* **Status**: `coordination-board.md`
* **Tools**: `agent-architecture.md`
* Provide a 3-step "Start Here" guide for new agents.
### 4. Deprecate `claude-gemini-collaboration.md`
* Rename to `docs/internal/agents/archive/collaboration-concept-legacy.md` or remove the "Challenge System" sections if the file is still valuable for its architectural definitions.
* *Recommendation*: If the "Architecture vs. Automation" split is still relevant, update the file to use `backend-infra-engineer` (Architecture) vs `GEMINI_AUTOM` (Automation) instead of "Claude vs Gemini".
## Execution Order
1. Create `docs/internal/agents/README.md`.
2. Update `AGENTS.md`.
3. Clean up `coordination-board.md`.
4. Refactor `claude-gemini-collaboration.md`.

View File

@@ -0,0 +1,132 @@
# Documentation Cleanup Summary - November 2025
**Date:** 2025-11-24
**Action:** Cleanup of speculative planning documents and AI-generated bloat from `/docs/internal/plans/`
**Rationale:** Planning documents should live in GitHub issues and coordination board, not as static markdown. Only actionable, actively-tracked plans belong in the codebase.
## Files Deleted (Pure AI-Generated Bloat)
These files were entirely speculative with no corresponding implementation:
1. **asm-debug-prompt-engineering.md** (45KB)
- Extensive prompt templates for 65816 debugging
- No evidence of integration or use
- Classified as AI-generated reference material
2. **ai-assisted-development-plan.md** (17KB)
- Workflow proposal for AI-assisted development
- All features marked "Active" with "Next Review" dates but never implemented
- Generic architecture diagrams with no corresponding code
3. **app-dev-agent-tools.md** (21KB)
- 824 lines specifying 16 agent tools (build, code analysis, debug, editor integration)
- All tools in Phase 1-3 (theoretical)
- No implementation in codebase or recent commits
4. **EDITOR_ROADMAPS_2025-11.md** (25KB)
- Multi-agent analysis document referencing "imgui-frontend-engineer" agent analysis
- Generic roadmap format with estimated effort hours
- Duplicate content with dungeon_editor_ui_refactor.md
5. **message_system_improvement_plan.md** (2KB)
- Duplicate of sections in message_editor_implementation_roadmap.md
- Generic feature wishlist (JSON export, translation workspace, search & replace)
- No distinct value
6. **graphics_system_improvement_plan.md** (2.8KB)
- Feature wishlist (unified editor, palette management, sprite assembly)
- No concrete deliverables or implementation plan
- Superseded by architecture documentation
7. **ui_modernization.md** (3.3KB)
- Describes patterns already documented in CLAUDE.md
- Marked "Active" but content is obsolete (already implemented)
- Redundant with existing guidelines
## Files Archived (Partially Implemented / Historical Reference)
These files have some value as reference but are not actively tracked work:
1. **emulator-debug-api-design.md**`archive/plans-2025-11/`
- Design document for emulator debugging API
- Some features implemented (breakpoints, memory inspection)
- Watchpoints and symbol loading still planned but deprioritized
- Value: Technical reference for future work
2. **message_editor_implementation_roadmap.md**`archive/plans-2025-11/`
- References actual code (MessageData, MessagePreview classes)
- Documents what's completed vs. what's missing (JSON import/export)
- Some ongoing development but should be tracked in coordination board
- Value: Implementation reference
3. **hex_editor_enhancements.md**`archive/plans-2025-11/`
- Phase 1 (Data Inspector) concept defined
- Phases 2-4 unimplemented
- Better tracked as GitHub issue than static plan
- Value: Technical spike reference
4. **dungeon_editor_ui_refactor.md**`archive/plans-2025-11/`
- Actually referenced in git commit acab491a1f (component was removed)
- Concrete refactoring steps with clear deliverables
- Now completed/obsolete
- Value: Historical record of refactoring
## Files Retained (Actively Tracked)
1. **web_port_strategy.md**
- Strategic milestone document for WASM port
- Multiple recent commits show active ongoing work (52b1a99, 56e05bf, 206e926, etc.)
- Clear milestones with deliverables
- Actively referenced in CI/build processes
- Status: KEEP - actively developed feature
2. **ai-infra-improvements.md**
- Structured phase-based plan (gRPC server, emulator RPCs, breakpoints, symbols)
- More specific than other plans with concrete files to modify
- Tracks infrastructure gaps with file references
- Status: KEEP - tracks ongoing infrastructure work
3. **README.md**
- Directory governance and guidelines
- Actively enforces plan organization standards
- Status: KEEP - essential for directory management
## Rationale for This Cleanup
### Problem
- 14 planning files (400KB) in `/docs/internal/plans/`
- Most marked "Active" with forward-looking dates (Nov 25 → Dec 2)
- Little correlation with actual work in recent commits
- Directory becoming a repository of speculative AI-generated content
### Solution
- Keep only plans with active ongoing work (2 files)
- Archive reference documents with partial implementation (4 files)
- Delete pure speculation and AI-generated bloat (7 files)
- Directory size reduced from 400KB to 13KB at root level
### Principle
**Planning belongs in GitHub issues and coordination board, not in markdown files.**
Static plan documents should only exist for:
1. Strategic initiatives (like WASM web port) with active commits
2. Infrastructure work with concrete phases and file references
3. Historical reference after completion
Speculative planning should use:
- GitHub Discussions for RFCs
- Issues with labels for feature requests
- Coordination board for multi-agent work tracking
## Files in Archive
```
docs/internal/agents/archive/plans-2025-11/
├── CLEANUP_SUMMARY.md
├── dungeon_editor_ui_refactor.md
├── emulator-debug-api-design.md
├── hex_editor_enhancements.md
└── message_editor_implementation_roadmap.md
```
These are available if needed as historical context but should not be referenced for active development.

View File

@@ -0,0 +1,48 @@
# Dungeon Editor UI Refactor Plan
## 1. Overview
The Dungeon Editor currently uses a primitive "colored square" representation for objects in the object selector, despite having a full-fidelity rendering component (`DungeonObjectSelector`) available. This plan outlines the refactoring steps to integrate the game-accurate object browser into the main `ObjectEditorCard`, improving UX and eliminating code duplication.
## 2. Current State Analysis
- **`ObjectEditorCard` (Active UI):** Reimplements object selection logic in `DrawObjectSelector()`. Renders objects as simple colored rectangles (`DrawObjectPreviewIcon`).
- **`DungeonObjectSelector` (Component):** Contains `DrawObjectAssetBrowser()`, which uses `ObjectDrawer` to render actual tile graphics. This component is instantiated as a member `object_selector_` in `ObjectEditorCard` but is effectively unused.
- **`DungeonEditorV2`:** Instantiates a separate, unused `DungeonObjectSelector` (`object_selector_`), adding to the confusion.
## 3. Implementation Plan
### Phase 1: Component Preparation
1. **Expose UI Method:** Ensure `DungeonObjectSelector::DrawObjectAssetBrowser()` is public or accessible to `ObjectEditorCard`.
2. **State Synchronization:** Ensure `DungeonObjectSelector` has access to the same `Rom` and `PaletteGroup` data as `ObjectEditorCard` so it can render correctly.
### Phase 2: Refactor ObjectEditorCard
1. **Delegate Rendering:** Replace the body of `ObjectEditorCard::DrawObjectSelector()` with a call to `object_selector_.DrawObjectAssetBrowser()`.
2. **Callback Wiring:**
* In `ObjectEditorCard::Initialize` (or constructor), set up the callback for `object_selector_`.
* When an object is selected in `object_selector_`, it should update `ObjectEditorCard::preview_object_` and `canvas_viewer_`.
* Current logic:
```cpp
object_selector_.SetObjectSelectedCallback([this](const zelda3::RoomObject& obj) {
this->preview_object_ = obj;
this->has_preview_object_ = true;
this->canvas_viewer_->SetPreviewObject(obj);
this->interaction_mode_ = InteractionMode::Place;
});
```
3. **Cleanup:** Remove private helper `DrawObjectPreviewIcon` and the old loop logic in `ObjectEditorCard`.
### Phase 3: Cleanup DungeonEditorV2
1. **Remove Redundancy:** Remove the top-level `DungeonObjectSelector object_selector_` from `DungeonEditorV2`. The one inside `ObjectEditorCard` is sufficient.
2. **Verify Initialization:** Ensure `DungeonEditorV2` correctly initializes `ObjectEditorCard` with the necessary dependencies.
## 4. Verification
1. **Build:** Compile `yaze`.
2. **Test:** Open Dungeon Editor -> Object Editor tab.
3. **Expectation:** The object list should now show actual graphics (walls, chests, pots) instead of colored squares.
4. **Interaction:** Clicking an object should correctly load it into the cursor for placement.
## 5. Dependencies
- `src/app/editor/dungeon/object_editor_card.cc`
- `src/app/editor/dungeon/object_editor_card.h`
- `src/app/editor/dungeon/dungeon_object_selector.cc`
- `src/app/editor/dungeon/dungeon_object_selector.h`
- `src/app/editor/dungeon/dungeon_editor_v2.h`

View File

@@ -1,5 +1,11 @@
# Emulator Debug API Design for AI Agent Integration
**Status:** Active
**Owner (Agent ID):** snes-emulator-expert
**Last Updated:** 2025-11-25
**Next Review:** 2025-12-02
**Coordination Board Entry:** link when claimed
## Executive Summary
This document outlines the design for a comprehensive debugging API that enables AI agents to debug Zelda ROM hacks through the yaze emulator. The API provides execution control, memory inspection, disassembly, and analysis capabilities specifically tailored for 65816 and SPC700 debugging.
@@ -507,4 +513,4 @@ class DebuggerToolHandler {
This debugging API design provides a comprehensive foundation for AI agents to effectively debug SNES ROM hacks. The phased approach ensures quick delivery of core features while building toward advanced analysis capabilities. The integration with existing yaze infrastructure and focus on 65816-specific debugging makes this a powerful tool for ROM hacking assistance.
The API balances technical depth with usability, providing both low-level control for precise debugging and high-level analysis for pattern recognition. This enables AI agents to assist with everything from simple crash debugging to complex performance optimization.
The API balances technical depth with usability, providing both low-level control for precise debugging and high-level analysis for pattern recognition. This enables AI agents to assist with everything from simple crash debugging to complex performance optimization.

View File

@@ -0,0 +1,80 @@
# Plan: Hex Editor Enhancements (Inspired by ImHex)
**Status:** Active
**Owner (Agent ID):** imgui-frontend-engineer
**Last Updated:** 2025-11-25
**Next Review:** 2025-12-02
**Coordination Board Entry:** link when claimed
This document outlines the roadmap for enhancing the `yaze` Memory/Hex Editor to provide robust analysis tools similar to ImHex.
## Phase 1: Data Inspector (High Priority)
**Goal:** Provide immediate context for the selected byte(s) in the Hex Editor without mental math.
**Implementation Steps:**
1. **Create `DataInspector` Component:**
* A standalone ImGui widget (`src/app/editor/code/data_inspector.h/cc`).
* Accepts a `const uint8_t* data_ptr` and `size_t max_len`.
2. **Standard Types:**
* Decode and display Little Endian values:
* `int8_t` / `uint8_t`
* `int16_t` / `uint16_t`
* `int24_t` / `uint24_t` (Common SNES pointers)
* `int32_t` / `uint32_t`
* Display Binary representation (`00001111`).
3. **SNES-Specific Types (The "Yaze" Value):**
* **SNES LoROM Address:** Convert the physical offset to `$BB:AAAA` format.
* **RGB555 Color:** Interpret 2 bytes as `0bbbbbgggggrrrrr`. Show a colored rectangle preview.
* **Tile Attribute:** Interpret byte as `vhopppcc` (Vertical/Horizontal flip, Priority, Palette, Tile High bit).
4. **Integration:**
* Modify `MemoryEditorWithDiffChecker` to instantiate and render `DataInspector` in a sidebar or child window next to the main hex grid.
* Hook into the hex editor's "Selection Changed" event (or poll selection state) to update the Inspector.
## Phase 2: Entropy Navigation (Navigation)
**Goal:** Visualize the ROM's structure to quickly find free space, graphics, or code.
**Implementation Steps:**
1. **Entropy Calculator:**
* Create a utility to calculate Shannon entropy for blocks of data (e.g., 256-byte chunks).
* Run this calculation in a background thread when a ROM is loaded to generate an `EntropyMap`.
2. **Minimap Widget:**
* Render a thin vertical bar next to the hex scrollbar.
* Map the file offset to vertical pixels.
* **Color Coding:**
* **Black:** Zeroes (`0x00` fill).
* **Dark Grey:** `0xFF` fill (common flash erase value).
* **Blue:** Low entropy (Text, Tables).
* **Red:** High entropy (Compressed Graphics, Code).
3. **Interaction:**
* Clicking the minimap jumps the Hex Editor to that offset.
## Phase 3: Structure Templates (Advanced Analysis)
**Goal:** Define and visualize complex data structures on top of the raw hex.
**Implementation Steps:**
1. **Template Definition System:**
* Define a C++-based schema builder (e.g., `StructBuilder("Header").AddString("Title", 21).AddByte("MapMode")...`).
2. **Visualizer:**
* Render these structures as a tree view (ImGui TreeNodes).
* When a tree node is hovered, highlight the corresponding bytes in the Hex Editor grid.
3. **Standard Templates:**
* Implement templates for known ALTTP structures: `SNES Header`, `Dungeon Header`, `Sprite Properties`.
## Phase 4: Disassembly Integration
**Goal:** Seamless transition between data viewing and code analysis.
**Implementation Steps:**
1. **Context Menu:** Add "Disassemble Here" to the Hex Editor right-click menu.
2. **Disassembly View:**
* Invoke the `disassembler` (already present in `app/emu/debug`) on the selected range.
* Display the output in a popup or switch to the Assembly Editor.
---
## Initial Work Item: Data Inspector
We will begin with Phase 1. This requires creating the `DataInspector` class and hooking it into `MemoryEditorWithDiffChecker`.

View File

@@ -1,8 +1,10 @@
# Message Editor Implementation Roadmap
**Status**: Active Development
**Last Updated**: 2025-11-21
**Owner**: Frontend/UI Team
**Status**: Active Development
**Owner (Agent ID)**: imgui-frontend-engineer
**Last Updated**: 2025-11-25
**Next Review**: 2025-12-02
**Coordination Board Entry**: link when claimed
**Related Docs**:
- `docs/internal/architecture/message_system.md` (Gemini's architecture vision)
- `docs/internal/plans/message_system_improvement_plan.md` (Gemini's feature proposals)

View File

@@ -0,0 +1,436 @@
# Architecture Documentation Review & Improvements Report
**Review Date**: November 21, 2025
**Reviewed By**: Claude Documentation Janitor
**Status**: Complete
**Documents Reviewed**: 8 architecture files
## Executive Summary
A comprehensive review of Gemini's architecture documentation has been completed. The documentation is well-structured and generally accurate, with clear explanations of complex systems. Several improvements have been made to enhance accuracy, completeness, and usability:
**Key Accomplishments**:
- Consolidated duplicate graphics system documentation
- Enhanced accuracy with specific file paths and method signatures
- Added best practices and contributing guidelines
- Created comprehensive navigation hub (README.md)
- Added cross-references to CLAUDE.md
- Improved technical depth and implementation details
**Quality Assessment**: **Good Work Overall** - Documentation demonstrates solid understanding of the codebase with clear explanations and good organization. Improvements focused on completeness and precision.
---
## Document-by-Document Review
### 1. graphics_system_architecture.md & graphics_system.md
**Status**: CONSOLIDATED & ENHANCED
**Original Issues**:
- Two separate files covering similar content (69 lines vs 74 lines each)
- Partial overlap in coverage (both covered Arena, Bitmap, compression)
- graphics_system.md lacked detailed rendering pipeline explanation
- Missing specific method signatures
- No best practices section
**Improvements Made**:
✓ Merged both documents into enhanced graphics_system_architecture.md
✓ Added comprehensive rendering pipeline with 4 distinct phases
✓ Added detailed component descriptions with key methods
✓ Added structured table for compression formats with sheet indices
✓ Enhanced Canvas Interactions section with coordinate system details
✓ Added Best Practices section (6 key practices)
✓ Expanded Future Improvements section
✓ Added specific decompression function names: `DecompressV2()`, `CompressV3()`
✓ Deleted duplicate graphics_system.md
**Code Accuracy Verified**:
- Arena holds 223 Bitmap objects (confirmed in arena.h line 82)
- Compression functions exist as documented (compression.h lines 226, 241)
- IRenderer pattern documented correctly (irenderer.h exists)
- BackgroundBuffer management confirmed (arena.h lines 127-128)
**Lines of Documentation**: 178 (consolidated from 143, increased for comprehensiveness)
---
### 2. dungeon_editor_system.md
**Status**: SIGNIFICANTLY ENHANCED
**Original Issues**:
- Component descriptions lacked full context
- Missing file paths in location column
- No best practices for contributors
- Future Improvements section was minimal
- No discussion of callback-based communication pattern
- Missing discussion of coordinate system details
**Improvements Made**:
✓ Added architectural pattern explanation (coordinator pattern)
✓ Expanded Key Components table with full file paths (8 → 8 entries, more detail)
✓ Added DungeonRoomSelector and DungeonObjectValidator to component table
✓ Added "Best Practices for Contributors" section with:
- Guidelines for adding new editor modes (6 steps)
- Object editing best practices with code examples
- Callback communication patterns
- Coordinate system management guidance
- Batch operation efficiency tips
✓ Expanded Future Improvements (3 → 6 improvements)
✓ Enhanced Interaction Flow with more detail
✓ Added coordinate system explanation (0-63 grid)
**Code Accuracy Verified**:
- DungeonEditorV2 inherits from Editor (dungeon_editor_v2.h line 42)
- DungeonEditorSystem has EditorMode enum with kObjects mode (dungeon_editor_system.h lines 40-49)
- UndoPoint structure confirmed (dungeon_object_editor.h line 93)
- Component file paths verified in actual directory structure
**Added Practical Guidance**: Code examples and step-by-step procedures for common tasks
---
### 3. room_data_persistence.md
**Status**: ENHANCED WITH DETAILS
**Original Issues**:
- Lacked specific ROM address information
- Missing details about pointer tables
- No discussion of thread safety
- Method signatures incomplete
- Bank boundary considerations mentioned but not explained
**Improvements Made**:
✓ Added specific method signatures and parameters
✓ Enhanced description of room ID range (0x000-0x127)
✓ Clarified pointer table lookup process
✓ Emphasized critical importance of repointing logic
✓ Expanded ROM address references with constants from dungeon_rom_addresses.h
✓ Better explanation of room size calculation for safety
✓ Clarified thread safety aspects of bulk loading
**Code Accuracy Verified**:
- LoadRoom method signature confirmed (dungeon_room_loader.h line 26)
- LoadAllRooms uses std::array<zelda3::Room, 0x128> (dungeon_room_loader.h line 27)
- Room ID range 0x000-0x127 confirmed (296 rooms = 0x128)
**Still Accurate**: Saving strategy marked as "Planned/In-Progress" - correctly reflects implementation status
---
### 4. undo_redo_system.md
**Status**: VERIFIED ACCURATE
**Verification Results**:
✓ UndoPoint structure matches implementation exactly (dungeon_object_editor.h lines 93-98)
- objects: std::vector<RoomObject>
- selection: SelectionState
- editing: EditingState
- timestamp: std::chrono::steady_clock::time_point
✓ Undo/Redo workflow documented correctly
✓ Best practices align with implementation patterns
✓ Batch operation guidance is sound
**No Changes Required**: Documentation is accurate and complete as written
**Quality Notes**: Clear explanation of state snapshot pattern, good guidance on batch operations
---
### 5. overworld_editor_system.md
**Status**: VERIFIED ACCURATE
**Verification Results**:
✓ All component descriptions match actual classes
✓ Interaction flow accurately describes actual workflow
✓ Coordinate systems explanation is correct
✓ Large maps configuration documented correctly
✓ Deferred loading section accurately describes implementation
**Code Cross-Check Completed**:
- OverworldEditor class confirmed (overworld_editor.h line 64)
- Overworld system coordinator pattern verified
- OverworldMap data model description accurate
- Entity renderer pattern confirmed
**No Changes Required**: Documentation is accurate and comprehensive
---
### 6. overworld_map_data.md
**Status**: VERIFIED ACCURATE WITH ENHANCEMENTS
**Original Issues**:
- Good documentation but could be more precise with ROM address constants
- ZSCustomOverworld section mentioned need to verify exact implementation
**Improvements Made**:
✓ Verified all ROM address constants against overworld_map.h
✓ Confirmed ZSCustomOverworld property names and storage locations
✓ Storage locations now explicitly listed (OverworldCustomAreaSpecificBGPalette, etc.)
✓ Cross-referenced with overworld.h for implementation accuracy
**Code Cross-Check Completed**:
- OverworldMap ROM addresses verified (overworld_map.h lines 21-73)
- Custom property constants confirmed:
- OverworldCustomAreaSpecificBGPalette = 0x140000 (line 21)
- OverworldCustomMosaicArray = 0x140200 (line 39)
- OverworldCustomSubscreenOverlayArray = 0x140340 (line 28)
- OverworldCustomAnimatedGFXArray = 0x1402A0 (line 31)
- ZSCustomOverworld v3 constants verified
**No Changes Required**: Documentation is accurate as written
---
### 7. zscustomoverworld_integration.md
**Status**: SIGNIFICANTLY ENHANCED
**Original Issues**:
- Version detection section marked as "need to verify"
- Missing ROM storage locations table
- No implementation code examples
- ConfigureMultiAreaMap method not fully explained
- Missing details on feature enables
**Improvements Made**:
✓ Added complete ROM storage locations table with:
- Feature names and constants
- ROM addresses (0x140000 range)
- Data sizes (1-8 bytes per map)
- Usage notes for each feature
✓ Clarified version detection using overworld_version_helper.h
✓ Added asm_version check details
✓ Provided code example for custom properties access
✓ Emphasized never setting area_size directly
✓ Explained ConfigureMultiAreaMap 5-step process
✓ Added table of feature enable flag addresses
**Code Examples Added**:
```cpp
// Proper multi-area configuration
absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size);
// Custom properties access pattern
if (rom->asm_version >= 1) {
map.SetupCustomTileset(rom->asm_version);
uint16_t custom_bg_color = map.area_specific_bg_color_;
}
```
**Code Accuracy Verified**:
- ConfigureMultiAreaMap method signature confirmed (overworld.h line 204)
- SetupCustomTileset method confirmed (overworld_map.h line 257)
- All ROM address constants verified against source (overworld_map.h lines 21-73)
---
### 8. TEST_INFRASTRUCTURE_IMPROVEMENTS.md
**Status**: NOTED BUT NOT REVIEWED
**Reasoning**: This file focuses on test infrastructure and was not part of the core architecture review scope. It exists as a separate documentation artifact.
---
## New Documentation Created
### docs/internal/architecture/README.md
**Status**: CREATED
**Purpose**: Comprehensive navigation hub for all architecture documentation
**Contents**:
- Overview of architecture documentation purpose
- Quick reference guide organized by component
- Design patterns used in the project (5 patterns documented)
- Contributing guidelines
- Architecture evolution notes
- Status and maintenance information
**Key Sections**:
1. Core Architecture Guides (5 major systems)
2. Quick Reference by Component (organized by source directory)
3. Design Patterns Used (Modular/Component-Based, Callbacks, Singleton, Progressive Loading, Snapshot-Based Undo/Redo)
4. Contributing Guidelines (7 key principles)
5. Related Documents (links to CLAUDE.md, README.md)
6. Architecture Evolution (historical context)
**File Size**: 400+ lines, comprehensive navigation and guidance
---
## CLAUDE.md Enhancements
**Changes Made**:
✓ Added new "Architecture Documentation" section
✓ Provided links to all 8 architecture documents with brief descriptions
✓ Reorganized "Important File Locations" section
✓ Updated file path references to match actual locations
**New Section**:
```markdown
## Architecture Documentation
Detailed architectural guides are available in `docs/internal/architecture/`:
- Graphics System
- Dungeon Editor System
- Room Data Persistence
- Overworld Editor System
- Overworld Map Data
- Undo/Redo System
- ZSCustomOverworld Integration
- Architecture Index
```
---
## Summary of Inaccuracies Found
**Critical Issues**: None found
**Minor Issues Addressed**:
1. Duplicate graphics system documentation (fixed by consolidation)
2. Incomplete method signatures (enhanced with full details)
3. Missing ROM address constants (added from source)
4. Vague component descriptions (expanded with file paths and roles)
5. Missing implementation examples (added where helpful)
---
## Recommendations for Future Documentation
### Short-Term (For Next Review)
1. **Test Architecture Documentation**: Create document for test structure and patterns
2. **ROM Structure Guide**: Detailed reference of ALttP ROM layout and bank addressing
3. **Asar Integration Details**: More comprehensive guide to assembly patching
4. **CLI Tool Architecture**: Document z3ed CLI and TUI component design
### Medium-Term (Next Quarter)
1. **Performance Optimization Guide**: Document optimization patterns and bottlenecks
2. **Thread Safety Guidelines**: Comprehensive guide to concurrent operations
3. **Graphics Format Reference**: Detailed 2BPP/3BPP/Indexed format guide
4. **ROM Hacking Patterns**: Common patterns and anti-patterns in the codebase
### Long-Term (Strategic)
1. **API Reference Documentation**: Auto-generated API docs from inline comments
2. **Architecture Decision Records (ADRs)**: Document why certain patterns were chosen
3. **Migration Guides**: Documentation for code refactoring and API changes
4. **Video Tutorials**: Visual architecture walkthroughs
---
## Gemini's Documentation Quality Assessment
### Strengths
**Clear Structure**: Documents are well-organized with logical sections
**Good Explanations**: Complex systems explained in accessible language
**Accurate Understanding**: Demonstrates solid grasp of codebase
**Component Relationships**: Clear description of how pieces interact
**Practical Focus**: Includes real examples and workflows
**ROM Knowledge**: Correct handling of SNES-specific details
### Areas for Improvement
**Precision**: Could include more specific file paths and method signatures
**Completeness**: Some sections could benefit from code examples
**Verification**: Some implementation details marked as "need to verify"
**Best Practices**: Could include more contributor guidance
**Cross-References**: Could link between related documents more
### Growth Opportunities
1. **Deepen ROM Knowledge**: Learn more about pointer tables and memory banking
2. **Study Design Patterns**: Research the specific patterns used (coordinator, callback, singleton)
3. **Add Examples**: Include real code snippets from the project
4. **Test Verification**: Verify documentation against actual test cases
5. **Performance Details**: Document performance implications of design choices
---
## Checklist of Deliverables
**Updated/Corrected Versions of All Documents**:
- graphics_system_architecture.md (merged and enhanced)
- dungeon_editor_system.md (enhanced)
- room_data_persistence.md (enhanced)
- overworld_editor_system.md (verified accurate)
- overworld_map_data.md (verified accurate)
- undo_redo_system.md (verified accurate)
- zscustomoverworld_integration.md (enhanced)
- graphics_system.md (deleted - consolidated)
**New Documentation Created**:
- docs/internal/architecture/README.md (navigation hub)
**Integration Updates**:
- CLAUDE.md (added Architecture Documentation section with links)
**Summary Report**:
- This document (comprehensive findings and recommendations)
---
## Files Modified
### Updated Files
1. `/Users/scawful/Code/yaze/docs/internal/architecture/graphics_system_architecture.md`
- Status: Enhanced (178 lines, was 74)
- Changes: Consolidated duplicate, added rendering pipeline, best practices, code examples
2. `/Users/scawful/Code/yaze/docs/internal/architecture/dungeon_editor_system.md`
- Status: Enhanced
- Changes: Added best practices section, contributor guidelines, expanded components, examples
3. `/Users/scawful/Code/yaze/docs/internal/architecture/room_data_persistence.md`
- Status: Enhanced
- Changes: Improved method signatures, ROM address details, thread safety notes
4. `/Users/scawful/Code/yaze/docs/internal/architecture/zscustomoverworld_integration.md`
- Status: Enhanced
- Changes: Added ROM storage table, implementation details, code examples
5. `/Users/scawful/Code/yaze/CLAUDE.md`
- Status: Enhanced
- Changes: Added Architecture Documentation section with links and descriptions
### New Files
1. `/Users/scawful/Code/yaze/docs/internal/architecture/README.md` (400+ lines)
- Comprehensive navigation hub with quick references, design patterns, and guidelines
### Deleted Files
1. `/Users/scawful/Code/yaze/docs/internal/architecture/graphics_system.md`
- Consolidated into graphics_system_architecture.md
---
## Conclusion
Gemini's architecture documentation demonstrates a solid understanding of the YAZE codebase. The documentation is clear, well-organized, and largely accurate. The improvements made focus on:
1. **Consolidating Duplicates**: Merged two graphics system documents into one comprehensive guide
2. **Enhancing Accuracy**: Added specific file paths, method signatures, and ROM addresses
3. **Improving Usability**: Created navigation hub and added cross-references
4. **Adding Guidance**: Included best practices and contributor guidelines
5. **Ensuring Completeness**: Expanded sections that were marked incomplete
The architecture documentation now serves as an excellent resource for developers working on or understanding the YAZE codebase. The navigation hub makes it easy to find relevant information, and the added examples and best practices provide practical guidance for contributors.
**Overall Quality Rating**: **8/10** - Good work with solid understanding, some areas for even greater depth and precision.
---
**Report Prepared By**: Documentation Janitor
**Date**: November 21, 2025
**Architecture Documentation Status**: **Ready for Use**

View File

@@ -0,0 +1,258 @@
# Agent Documentation Audit Report
**Audit Date**: 2025-11-23
**Auditor**: CLAUDE_DOCS (Documentation Janitor)
**Total Files Reviewed**: 30 markdown files
**Total Size**: 9,149 lines, ~175KB
---
## Executive Summary
The `/docs/internal/agents/` directory contains valuable agent collaboration infrastructure but has accumulated task-specific documentation that should be **archived** (5 files), **consolidated** (3 file groups), and one **template** file that should remain. The coordination-board.md is oversized (83KB) and needs archival strategy.
**Key Findings**:
- **3 Gemini-specific task prompts** (gemini-master, gemini3-overworld-fix, gemini-task-checklist) are completed or superseded
- **2 Onboarding documents** (COLLABORATION_KICKOFF, CODEX_ONBOARDING) are one-time setup docs for past kickoffs
- **1 Handoff document** (CLAUDE_AIINF_HANDOFF) documents a completed session handoff
- **Core infrastructure documents** (coordination-board, personas, agent-architecture) should remain
- **System reference documents** (gemini-overworld-system-reference, gemini-dungeon-system-reference) are valuable for context
- **Initiative documents** (initiative-v040, initiative-test-slimdown) are active/in-progress
---
## File-by-File Audit Table
| File | Size | Relevance (1-5) | Status | Recommended Action | Justification |
|------|------|-----------------|--------|-------------------|---------------|
| **ACTIVE CORE** |
| coordination-board.md | 83KB | 5 | ACTIVE | ARCHIVE OLD ENTRIES | Live coordination hub; archive entries >2 weeks old to separate file |
| personas.md | 1.8KB | 5 | ACTIVE | KEEP | Defines CLAUDE_CORE, CLAUDE_AIINF, CLAUDE_DOCS, GEMINI_AUTOM personas |
| agent-architecture.md | 14KB | 5 | ACTIVE | KEEP | Foundational reference for agent roles, capabilities, interaction patterns |
| **ACTIVE INITIATIVES** |
| initiative-v040.md | 8.4KB | 5 | ACTIVE | KEEP | Ongoing v0.4.0 development (SDL3, emulator accuracy); linked from coordination-board |
| initiative-test-slimdown.md | 2.3KB | 4 | IN_PROGRESS | KEEP | Scoped test infrastructure work; referenced in coordination-board |
| initiative-template.md | 1.3KB | 4 | ACTIVE | KEEP | Reusable template for future initiatives |
| **ACTIVE COLLABORATION FRAMEWORK** |
| claude-gemini-collaboration.md | 12KB | 4 | ACTIVE | KEEP | Documents Claude-Gemini teamwork structure; still relevant |
| agent-leaderboard.md | 10KB | 3 | SEMI-ACTIVE | CONSIDER ARCHIVING | Gamification artifact from 2025-11-20; update score tracking to coordination-board |
| **GEMINI TASK-SPECIFIC (COMPLETED/SUPERSEDED)** |
| gemini-build-setup.md | 2.2KB | 3 | COMPLETED | ARCHIVE | Build guide for Gemini; superseded by docs/public/build/quick-reference.md |
| gemini-master-prompt.md | 7.1KB | 2 | COMPLETED | ARCHIVE | Session context doc for Gemini session; work is complete (fixed ASM version checks) |
| gemini3-overworld-fix-prompt.md | 5.4KB | 2 | COMPLETED | ARCHIVE | Specific bug fix prompt for overworld regression; issue resolved (commit aed7967e29) |
| gemini-task-checklist.md | 6.5KB | 2 | COMPLETED | ARCHIVE | Gemini task checklist from 2025-11-20 session; all items completed or handed off |
| gemini-overworld-reference.md | 7.0KB | 3 | REFERENCE | CONSOLIDATE | Duplicate info from gemini-overworld-system-reference.md; merge into system-reference |
| **DUNGEON/OVERWORLD SYSTEM REFERENCES** |
| gemini-overworld-system-reference.md | 11KB | 4 | REFERENCE | KEEP | Technical deep-dive for overworld system; valuable ongoing reference |
| gemini-dungeon-system-reference.md | 14KB | 4 | REFERENCE | KEEP | Technical deep-dive for dungeon system; valuable ongoing reference |
| **HANDOFF DOCUMENTS** |
| CLAUDE_AIINF_HANDOFF.md | 7.3KB | 2 | COMPLETED | ARCHIVE | Session handoff from 2025-11-20; work documented in coordination-board |
| **ONE-TIME SETUP/KICKOFF** |
| COLLABORATION_KICKOFF.md | 5.3KB | 2 | COMPLETED | ARCHIVE | Kickoff for Claude-Gemini collaboration; framework now in place |
| CODEX_ONBOARDING.md | 5.9KB | 2 | COMPLETED | ARCHIVE | Onboarding guide for Codex agent; role is now established |
| **DEVELOPMENT GUIDES** |
| overworld-agent-guide.md | 14KB | 3 | REFERENCE | CONSOLIDATE | Overlaps with gemini-overworld-system-reference.md; merge content |
| ai-agent-debugging-guide.md | 22KB | 4 | ACTIVE | KEEP | Comprehensive debug reference for AI agents working on yaze |
| ai-development-tools.md | 17KB | 4 | ACTIVE | KEEP | Development tools reference for AI agents |
| ai-infrastructure-initiative.md | 11KB | 3 | REFERENCE | CONSIDER ARCHIVING | Infrastructure planning doc; mostly superseded by active initiatives |
| **Z3ED DOCUMENTATION** |
| z3ed-command-abstraction.md | 15KB | 3 | REFERENCE | CONSOLIDATE | CLI refactoring reference; merge with z3ed-refactoring.md |
| z3ed-refactoring.md | 9.7KB | 3 | REFERENCE | CONSOLIDATE | CLI refactoring summary; merge with command-abstraction.md |
| **INFRASTRUCTURE** |
| CI-TEST-AUDIT-REPORT.md | 5.6KB | 2 | COMPLETED | ARCHIVE | Test audit from 2025-11-20; findings incorporated into CI/test docs |
| filesystem-tool.md | 6.0KB | 2 | REFERENCE | ARCHIVE | Tool documentation; likely obsolete or incorporated elsewhere |
| dev-assist-agent.md | 8.4KB | 3 | REFERENCE | REVIEW | Development assistant design doc; check if still relevant |
| ai-modularity.md | 6.6KB | 3 | REFERENCE | REVIEW | Modularity initiative doc; check completion status |
| gh-actions-remote.md | 1.6KB | 1 | REFERENCE | DELETE | GitHub Actions remote reference; likely outdated tool documentation |
---
## Consolidation Recommendations
### Group A: Gemini Overworld References (CONSOLIDATE)
**Files to Merge**:
- `gemini-overworld-reference.md` (7.0KB)
- `gemini-overworld-system-reference.md` (11KB)
- `overworld-agent-guide.md` (14KB)
**Action**: Keep `gemini-overworld-system-reference.md` as the authoritative reference. Archive the other two files.
**Reasoning**: All three files cover similar ground (overworld architecture, file structure, data models). System-reference is most comprehensive and is actively used by agents.
---
### Group B: z3ed Refactoring References (CONSOLIDATE)
**Files to Merge**:
- `z3ed-command-abstraction.md` (15KB)
- `z3ed-refactoring.md` (9.7KB)
**Action**: Keep `z3ed-refactoring.md` as the summary. Move detailed command abstraction specifics to a new `z3ed-implementation-details.md` in `docs/internal/` (not agents/).
**Reasoning**: Refactoring is complete, but CLI architecture docs are valuable for future CLI work. Separate implementation details from agent coordination.
---
### Group C: Gemini Task-Specific Prompts (ARCHIVE)
**Files to Archive**:
- `gemini-master-prompt.md` (7.1KB)
- `gemini3-overworld-fix-prompt.md` (5.4KB)
- `gemini-task-checklist.md` (6.5KB)
- `gemini-build-setup.md` (2.2KB)
**Action**: Move to `docs/internal/agents/archive/gemini-session-2025-11-20/` with a README explaining the session context.
**Reasoning**: These are session-specific task documents. The work they document is complete. They're valuable for understanding past sessions but shouldn't clutter the active agent docs directory.
---
### Group D: Session Handoffs & Kickoffs (ARCHIVE)
**Files to Archive**:
- `CLAUDE_AIINF_HANDOFF.md` (7.3KB)
- `COLLABORATION_KICKOFF.md` (5.3KB)
- `CODEX_ONBOARDING.md` (5.9KB)
**Action**: Move to `docs/internal/agents/archive/session-handoffs/` with dates in filenames.
**Reasoning**: One-time setup documents. The collaboration framework is now established and documented in `claude-gemini-collaboration.md`. Handoff information has been integrated into coordination-board.
---
### Group E: Completed Audits & Reports (ARCHIVE)
**Files to Archive**:
- `CI-TEST-AUDIT-REPORT.md` (5.6KB)
**Action**: Move to `docs/internal/agents/archive/reports/` with date.
**Reasoning**: Audit findings have been incorporated into active test documentation. The report itself is a historical artifact.
---
## Immediate Actions
### Priority 1: Resolve Coordination Board Size (CRITICAL)
**Current**: 83KB file makes it unwieldy
**Action**:
1. Create `coordination-board-archive.md` in same directory
2. Move entries older than 2 weeks to archive (keep last 60-80 entries, ~40KB max)
3. Update coordination-board.md header with note about archival strategy
4. Create a script to automate monthly archival
**Expected Result**: Faster file loads, easier to find current work
---
### Priority 2: Create Archive Structure
```
docs/internal/agents/
├── archive/
│ ├── gemini-session-2025-11-20/
│ │ ├── README.md (context)
│ │ ├── gemini-master-prompt.md
│ │ ├── gemini3-overworld-fix-prompt.md
│ │ ├── gemini-task-checklist.md
│ │ └── gemini-build-setup.md
│ ├── session-handoffs/
│ │ ├── 2025-11-20-CLAUDE_AIINF_HANDOFF.md
│ │ ├── 2025-11-20-COLLABORATION_KICKOFF.md
│ │ └── 2025-11-20-CODEX_ONBOARDING.md
│ └── reports/
│ └── 2025-11-20-CI-TEST-AUDIT-REPORT.md
```
---
### Priority 3: Consolidate Overlapping Documents
1. **Merge overworld guides**: Keep `gemini-overworld-system-reference.md`, archive others
2. **Merge z3ed docs**: Keep `z3ed-refactoring.md`, consolidate implementation details
3. **Review low-relevance files**: Check `dev-assist-agent.md`, `ai-modularity.md`, `ai-infrastructure-initiative.md`
---
## Files to Keep (Active Core)
These files should remain in `/docs/internal/agents/` with no changes:
| File | Reason |
|------|--------|
| `coordination-board.md` | Live coordination hub (after archival cleanup) |
| `personas.md` | Defines agent roles and responsibilities |
| `agent-architecture.md` | Foundational reference for agent systems |
| `initiative-v040.md` | Active development initiative |
| `initiative-test-slimdown.md` | Active development initiative |
| `initiative-template.md` | Reusable template for future work |
| `claude-gemini-collaboration.md` | Active team collaboration framework |
| `gemini-overworld-system-reference.md` | Technical deep-dive (widely used) |
| `gemini-dungeon-system-reference.md` | Technical deep-dive (widely used) |
| `ai-agent-debugging-guide.md` | Debugging reference for agents |
| `ai-development-tools.md` | Development tools reference |
---
## Files Requiring Further Review
These files need owner confirmation before archival:
| File | Status | Recommendation |
|------|--------|-----------------|
| `dev-assist-agent.md` | UNCLEAR | Contact owner to confirm if still active |
| `ai-modularity.md` | UNCLEAR | Check if modularity initiative is complete |
| `ai-infrastructure-initiative.md` | SEMI-ACTIVE | May be superseded by v0.4.0 initiative |
| `agent-leaderboard.md` | SEMI-ACTIVE | Consider moving gamification tracking to coordination-board |
| `gh-actions-remote.md` | LIKELY-OBSOLETE | Very small file; verify it's not actively referenced |
---
## Summary of Recommendations
### Files to Archive (7-8 files, ~45KB)
- Gemini task-specific prompts (4 files)
- Session handoffs and kickoffs (3 files)
- Completed audit reports (1 file)
### Files to Consolidate (3 groups)
- Overworld references: Keep system-reference.md, archive others
- Z3ed references: Merge into single document
- Review low-relevance infrastructure initiatives
### Files to Keep (11 files, ~80KB)
- Core coordination and architecture files
- Active initiatives
- Technical deep-dives used by agents
### Structure Improvement
- Create `archive/` subdirectory with documented substructure
- Establish coordination-board.md archival strategy
- Aim for <100KB total in active agents directory
---
## Implementation Timeline
**Week 1 (Nov 23-29)**:
- [ ] Create archive directory structure
- [ ] Move files to archive (priority: task-specific prompts)
- [ ] Update cross-references in remaining docs
**Week 2 (Nov 30-Dec 6)**:
- [ ] Archive coordination-board entries (manual or scripted)
- [ ] Consolidate overlapping system references
- [ ] Review unclear files with owners
**Ongoing**:
- [ ] Implement monthly coordination-board.md archival
- [ ] Keep active initiatives up-to-date
---
## Notes for Future Archival
When adding new agent documentation:
1. **Session-specific docs** (task prompts, handoffs, prompts) → Archive after completion
2. **One-time setup docs** (kickoffs, onboarding) → Archive after 2 weeks
3. **Active infrastructure** (coordination, personas, initiatives) → Keep in root
4. **System references** (architecture, debugging) → Keep if actively used by agents
5. **Completed work reports** → Archive with date in filename
**Target**: Keep `/docs/internal/agents/` under 100KB with ~15-20 active files. Move everything else to `archive/`.

View File

@@ -48,7 +48,7 @@ Established three-agent team:
- **Codex (CODEX)**: Documentation, coordination, QA, organization
Files created:
- `docs/internal/agents/agent-leaderboard.md` - Competitive tracking
- `docs/internal/agents/archive/legacy-2025-11/agent-leaderboard-archived-2025-11-25.md` - Competitive tracking
- `docs/internal/agents/claude-gemini-collaboration.md` - Collaboration framework
- `docs/internal/agents/CODEX_ONBOARDING.md` - Codex welcome guide
- `docs/internal/agents/coordination-board.md` - Updated with team assignments
@@ -156,7 +156,7 @@ Files created:
### Key Documents
- **Coordination Board**: `docs/internal/agents/coordination-board.md`
- **Leaderboard**: `docs/internal/agents/agent-leaderboard.md`
- **Leaderboard**: `docs/internal/agents/archive/legacy-2025-11/agent-leaderboard-archived-2025-11-25.md`
- **Collaboration Guide**: `docs/internal/agents/claude-gemini-collaboration.md`
- **Testing Docs**: `docs/internal/testing/README.md`

View File

@@ -149,7 +149,7 @@ Welcome aboard! Claude and Gemini have been duking it out fixing critical build
## Getting Started
1. **Read the coordination board**: `docs/internal/agents/coordination-board.md`
2. **Check the leaderboard**: `docs/internal/agents/agent-leaderboard.md`
2. **Check the leaderboard**: `docs/internal/agents/archive/legacy-2025-11/agent-leaderboard-archived-2025-11-25.md`
3. **Pick a task** from the list above (start with Documentation Cleanup)
4. **Post on coordination board** when you start/finish tasks
5. **Join the friendly rivalry** - may the best AI win! 🏆

View File

@@ -12,7 +12,7 @@ Accelerate yaze release by combining Claude's architectural expertise with Gemin
### Documents Created
1. **Agent Leaderboard** (`docs/internal/agents/agent-leaderboard.md`)
1. **Agent Leaderboard** (`docs/internal/agents/archive/legacy-2025-11/agent-leaderboard-archived-2025-11-25.md`)
- Objective scoring system (points based on impact)
- Current scores: Claude 725 pts, Gemini 90 pts
- Friendly trash talk section
@@ -144,7 +144,7 @@ Accelerate yaze release by combining Claude's architectural expertise with Gemin
## Resources
- **Leaderboard**: `docs/internal/agents/agent-leaderboard.md`
- **Leaderboard**: `docs/internal/agents/archive/legacy-2025-11/agent-leaderboard-archived-2025-11-25.md`
- **Framework**: `docs/internal/agents/claude-gemini-collaboration.md`
- **Coordination**: `docs/internal/agents/coordination-board.md`
- **CI Status Script**: `scripts/agents/get-gh-workflow-status.sh`

View File

@@ -0,0 +1,142 @@
# Testing Documentation Archive (November 2025)
This directory contains testing-related documentation that was archived during a comprehensive cleanup of `/docs/internal/testing/` to reduce duplication and improve maintainability.
## Archive Rationale
The testing directory contained 25 markdown files with significant duplication of content from:
- `test/README.md` - The canonical test suite documentation
- `docs/public/build/quick-reference.md` - The canonical build reference
- `docs/internal/ci-and-testing.md` - CI/CD pipeline documentation
## Archived Files (6 total)
### Bloated/Redundant Documentation
1. **testing-strategy.md** (843 lines)
- Duplicates the tiered testing strategy from `test/README.md`
- Reason: Content moved to canonical test/README.md
- Reference: See test/README.md for current strategy
2. **TEST_INFRASTRUCTURE_IMPROVEMENT_PLAN.md** (2257 lines)
- Massive improvement proposal document
- Duplicates much of test/README.md and docs/internal/ci-and-testing.md
- Reason: Content integrated into existing canonical docs
- Reference: Implementation recommendations are in docs/internal/ci-and-testing.md
3. **ci-improvements-proposal.md** (690 lines)
- Detailed CI/CD improvement proposals
- Overlaps significantly with docs/internal/ci-and-testing.md
- Reason: Improvements documented in canonical CI/testing doc
- Reference: See docs/internal/ci-and-testing.md
4. **cmake-validation.md** (672 lines)
- CMake validation guide
- Duplicates content from docs/public/build/quick-reference.md
- Reason: Build validation covered in quick-reference.md
- Reference: See docs/public/build/quick-reference.md
5. **integration-plan.md** (505 lines)
- Testing infrastructure integration planning document
- Much of content duplicated in test/README.md
- Reason: Integration approach implemented and documented elsewhere
- Reference: See test/README.md for current integration approach
6. **matrix-testing-strategy.md** (499 lines)
- Platform/configuration matrix testing strategy
- Some unique content but much is duplicated in other docs
- Reason: Matrix testing implementation is in scripts/
- Reference: Check scripts/test-config-matrix.sh and related scripts
## Deleted Files (14 total - Already in git staging)
These files were completely duplicative and offered no unique value:
1. **QUICKSTART.md** - Exact duplicate of QUICK_START_GUIDE.md
2. **QUICK_START_GUIDE.md** - Duplicates test/README.md Quick Start section
3. **QUICK_REFERENCE.md** - Redundant quick reference for symbol detection
4. **README_TESTING.md** - Duplicate hub documentation
5. **TESTING_INDEX.md** - Navigation index (redundant)
6. **ARCHITECTURE_HANDOFF.md** - AI-generated project status document
7. **INITIATIVE.md** - AI-generated project initiative document
8. **EXECUTIVE_SUMMARY.md** - AI-generated executive summary
9. **IMPLEMENTATION_GUIDE.md** - Symbol detection implementation guide (superseded)
10. **MATRIX_TESTING_README.md** - Matrix testing system documentation
11. **MATRIX_TESTING_IMPLEMENTATION.md** - Matrix testing implementation guide
12. **MATRIX_TESTING_CHECKLIST.md** - Matrix testing checklist
13. **SYMBOL_DETECTION_README.md** - Duplicate of symbol-conflict-detection.md
14. **TEST_INFRASTRUCTURE_IMPROVEMENT_PLAN.md** - (see archived files above)
## Files Retained (5 total in docs/internal/testing/)
1. **dungeon-gui-test-design.md** (1007 lines)
- Unique architectural test design for dungeon editor
- Specific to DungeonEditorV2 testing with ImGuiTestEngine
- Rationale: Contains unique architectural and testing patterns not found elsewhere
2. **pre-push-checklist.md** (335 lines)
- Practical developer checklist for pre-commit validation
- Links to scripts and CI verification
- Rationale: Useful operational checklist referenced by developers
3. **README.md** (414 lines)
- Hub documentation for testing infrastructure
- Links to canonical testing documents and resources
- Rationale: Serves as navigation hub to various testing documents
4. **symbol-conflict-detection.md** (440 lines)
- Complete documentation for symbol conflict detection system
- Details on symbol extraction, detection, and pre-commit hooks
- Rationale: Complete reference for symbol conflict system
5. **sample-symbol-database.json** (1133 bytes)
- Example JSON database for symbol conflict detection
- Supporting documentation for symbol system
- Rationale: Example data for understanding symbol database format
## Canonical Documentation References
When working with testing, refer to these canonical sources:
- **Test Suite Overview**: `test/README.md` (407 lines)
- Tiered testing strategy, test structure, running tests
- How to write new tests, CI configuration
- **Build & Test Quick Reference**: `docs/public/build/quick-reference.md`
- CMake presets, common build commands
- Test execution quick reference
- **CI/CD Pipeline**: `docs/internal/ci-and-testing.md`
- CI workflow configuration, test infrastructure
- GitHub Actions integration
- **CLAUDE.md**: Project root CLAUDE.md
- References canonical test documentation
- Links to quick-reference.md and test/README.md
## How to Restore
If you need to reference archived content:
```bash
# View specific archived document
cat docs/internal/agents/archive/testing-docs-2025/testing-strategy.md
# Restore if needed
mv docs/internal/agents/archive/testing-docs-2025/<filename>.md docs/internal/testing/
```
## Cleanup Results
- **Before**: 25 markdown files (12,170 total lines)
- **After**: 5 markdown files (2,943 total lines)
- **Reduction**: 75.8% fewer files, 75.8% fewer lines
- **Result**: Cleaner documentation structure, easier to maintain, reduced duplication
## Related Cleanup
This cleanup was performed as part of documentation janitor work to:
- Remove AI-generated spam and duplicate documentation
- Enforce single source of truth for each documentation topic
- Keep root documentation directory clean
- Maintain clear, authoritative documentation structure

View File

@@ -709,6 +709,6 @@ LOG_WARNING("Emulator",
## Related Documentation
- **FileSystemTool**: `filesystem-tool.md`
- **AI Infrastructure**: `ai-infrastructure-initiative.md`
- **AI Infrastructure (archived)**: `archive/legacy-2025-11/ai-infrastructure-initiative-archived-2025-11-25.md`
- **Agent Architecture**: `agent-architecture.md`
- **Development Plan**: `../plans/ai-assisted-development-plan.md`

View File

@@ -254,5 +254,5 @@ agent.SetAIEnabled(true);
## Related Documentation
- [Build Tool Documentation](filesystem-tool.md)
- [AI Infrastructure Initiative](ai-infrastructure-initiative.md)
- [Test Suite Configuration](../../test-suite-configuration.md)
- [AI Infrastructure Initiative (archived)](archive/legacy-2025-11/ai-infrastructure-initiative-archived-2025-11-25.md)
- [Test Suite Configuration](../../test-suite-configuration.md)

View File

@@ -0,0 +1,424 @@
# WASM Debug Infrastructure for AI Integration
**Date:** November 25, 2025 (Updated)
**Status:** Current - Active debugging API
**Version:** 2.3.0
**Purpose:** Comprehensive debug API for Gemini/Antigravity AI integration to analyze rendering issues and game state in the yaze web application.
**Note:** This document is the high-level overview for WASM debugging. For detailed API reference, see `wasm-yazeDebug-api-reference.md`. For general WASM status and control APIs, see `wasm_dev_status.md`.
## Overview
The WASM debug infrastructure provides JavaScript access to internal yaze data structures for AI-powered debugging of dungeon palette rendering issues and other visual artifacts.
## Memory Configuration
The WASM build uses optimized memory settings configured in `src/app/app.cmake`:
| Setting | Value | Purpose |
|---------|-------|---------|
| `INITIAL_MEMORY` | 256MB | Reduces heap resizing during ROM load (~200MB needed for overworld) |
| `MAXIMUM_MEMORY` | 1GB | Prevents runaway allocations |
| `STACK_SIZE` | 8MB | Handles recursive operations during asset decompression |
| `ALLOW_MEMORY_GROWTH` | 1 | Enables dynamic heap expansion |
**Emscripten Flags:**
```
-s INITIAL_MEMORY=268435456 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1073741824 -s STACK_SIZE=8388608
```
## ROM Loading Progress
The WASM build reports loading progress through C++ `WasmLoadingManager`. Progress can be monitored in the browser UI or console.
### Loading Stages
| Progress | Stage |
|----------|-------|
| 0% | Reading ROM file... |
| 5% | Loading ROM data... |
| 10% | Initializing editors... |
| 18% | Loading graphics sheets... |
| 26% | Loading overworld... |
| 34% | Loading dungeons... |
| 42% | Loading screen editor... |
| 50%+ | Loading remaining editors... |
| 100% | Complete |
### Monitoring Loading in Console
```javascript
// Check ROM loading status
window.yaze.control.getRomStatus()
// Check graphics loading status
window.yazeDebug.arena.getStatus()
// Get editor state after loading
window.yaze.editor.getSnapshot()
```
### Loading Indicator JavaScript API
```javascript
// Called by C++ via WasmLoadingManager
window.createLoadingIndicator(id, taskName) // Creates loading overlay
window.updateLoadingProgress(id, progress, msg) // Updates progress (0.0-1.0)
window.removeLoadingIndicator(id) // Removes loading overlay
window.isLoadingCancelled(id) // Check if user cancelled
```
## Files Modified/Created
### Core Debug Inspector
- **`src/web/yaze_debug_inspector.cc`** (renamed from `palette_inspector.cpp`)
- Main WASM debug inspector providing JavaScript bindings via Emscripten
- Exports functions for palette, ROM, overworld, arena, and emulator debugging
### JavaScript API
- **`src/web/shell.html`**
- Added `window.yazeDebug` API object for browser console access
- Enhanced `paletteInspector` with better error handling
- Added pixel inspector overlay for visual debugging
### File System Fixes
- **`src/web/app.js`**
- Fixed race condition in `initPersistentFS()`
- Added detection for C++ runtime-initialized IDBFS
- Improved user feedback when file system is initializing
### Build System
- **`src/app/app.cmake`**
- Updated to include `web/yaze_debug_inspector.cc` for WASM builds
## Debug API Reference
### JavaScript API (`window.yazeDebug`)
```javascript
// Check if API is ready
window.yazeDebug.isReady() // Returns: boolean
// Capabilities
window.yazeDebug.capabilities // ['palette', 'arena', 'timeline', 'pixel-inspector', 'rom', 'overworld']
// Palette Debugging
window.yazeDebug.palette.getEvents() // Get palette debug events
window.yazeDebug.palette.getFullState() // Full palette state with metadata
window.yazeDebug.palette.getData() // Raw palette data
window.yazeDebug.palette.getComparisons() // Color comparison data
window.yazeDebug.palette.samplePixel(x,y) // Sample pixel at coordinates
window.yazeDebug.palette.clear() // Clear debug events
// ROM Debugging
window.yazeDebug.rom.getStatus() // ROM load state, size, title
window.yazeDebug.rom.readBytes(address, count) // Read up to 256 bytes from ROM
window.yazeDebug.rom.getPaletteGroup(groupName, idx) // Get palette group by name
// Overworld Debugging
window.yazeDebug.overworld.getMapInfo(mapId) // Map properties (0-159)
window.yazeDebug.overworld.getTileInfo(mapId, x, y) // Tile data at coordinates
// Arena (Graphics) Debugging
window.yazeDebug.arena.getStatus() // Texture queue size, active sheets
window.yazeDebug.arena.getSheetInfo(idx) // Details for specific graphics sheet
// Timeline Analysis
window.yazeDebug.timeline.get() // Ordered event timeline
// AI Analysis Helpers
window.yazeDebug.analysis.getSummary() // Diagnostic summary
window.yazeDebug.analysis.getHypothesis() // AI hypothesis analysis
window.yazeDebug.analysis.getFullState() // Combined full state
// Utility Functions
window.yazeDebug.dumpAll() // Complete state dump (JSON)
window.yazeDebug.formatForAI() // Human-readable format for AI
```
### C++ EMSCRIPTEN_BINDINGS
```cpp
EMSCRIPTEN_BINDINGS(yaze_debug_inspector) {
// Palette debug functions
function("getDungeonPaletteEvents", &getDungeonPaletteEvents);
function("getColorComparisons", &getColorComparisons);
function("samplePixelAt", &samplePixelAt);
function("clearPaletteDebugEvents", &clearPaletteDebugEvents);
function("getFullPaletteState", &getFullPaletteState);
function("getPaletteData", &getPaletteData);
function("getEventTimeline", &getEventTimeline);
function("getDiagnosticSummary", &getDiagnosticSummary);
function("getHypothesisAnalysis", &getHypothesisAnalysis);
// Arena debug functions
function("getArenaStatus", &getArenaStatus);
function("getGfxSheetInfo", &getGfxSheetInfo);
// ROM debug functions
function("getRomStatus", &getRomStatus);
function("readRomBytes", &readRomBytes);
function("getRomPaletteGroup", &getRomPaletteGroup);
// Overworld debug functions
function("getOverworldMapInfo", &getOverworldMapInfo);
function("getOverworldTileInfo", &getOverworldTileInfo);
// Emulator debug functions
function("getEmulatorStatus", &getEmulatorStatus);
function("readEmulatorMemory", &readEmulatorMemory);
function("getEmulatorVideoState", &getEmulatorVideoState);
// Combined state
function("getFullDebugState", &getFullDebugState);
}
```
## File System Issues & Fixes
### Problem: Silent File Opening Failure
**Symptoms:**
- Clicking "Open ROM" did nothing
- No error messages shown to user
- Console showed `FS not ready yet; still initializing` even after `IDBFS synced successfully`
**Root Cause:**
The application had two separate IDBFS initialization paths:
1. **C++ runtime** (`main.cc``MountFilesystems()`) - Initializes IDBFS and logs `IDBFS synced successfully`
2. **JavaScript** (`app.js``initPersistentFS()`) - Tried to mount IDBFS again, failed silently
The JavaScript code never set `fsReady = true` because:
- It waited for IDBFS to be available
- C++ already mounted IDBFS
- The JS `FS.syncfs()` callback never fired (already synced)
- `fsReady` stayed `false`, blocking all file operations
**Fix Applied:**
```javascript
function initPersistentFS() {
// ... existing code ...
// Check if /roms already exists (C++ may have already set up the FS)
var romsExists = false;
try {
FS.stat('/roms');
romsExists = true;
} catch (e) {
// Directory doesn't exist
}
if (romsExists) {
// C++ already mounted IDBFS, just mark as ready
console.log('[WASM] FS already initialized by C++ runtime');
fsReady = true;
resolve();
return;
}
// ... continue with JS mounting if needed ...
}
```
### Problem: No User Feedback During FS Init
**Symptoms:**
- User clicked "Open ROM" during initialization
- Nothing happened, no error shown
- Only console warning logged
**Fix Applied:**
```javascript
function ensureFSReady(showAlert = true) {
if (fsReady && typeof FS !== 'undefined') return true;
if (fsInitPromise) {
console.warn('FS not ready yet; still initializing.');
if (showAlert) {
// Show status message in header
var status = document.getElementById('header-status');
if (status) {
status.textContent = 'File system initializing... please wait';
status.style.color = '#ffaa00';
setTimeout(function() {
status.textContent = 'Ready';
status.style.color = '';
}, 3000);
}
}
return false;
}
// ... rest of function
}
```
### Problem: JSON Parse Errors in Problems Panel
**Symptoms:**
- Console error: `Failed to parse palette events JSON: unexpected character at line 1 column 2`
- Problems panel showed errors when no palette events existed
**Root Cause:**
- `Module.getDungeonPaletteEvents()` returned empty string `""` instead of `"[]"`
- JSON.parse failed on empty string
**Fix Applied:**
```javascript
// Handle empty or invalid responses
if (!eventsStr || eventsStr.length === 0 || eventsStr === '[]') {
var list = document.getElementById('problems-list');
if (list) {
list.innerHTML = '<div class="problems-empty">No palette events yet.</div>';
}
return;
}
```
## Valid Palette Group Names
For `getRomPaletteGroup(groupName, paletteIndex)`:
| Group Name | Description |
|------------|-------------|
| `ow_main` | Overworld main palettes |
| `ow_aux` | Overworld auxiliary palettes |
| `ow_animated` | Overworld animated palettes |
| `hud` | HUD/UI palettes |
| `global_sprites` | Global sprite palettes |
| `armors` | Link armor palettes |
| `swords` | Sword palettes |
| `shields` | Shield palettes |
| `sprites_aux1` | Sprite auxiliary 1 |
| `sprites_aux2` | Sprite auxiliary 2 |
| `sprites_aux3` | Sprite auxiliary 3 |
| `dungeon_main` | Dungeon main palettes |
| `grass` | Grass palettes |
| `3d_object` | 3D object palettes |
| `ow_mini_map` | Overworld minimap palettes |
## Building
```bash
# Build WASM with debug infrastructure
./scripts/build-wasm.sh debug
# Serve locally (sets COOP/COEP headers for SharedArrayBuffer)
./scripts/serve-wasm.sh 8080
```
**Important:** The dev server must set COOP/COEP headers for SharedArrayBuffer support. Use `./scripts/serve-wasm.sh` which handles this automatically.
## Testing the API
Open browser console after loading the application:
```javascript
// Verify API is loaded
window.yazeDebug.isReady()
// Get ROM status (after loading a ROM)
window.yazeDebug.rom.getStatus()
// Read bytes from ROM address 0x10000
window.yazeDebug.rom.readBytes(0x10000, 32)
// Get dungeon palette group
window.yazeDebug.rom.getPaletteGroup('dungeon_main', 0)
// Get overworld map info for Light World map 0
window.yazeDebug.overworld.getMapInfo(0)
// Full debug dump for AI analysis
window.yazeDebug.dumpAll()
```
## Known Limitations
1. **Emulator debug functions** require emulator to be initialized and running
2. **Overworld tile info** requires overworld to be loaded in editor
3. **Palette sampling** works on the visible canvas area only
4. **ROM byte reading** limited to 256 bytes per call to prevent large responses
5. **Memory reading** from emulator limited to 256 bytes per call
6. **Loading indicator** managed by C++ `WasmLoadingManager` - don't create separate JS indicators
## Quick Start for AI Agents
1. **Load a ROM**: Use the file picker or drag-and-drop a `.sfc` file
2. **Wait for loading**: Monitor progress via loading overlay or `window.yaze.control.getRomStatus()`
3. **Verify ready state**: `window.yaze.control.isReady()` should return `true`
4. **Start debugging**: Use `window.yazeDebug.dumpAll()` for full state or specific APIs
```javascript
// Complete verification sequence
if (window.yaze.control.isReady()) {
const status = window.yaze.control.getRomStatus();
if (status.loaded) {
console.log('ROM loaded:', status.filename);
console.log('AI-ready dump:', window.yazeDebug.formatForAI());
}
}
```
## Gemini Antigravity AI Integration
The web interface includes dedicated tools for AI assistants that struggle to discover ImGui elements.
### window.aiTools API
High-level helper functions with console output for AI readability:
```javascript
// Get full application state (ROM, editor, cards, layouts)
window.aiTools.getAppState()
// Get current editor snapshot
window.aiTools.getEditorState()
// Card management
window.aiTools.getVisibleCards()
window.aiTools.getAvailableCards()
window.aiTools.showCard('Room Selector')
window.aiTools.hideCard('Object Editor')
// Navigation
window.aiTools.navigateTo('room:0') // Go to dungeon room
window.aiTools.navigateTo('map:5') // Go to overworld map
window.aiTools.navigateTo('Dungeon') // Switch editor
// Data access
window.aiTools.getRoomData(0) // Dungeon room data
window.aiTools.getMapData(0) // Overworld map data
// Documentation
window.aiTools.dumpAPIReference() // Complete API reference
```
### Nav Bar Dropdowns
The web UI includes four dedicated dropdown menus:
| Dropdown | Purpose |
|----------|---------|
| **Editor** | Quick switch between all 13 editors |
| **Emulator** | Show/Run/Pause/Step/Reset + Memory Viewer |
| **Layouts** | Preset card configurations |
| **AI Tools** | All `window.aiTools` functions via UI |
### Command Palette (Ctrl+K)
All AI tools accessible via palette:
- `Editor: <name>` - Switch editors
- `Emulator: <action>` - Control emulator
- `AI: Get App State` - Application state
- `AI: API Reference` - Full API documentation
## Future Enhancements
- [ ] Add dungeon room state debugging
- [ ] Add sprite debugging
- [ ] Add memory watch points
- [ ] Add breakpoint support for emulator
- [ ] Add texture atlas visualization
- [ ] Add palette history tracking

View File

@@ -0,0 +1,286 @@
# WASM / Web Agent Integration Status
**Last Updated:** November 25, 2025
**Status:** Functional MVP with Agent APIs (ROM loading fixed, loading progress added, control APIs implemented, performance optimizations applied)
## Overview
This document tracks the development state of the `yaze` WASM web application, specifically focusing on the AI Agent integration (`z3ed` console) and the modern UI overhaul.
## 1. Completed Features
### ROM Loading & Initialization (November 2025 Fixes)
* **ROM File Validation (`rom_file_manager.cc`):**
* Fixed minimum ROM size check from 1MB to 512KB (was rejecting valid 1MB Zelda 3 ROMs)
* **CMake WASM Configuration (`app.cmake`):**
* Added `MODULARIZE=1` and `EXPORT_NAME='createYazeModule'` to match `app.js` expectations
* Added missing exports: `_yazeHandleDroppedFile`, `_yazeHandleDropError`, `_yazeHandleDragEnter`, `_yazeHandleDragLeave`, `_malloc`, `_free`
* Added missing runtime methods: `lengthBytesUTF8`, `IDBFS`, `allocateUTF8`
* **JavaScript Fixes (`filesystem_manager.js`):**
* Fixed `Module.ccall` return type from `'null'` (string) to `null`
* Fixed direct function fallback to properly allocate/free memory for string parameters
* **Drop Zone (`drop_zone.js`):**
* Disabled duplicate auto-initialization (conflicted with C++ handler)
* Now delegates to `FilesystemManager.handleRomUpload` instead of calling non-existent function
* **Loading Progress (`editor_manager.cc`):**
* Added `WasmLoadingManager` integration to `LoadAssets()`
* Shows progress for each editor: "Loading overworld...", "Loading dungeons...", etc.
* **UI Streamlining (`shell.html`, `app.js`):**
* Removed HTML welcome screen - canvas is always visible
* Loading overlay shows during initialization with status messages
### AI Agent Integration
* **Core Bridge (`wasm_terminal_bridge.cc`):**
* Exposes `Z3edProcessCommand` to JavaScript.
* Exposes `GetGlobalBrowserAIService()` and `GetGlobalRom()` to C++ handlers.
* **Browser Agent (`browser_agent.cc`):**
* **`agent chat`**: Fully functional with conversation history. Uses `std::thread` for non-blocking AI calls.
* **`agent plan`**: Generates text-based implementation plans (asynchronous).
* **`agent diff`**: Shows the "pending plan" (conceptual diff).
* **`agent list/describe`**: Introspects ROM resources via `ResourceCatalog`.
* **`agent todo`**: Fully implemented with persistent storage.
* **Browser AI Service** (`src/cli/service/ai/browser_ai_service.cc`):
* Implements `AIService` interface for browser-based AI calls
* Uses `IHttpClient` from network abstraction layer (CORS-compatible)
* Supports Gemini API (text and vision models)
* Secures API keys via sessionStorage (cleared on tab close)
* Comprehensive error handling with `absl::Status`
* **Browser Storage** (`src/app/platform/wasm/wasm_browser_storage.cc`):
* Non-hardcoded API key management via sessionStorage/localStorage
* User-provided keys, never embedded in binary
* Namespaced storage to avoid conflicts
* **Persistence (`todo_manager.cc`):**
* Updated to use `WasmStorage` (IndexedDB) when compiled for Emscripten. TODOs persist across reloads.
### UI & UX
* **Drag & Drop (`wasm_drop_handler.cc`):**
* Supports `.sfc`, `.smc`, `.zip`.
* Automatically writes to `/roms/` in MEMFS and loads the ROM.
* Stubbed support for `.pal` / `.tpl`.
* **Modern Interface:**
* **`main.css`**: Unified design system (VS Code dark theme variables).
* **`app.js`**: Extracted logic from `shell.html`. Handles terminal resize, zoom, and PWA updates.
* **Components**: `terminal.css`, `collab_console.css`, etc., updated to use CSS variables.
### WASM Control APIs (November 2025)
The WASM build now exposes comprehensive JavaScript APIs for programmatic control, enabling LLM agents with DOM access to interact with the editor.
#### Editor State APIs (`window.yaze.editor`)
* **`getSnapshot()`**: Get current editor state (type, ROM status, active data)
* **`getCurrentRoom()`**: Get dungeon room info (room_id, active_rooms, visible_cards)
* **`getCurrentMap()`**: Get overworld map info (map_id, world, world_name)
* **`getSelection()`**: Get current selection in active editor
#### Read-only Data APIs (`window.yaze.data`)
* **Dungeon Data:**
* `getRoomTiles(roomId)` - Get room tile data (layer1, layer2)
* `getRoomObjects(roomId)` - Get objects in a room
* `getRoomProperties(roomId)` - Get room properties (music, palette, tileset)
* **Overworld Data:**
* `getMapTiles(mapId)` - Get map tile data
* `getMapEntities(mapId)` - Get entities (entrances, exits, items, sprites)
* `getMapProperties(mapId)` - Get map properties (gfx_group, palette, area_size)
* **Palette Data:**
* `getPalette(group, id)` - Get palette colors
* `getPaletteGroups()` - List available palette groups
#### GUI Automation APIs (`window.yaze.gui`)
* **Element Discovery:**
* `discover()` - List all interactive UI elements with metadata
* `getElementBounds(id)` - Get element position and dimensions (backed by `WidgetIdRegistry`)
* `waitForElement(id, timeout)` - Async wait for element to appear
* **Interaction:**
* `click(target)` - Click by element ID or {x, y} coordinates
* `doubleClick(target)` - Double-click
* `drag(from, to, steps)` - Drag operation
* `pressKey(key, modifiers)` - Send keyboard input
* `type(text, delay)` - Type text string
* `scroll(dx, dy)` - Scroll canvas
* **Utility:**
* `takeScreenshot(format)` - Capture canvas as base64
* `getCanvasInfo()` - Get canvas dimensions
* `isReady()` - Check if GUI API is ready
**Widget Tracking Infrastructure** (November 2025):
The `WidgetIdRegistry` system tracks all ImGui widget bounds in real-time:
- **Real-time Bounds**: `GetUIElementTree()` and `GetUIElementBounds()` query live widget positions via `WidgetIdRegistry`
- **Frame Lifecycle**: Integrated into `Controller::OnLoad()` with `BeginFrame()` and `EndFrame()` hooks
- **Bounds Data**: Includes `min_x`, `min_y`, `max_x`, `max_y` for accurate GUI automation
- **Metadata**: Returns `imgui_id`, `last_seen_frame`, widget type, visibility, enabled state
- **Key Files**: `src/app/gui/automation/widget_id_registry.h`, `src/app/gui/automation/widget_measurement.h`
#### Control APIs (`window.yaze.control`)
* **Editor Control:** `switchEditor()`, `getCurrentEditor()`, `getAvailableEditors()`
* **Card Control:** `openCard()`, `closeCard()`, `toggleCard()`, `getVisibleCards()`
* **Layout Control:** `setCardLayout()`, `getAvailableLayouts()`, `saveCurrentLayout()`
* **Menu Actions:** `triggerMenuAction()`, `getAvailableMenuActions()`
* **Session Control:** `getSessionInfo()`, `createSession()`, `switchSession()`
* **ROM Control:** `getRomStatus()`, `readRomBytes()`, `writeRomBytes()`, `saveRom()`
#### Extended UI Control APIs (November 2025)
**Async Editor Switching (`yazeDebug.switchToEditorAsync`)**:
Promise-based editor switching with operation tracking for reliable LLM automation.
* Returns `Promise<{success, editor, session_id, error}>` after editor transition completes
* Supports all 14 editor types: Assembly, Dungeon, Graphics, Music, Overworld, Palette, Screen, Sprite, Message, Hex, Agent, Settings, World, Map
* 5-second timeout with proper error reporting
**Card Control API (`yazeDebug.cards`)**:
* `show(cardId)` - Show a specific card by ID (e.g., "dungeon.room_selector")
* `hide(cardId)` - Hide a specific card
* `toggle(cardId)` - Toggle card visibility
* `getState()` - Get visibility state of all cards
* `getInCategory(category)` - List cards in a category (dungeon, overworld, etc.)
* `showGroup(groupName)` - Show predefined card groups (dungeon_editing, overworld_editing, etc.)
* `hideGroup(groupName)` - Hide predefined card groups
* `getGroups()` - List available card groups
**Sidebar Control API (`yazeDebug.sidebar`)**:
* `isTreeView()` - Check if tree view mode is active
* `setTreeView(enabled)` - Switch between tree view (200px) and icon mode (48px)
* `toggle()` - Toggle between view modes
* `getState()` - Get sidebar state (mode, width, collapsed)
**Right Panel Control API (`yazeDebug.rightPanel`)**:
* `open(panelName)` - Open specific panel: properties, agent, proposals, settings, help
* `close()` - Close current panel
* `toggle(panelName)` - Toggle panel visibility
* `getState()` - Get panel state (active, expanded, width)
* `openProperties()` - Convenience method for properties panel
* `openAgent()` - Convenience method for agent chat panel
**Tree View Sidebar**:
New hierarchical sidebar mode (200px wide) with:
* Category icons and expandable tree nodes
* Checkboxes for each card with visibility toggles
* Visible count badges per category
* "Show All" / "Hide All" buttons per category
* Toggle button to switch to icon mode
**Selection Properties Panel**:
New right-side panel for editing selected entities:
* Context-aware property display based on selection type
* Supports dungeon rooms, objects, sprites, entrances
* Supports overworld maps, tiles, sprites, entrances, exits, items
* Supports graphics sheets and palettes
* Position/size editors with clamping
* Byte/word property editors with hex display
* Flag property editors with checkboxes
* Advanced and raw data toggles
**Key Files:**
* `src/app/platform/wasm/wasm_control_api.cc` - C++ implementation
* `src/app/platform/wasm/wasm_control_api.h` - API declarations
* `src/web/core/agent_automation.js` - GUI automation layer
* `src/web/debug/yaze_debug_inspector.cc` - Extended WASM bindings
* `src/app/editor/system/editor_card_registry.cc` - Tree view sidebar implementation
* `src/app/editor/ui/right_panel_manager.cc` - Right panel management
* `src/app/editor/ui/selection_properties_panel.cc` - Properties panel implementation
### Performance Optimizations & Bug Fixes (November 2025)
A comprehensive audit and fix of the WASM web layer was performed to address performance issues, memory leaks, and race conditions.
#### JavaScript Performance Fixes (`app.js`)
* **Event Sanitization Optimization:**
* Removed redundant document-level event listeners (canvas-only now)
* Added WeakMap caching to avoid re-sanitizing the same event objects
* Optimized to check only relevant properties per event type category
* ~50% reduction in sanitization overhead
* **Console Log Buffer:**
* Replaced O(n) `Array.shift()` with O(1) circular buffer implementation
* Uses modulo arithmetic for constant-time log rotation
* **Polling Cleanup:**
* Added timeout tracking and max retry limits for module initialization
* Proper interval cleanup when components are destroyed
* Added `window.YAZE_MODULE_READY` flag for reliable initialization detection
#### Memory Leak Fixes
* **Service Worker Cache (`service-worker.js`):**
* Added `MAX_RUNTIME_CACHE_SIZE` (50 entries) with LRU eviction
* New `trimRuntimeCache()` function enforces size limits
* `addToRuntimeCacheWithEviction()` wrapper for cache operations
* **Confirmation Callbacks (`wasm_error_handler.cc`):**
* Added `CallbackEntry` struct with timestamps for timeout tracking
* Auto-cleanup of callbacks older than 5 minutes
* Page unload handler via `js_register_cleanup_handler()`
* **Loading Indicators (`loading_indicator.js`):**
* Added try-catch error handling to ensure cleanup on errors
* Stale indicator cleanup (5-minute timeout)
* Periodic cleanup interval with proper lifecycle management
#### Race Condition Fixes
* **Module Initialization (`app.js`):**
* Added `window.YAZE_MODULE_READY` flag set AFTER promise resolves
* Updated `waitForModule()` to check both Module existence AND ready flag
* Prevents code from seeing incomplete Module state
* **FS Ready State (`filesystem_manager.js`):**
* Restructured `initPersistentFS()` with synchronous lock pattern
* Promise created immediately before async operations
* Eliminates race where two calls could create duplicate promises
* **Redundant FS Exposure:**
* Added `fsExposed` flag to prevent wasteful redundant calls
* Reduced from 3 setTimeout calls to 1 conditional retry
#### C++ WASM Fixes
* **Memory Safety (`wasm_storage.cc`):**
* Added `free(data_ptr)` in error paths of `LoadRom()` to prevent memory leaks
* Ensures allocated memory is freed even when operations fail
* **Cleanup Handlers (`wasm_error_handler.cc`):**
* Added `cleanupConfirmCallbacks()` function for page unload
* Registered via `js_register_cleanup_handler()` in `Initialize()`
#### Drop Zone Optimization (`drop_zone.js`, `filesystem_manager.js`)
* **Eliminated Double File Reading:**
* Added new `FilesystemManager.handleRomData(filename, data)` method
* Accepts pre-read `Uint8Array` instead of `File` object
* Drop zone now passes already-read data instead of re-reading
* Reduces CPU and memory usage for ROM uploads
**Key Files Modified:**
* `src/web/app.js` - Event sanitization, console buffer, module init
* `src/web/core/filesystem_manager.js` - FS init race fix, handleRomData
* `src/web/core/loading_indicator.js` - Stale cleanup, error handling
* `src/web/components/drop_zone.js` - Use handleRomData
* `src/web/pwa/service-worker.js` - Cache eviction
* `src/app/platform/wasm/wasm_storage.cc` - Memory free on error
* `src/app/platform/wasm/wasm_error_handler.cc` - Callback cleanup
## 2. Technical Debt & Known Issues
* **`SimpleChatSession`**: This C++ class relies on `VimMode` and raw TTY input, which is incompatible with WASM. We bypassed this by implementing a custom `HandleChatCommand` in `browser_agent.cc`. The original `SimpleChatSession` remains unused in the browser build.
* **Emscripten Fetch Blocking**: The `EmscriptenHttpClient` implementation contains a `cv.wait()` which blocks the main thread. We worked around this by spawning `std::thread` in the command handlers, but the HTTP client itself remains synchronous-blocking if called directly on the main thread.
* **Single-Threaded Rendering**: Dungeon graphics loading happens on the main thread (`DungeonEditorV2::DrawRoomTab`), causing UI freezes on large ROMs.
## 3. Next Steps / Roadmap
### Short Term
1. **Palette Import**: Implement the logic in `wasm_drop_handler.cc` (or `main.cc` callback) to parse `.pal` files and apply them to `PaletteManager`.
2. **Deep Linking**: Add logic to `app.js` and `main.cc` to parse URL query parameters (e.g., `?rom=url`) for easy sharing.
### Medium Term
1. **In-Memory Proposal Registry**:
* Implement a `WasmProposalRegistry` that mimics the file-based `ProposalRegistry`.
* Store "sandboxes" as `Rom` copies in memory (or IndexedDB blobs).
* Enable `agent apply` to execute the plans generated by `agent plan`.
2. **Multithreaded Graphics**:
* Refactor `DungeonEditorV2` to use `WasmWorkerPool` for `LoadRoomGraphics`.
* Requires decoupling `Room` data structures from the loading logic to pass data across threads safely.
## 4. Key Files
* **C++ Logic**:
* `src/cli/handlers/agent/browser_agent.cc` (Agent commands)
* `src/cli/wasm_terminal_bridge.cc` (JS <-> C++ Bridge)
* `src/app/platform/wasm/wasm_drop_handler.cc` (File drag & drop)
* `src/app/platform/wasm/wasm_control_api.cc` (Control API implementation)
* `src/app/platform/wasm/wasm_control_api.h` (Control API declarations)
* `src/cli/service/agent/todo_manager.cc` (Persistence logic)
* **Web Frontend**:
* `src/web/shell.html` (Entry point)
* `src/web/app.js` (Main UI logic)
* `src/web/core/agent_automation.js` (GUI Automation layer)
* `src/web/styles/main.css` (Theme definitions)
* `src/web/components/terminal.js` (Console UI component)
* `src/web/components/collaboration_ui.js` (Collaboration UI)

View File

@@ -0,0 +1,687 @@
### WASM Debugging Guide: Dungeon Editor
**Status:** Current (November 2025)
**Last Updated:** 2025-11-25
**Version:** 2.3.0
The WASM build includes a powerful, hidden "Debug Inspector" that bypasses the need for GDB/LLDB by exposing C++ state directly to the browser console.
**Cross-Reference:** For comprehensive debug API reference, see `wasm-debug-infrastructure.md` and `wasm-yazeDebug-api-reference.md`.
#### 1. The "God Mode" Console Inspector
The file `src/web/yaze_debug_inspector.cc` binds C++ functions to the global `Module` object. You can invoke these directly from Chrome/Firefox DevTools.
**Status & State:**
* `Module.getEmulatorStatus()`: Returns JSON with CPU registers (A, X, Y, PC), Flags, and PPU state.
* `Module.getFullDebugState()`: Returns a massive JSON dump suitable for pasting into an AI prompt for analysis.
* `Module.getArenaStatus()`: Checks the memory arena used for dungeon rendering (vital for "out of memory" rendering glitches).
**Memory Inspection:**
* `Module.readEmulatorMemory(addr, length)`: Reads WRAM/SRAM.
* *Example:* Check Link's X-Coordinate ($20): `Module.readEmulatorMemory(0x7E0020, 2)`
* `Module.readRom(addr, length)`: Verifies if your ROM patch actually applied in memory.
**Graphics & Palette:**
* `Module.getDungeonPaletteEvents()`: Returns a log of recent palette uploads. Use this if colors look wrong or "flashy" in the editor.
#### 2. The Command Line Bridge
The terminal you see in the web app isn't just a UI toy; it's a direct bridge to the C++ backend.
* **Architecture**: `src/web/terminal.js` captures your keystrokes and calls `Module.ccall('Z3edProcessCommand', ...)` which routes to `src/cli/wasm_terminal_bridge.cc`.
* **Debug Tip**: If the editor UI freezes, the terminal often remains responsive (running on a separate event cadence). You can use it to:
1. Save your work: `save`
2. Dump state: (If a custom command exists)
3. Reset the emulator.
#### 3. The Hidden "Debug Controls" Card
The code in `src/app/editor/dungeon/dungeon_editor_v2.cc` contains a function `DrawDebugControlsCard()`, controlled by the boolean `show_debug_controls_`.
* **Current Status**: This is currently **hidden** by default and likely has no UI toggle in the public build.
* **Recommended Task**: Create a `z3ed` CLI command to toggle this boolean.
* *Implementation*: Add a command `editor debug toggle` in `wasm_terminal_bridge.cc` that finds the active `DungeonEditorV2` instance and flips `show_debug_controls_ = !show_debug_controls_`. This would give you on-screen access to render passes and layer toggles.
#### 4. Feature Parity
There are **NO** `__EMSCRIPTEN__` checks inside the `src/app/editor/dungeon/` logic.
* **Implication**: If a logic bug exists in WASM, it likely exists in the Native macOS/Linux build too. Reproduce bugs on Desktop first for easier debugging (breakpoints, etc.), then verify the fix on Web.
* **Exception**: Rendering glitches are likely WASM-specific due to the single-threaded `TickFrame` loop vs. the multi-threaded desktop renderer.
#### 5. Thread Pool Configuration
The WASM build uses a fixed thread pool (`PTHREAD_POOL_SIZE=8` in CMakePresets.json).
* **Warning Signs**: If you see "Tried to spawn a new thread, but the thread pool is exhausted", heavy parallel operations are exceeding the pool size.
* **Fix**: Increase `PTHREAD_POOL_SIZE` in CMakePresets.json and rebuild with `--clean`
* **Root Cause**: Often happens during ROM loading when multiple graphics sheets are decompressed in parallel.
#### 6. Memory Configuration
The WASM build uses optimized memory settings to reduce heap resize operations:
| Setting | Value | Purpose |
|---------|-------|---------|
| `INITIAL_MEMORY` | 256MB | Reduces heap resizing during ROM load |
| `MAXIMUM_MEMORY` | 1GB | Prevents runaway allocations |
| `STACK_SIZE` | 8MB | Handles recursive asset decompression |
* **Warning Signs**: Console shows `_emscripten_resize_heap` calls during loading
* **Common Causes**: Overworld map loading (~160MB for 160 maps), sprite preview buffers, dungeon object emulator
* **Optimization Applied**: Lazy initialization for SNES emulator instances and sprite preview buffers
#### 7. ROM Loading Progress
The C++ `WasmLoadingManager` controls loading progress display. Monitor loading status:
```javascript
// Check if ROM is loaded
window.yaze.control.getRomStatus()
// Returns: { loaded: true/false, filename: "...", title: "...", size: ... }
// Check arena (graphics) status
window.yazeDebug.arena.getStatus()
// Full editor state after loading
window.yaze.editor.getSnapshot()
```
**Loading Progress Stages:**
| Progress | Stage |
|----------|-------|
| 10% | Initializing editors... |
| 18% | Loading graphics sheets... |
| 26% | Loading overworld... |
| 34% | Loading dungeons... |
| 42%+ | Loading remaining editors... |
| 100% | Complete |
#### 8. Prompting AI Agents (Claude Code / Gemini Antigravity)
This section provides precise prompts and workflows for AI agents to interact with the YAZE WASM app.
##### Step 1: Verify the App is Ready
Before any operations, the AI must confirm the WASM module is initialized:
```javascript
// Check module ready state
window.YAZE_MODULE_READY // Should be true
// Check if APIs are available
typeof window.yaze !== 'undefined' &&
typeof window.yaze.control !== 'undefined' // Should be true
```
**If not ready**, wait and retry:
```javascript
// Poll until ready (max 10 seconds)
async function waitForYaze() {
for (let i = 0; i < 100; i++) {
if (window.YAZE_MODULE_READY && window.yaze?.control) return true;
await new Promise(r => setTimeout(r, 100));
}
return false;
}
await waitForYaze();
```
##### Step 2: Load a ROM File
**Option A: User Drag-and-Drop (Recommended)**
Prompt the user: *"Please drag and drop your Zelda 3 ROM (.sfc or .smc) onto the canvas."*
Then verify:
```javascript
// Check if ROM loaded successfully
const status = window.yaze.control.getRomStatus();
console.log(status);
// Expected: { loaded: true, filename: "zelda3.sfc", title: "...", size: 1048576 }
```
**Option B: Check if ROM Already Loaded**
```javascript
window.yaze.control.getRomStatus().loaded // true if ROM is present
```
**Option C: Load from IndexedDB (if previously saved)**
```javascript
// List saved ROMs
FilesystemManager.listSavedRoms && FilesystemManager.listSavedRoms();
```
##### Step 3: Open the Dungeon Editor
```javascript
// Switch to dungeon editor
window.yaze.control.switchEditor('Dungeon');
// Verify switch was successful
const snapshot = window.yaze.editor.getSnapshot();
console.log(snapshot.editor_type); // Should be "Dungeon"
```
##### Step 4: Navigate to a Specific Room
```javascript
// Get current room info
window.yaze.editor.getCurrentRoom();
// Returns: { room_id: 0, active_rooms: [...], visible_cards: [...] }
// Navigate to room 42 (Hyrule Castle Entrance)
window.aiTools.navigateTo('room:42');
// Or use control API
window.yaze.control.openCard('Room 42');
```
##### Step 5: Inspect Room Data
```javascript
// Get room properties
const props = window.yaze.data.getRoomProperties(42);
console.log(props);
// Returns: { music: 5, palette: 2, tileset: 7, ... }
// Get room objects (chests, torches, blocks, etc.)
const objects = window.yaze.data.getRoomObjects(42);
console.log(objects);
// Get room tile data
const tiles = window.yaze.data.getRoomTiles(42);
console.log(tiles.layer1, tiles.layer2);
```
##### Example AI Prompt Workflow
**User asks:** "Show me the objects in room 42 of the dungeon editor"
**AI should execute:**
```javascript
// 1. Verify ready
if (!window.YAZE_MODULE_READY) throw new Error("WASM not ready");
// 2. Check ROM
const rom = window.yaze.control.getRomStatus();
if (!rom.loaded) throw new Error("No ROM loaded - please drag a ROM file onto the canvas");
// 3. Switch to dungeon editor
window.yaze.control.switchEditor('Dungeon');
// 4. Navigate to room
window.aiTools.navigateTo('room:42');
// 5. Get and display data
const objects = window.yaze.data.getRoomObjects(42);
console.log("Room 42 Objects:", JSON.stringify(objects, null, 2));
```
#### 9. JavaScript Tips for Browser Debugging
##### Console Shortcuts
```javascript
// Alias for quick access
const y = window.yaze;
const yd = window.yazeDebug;
// Quick status check
y.control.getRomStatus()
y.editor.getSnapshot()
yd.arena.getStatus()
```
##### Error Handling Pattern
Always wrap API calls in try-catch when automating:
```javascript
function safeCall(fn, fallback = null) {
try {
return fn();
} catch (e) {
console.error('[YAZE API Error]', e.message);
return fallback;
}
}
// Usage
const status = safeCall(() => window.yaze.control.getRomStatus(), { loaded: false });
```
##### Async Operations
Some operations are async. Use proper await patterns:
```javascript
// Wait for element to appear in GUI
await window.yaze.gui.waitForElement('dungeon-room-canvas', 5000);
// Then interact
window.yaze.gui.click('dungeon-room-canvas');
```
##### Debugging State Issues
```javascript
// Full state dump for debugging
const debugState = {
moduleReady: window.YAZE_MODULE_READY,
romStatus: window.yaze?.control?.getRomStatus?.() || 'API unavailable',
editorSnapshot: window.yaze?.editor?.getSnapshot?.() || 'API unavailable',
arenaStatus: window.yazeDebug?.arena?.getStatus?.() || 'API unavailable',
consoleErrors: window._yazeConsoleLogs?.filter(l => l.includes('[ERROR]')) || []
};
console.log(JSON.stringify(debugState, null, 2));
```
##### Monitoring Loading Progress
```javascript
// Set up a loading progress monitor
let lastProgress = 0;
const progressInterval = setInterval(() => {
const status = window.yaze?.control?.getRomStatus?.();
if (status?.loaded) {
console.log('ROM loaded successfully!');
clearInterval(progressInterval);
}
}, 500);
// Clear after 30 seconds timeout
setTimeout(() => clearInterval(progressInterval), 30000);
```
##### Inspecting Graphics Issues
```javascript
// Check if graphics sheets are loaded
const arenaStatus = window.yazeDebug.arena.getStatus();
console.log('Loaded sheets:', arenaStatus.loaded_sheets);
console.log('Pending textures:', arenaStatus.pending_textures);
// Check for palette issues
const paletteEvents = Module.getDungeonPaletteEvents?.() || 'Not available';
console.log('Recent palette changes:', paletteEvents);
```
##### Memory Usage Check
```javascript
// Check WASM memory usage
const memInfo = {
heapSize: Module.HEAPU8?.length || 0,
heapSizeMB: ((Module.HEAPU8?.length || 0) / 1024 / 1024).toFixed(2) + ' MB'
};
console.log('Memory:', memInfo);
```
#### 10. Gemini Antigravity AI Tools
For AI assistants using the Antigravity browser extension, use the high-level `window.aiTools` API:
##### Complete Workflow Example
```javascript
// Step-by-step for Gemini/Antigravity
async function inspectDungeonRoom(roomId) {
// 1. Verify environment
if (!window.YAZE_MODULE_READY) {
return { error: "WASM module not ready. Please wait for initialization." };
}
// 2. Check ROM
const romStatus = window.yaze.control.getRomStatus();
if (!romStatus.loaded) {
return { error: "No ROM loaded. Please drag a Zelda 3 ROM onto the canvas." };
}
// 3. Switch to dungeon editor
window.yaze.control.switchEditor('Dungeon');
// 4. Navigate to room
window.aiTools.navigateTo(`room:${roomId}`);
// 5. Gather all data
return {
room_id: roomId,
properties: window.yaze.data.getRoomProperties(roomId),
objects: window.yaze.data.getRoomObjects(roomId),
tiles: window.yaze.data.getRoomTiles(roomId),
editor_state: window.yaze.editor.getCurrentRoom()
};
}
// Usage
const data = await inspectDungeonRoom(42);
console.log(JSON.stringify(data, null, 2));
```
##### Quick Reference Commands
```javascript
// Get full application state with console output
window.aiTools.getAppState()
// Get dungeon room data
window.aiTools.getRoomData(0)
// Navigate directly to a room
window.aiTools.navigateTo('room:42')
// Show/hide editor cards
window.aiTools.showCard('Room Selector')
window.aiTools.hideCard('Object Editor')
// Get complete API reference
window.aiTools.dumpAPIReference()
// AI-formatted state (paste-ready for prompts)
window.yazeDebug.formatForAI()
```
##### Handling Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| `window.yaze is undefined` | Module not initialized | Wait for `YAZE_MODULE_READY` |
| `getRomStatus().loaded = false` | No ROM file | Prompt user to drag ROM |
| `switchEditor returns error` | Invalid editor name | Use: Dungeon, Overworld, Graphics, Palette, Sprite, Music |
| `getRoomObjects returns empty` | Room not loaded | Navigate to room first |
| `Canvas shows black` | Graphics not loaded | Check `yazeDebug.arena.getStatus()` |
**Nav Bar Access:**
The web UI includes dedicated dropdown menus:
- **Editor** - Quick switch to any editor
- **Emulator** - Run/Pause/Step/Reset controls
- **Layouts** - Preset card configurations
- **AI Tools** - All `window.aiTools` functions via UI clicks
**Command Palette (Ctrl+K):**
Search for "AI:" to access all AI helper commands.
#### 11. Copying Data for External Analysis
```javascript
// Copy room data to clipboard for pasting elsewhere
async function copyRoomDataToClipboard(roomId) {
const data = {
timestamp: new Date().toISOString(),
room_id: roomId,
properties: window.yaze.data.getRoomProperties(roomId),
objects: window.yaze.data.getRoomObjects(roomId)
};
await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
console.log(`Room ${roomId} data copied to clipboard!`);
}
// Usage
copyRoomDataToClipboard(42);
```
---
#### 12. Antigravity: Debugging Dungeon Object Rendering Issues
This section provides Gemini Antigravity with specific workflows for identifying and analyzing dungeon object rendering problems, particularly discrepancies between the **Room Graphics Card** (background tiles) and the **Object Editor Card** (dungeon objects like pots, torches, chests).
##### Understanding the Rendering Pipeline
Dungeon rooms have two distinct rendering layers:
| Card | What It Shows | Render Source |
|------|--------------|---------------|
| **Room Graphics** | Background tiles (floors, walls, pits) | Layer1/Layer2 tile data |
| **Object Editor** | Interactive objects (pots, chests, blocks, torches) | Object list with sprite-based rendering |
**Common Issue:** Objects appear at wrong positions, wrong sprites, or don't match their expected appearance in the Object Editor compared to how they should look based on the room data.
##### Step-by-Step Debugging Workflow
###### 1. Capture the Current Visual State
Use the browser's screenshot capability to capture what you see:
```javascript
// Capture the entire canvas as a data URL
async function captureCanvasScreenshot() {
const canvas = document.getElementById('canvas');
if (!canvas) return { error: 'Canvas not found' };
const dataUrl = canvas.toDataURL('image/png');
console.log('[Screenshot] Canvas captured, length:', dataUrl.length);
// For AI analysis, you can copy to clipboard
await navigator.clipboard.writeText(dataUrl);
return { success: true, message: 'Screenshot copied to clipboard as data URL' };
}
// Usage
await captureCanvasScreenshot();
```
**For Antigravity:** Take screenshots when:
1. Room Graphics Card is visible (shows background)
2. Object Editor Card is visible (shows objects overlaid)
3. Both cards side-by-side if possible
###### 2. Extract Room Object Data Efficiently
```javascript
// Get complete object rendering data for a room
function getDungeonObjectDebugData(roomId) {
const data = {
room_id: roomId,
timestamp: Date.now(),
// Room properties affecting rendering
properties: window.yaze.data.getRoomProperties(roomId),
// All objects in the room with positions
objects: window.yaze.data.getRoomObjects(roomId),
// Current editor state
editor_state: window.yaze.editor.getCurrentRoom(),
// Graphics arena status (textures loaded?)
arena_status: window.yazeDebug?.arena?.getStatus() || 'unavailable',
// Visible cards (what's being rendered)
visible_cards: window.yaze.editor.getSnapshot()?.visible_cards || []
};
return data;
}
// Pretty print for analysis
const debugData = getDungeonObjectDebugData(42);
console.log(JSON.stringify(debugData, null, 2));
```
###### 3. Compare Object Positions vs Tile Positions
```javascript
// Check if objects are aligned with the tile grid
function analyzeObjectPlacement(roomId) {
const objects = window.yaze.data.getRoomObjects(roomId);
const tiles = window.yaze.data.getRoomTiles(roomId);
const analysis = objects.map(obj => {
// Objects use pixel coordinates, tiles are 8x8 or 16x16
const tileX = Math.floor(obj.x / 8);
const tileY = Math.floor(obj.y / 8);
return {
object_id: obj.id,
type: obj.type,
pixel_pos: { x: obj.x, y: obj.y },
tile_pos: { x: tileX, y: tileY },
// Check if position is on grid boundary
aligned_8px: (obj.x % 8 === 0) && (obj.y % 8 === 0),
aligned_16px: (obj.x % 16 === 0) && (obj.y % 16 === 0)
};
});
return analysis;
}
console.log(JSON.stringify(analyzeObjectPlacement(42), null, 2));
```
###### 4. Identify Visual Discrepancies
**Symptoms to look for:**
| Symptom | Likely Cause | Debug Command |
|---------|-------------|---------------|
| Objects invisible | Texture not loaded | `window.yazeDebug.arena.getStatus()` |
| Wrong sprite shown | Object type mismatch | `window.yaze.data.getRoomObjects(roomId)` |
| Position offset | Coordinate transform bug | Compare pixel_pos in data vs visual |
| Colors wrong | Palette not applied | `Module.getDungeonPaletteEvents()` |
| Flickering | Z-order/layer issue | Check `layer` property in object data |
###### 5. DOM Inspection for Card State
Efficiently query the DOM to understand what's being rendered:
```javascript
// Get all visible ImGui windows (cards)
function getVisibleCards() {
// ImGui renders to canvas, but card state is tracked in JS
const snapshot = window.yaze.editor.getSnapshot();
return {
active_cards: snapshot.visible_cards || [],
editor_type: snapshot.editor_type,
// Check if specific cards are open
has_room_selector: snapshot.visible_cards?.includes('Room Selector'),
has_object_editor: snapshot.visible_cards?.includes('Object Editor'),
has_room_canvas: snapshot.visible_cards?.includes('Room Canvas')
};
}
console.log(getVisibleCards());
```
###### 6. Full Diagnostic Dump for AI Analysis
```javascript
// Complete diagnostic for Antigravity to analyze rendering issues
async function generateRenderingDiagnostic(roomId) {
const diagnostic = {
timestamp: new Date().toISOString(),
room_id: roomId,
// Visual state
visible_cards: getVisibleCards(),
// Data state
room_properties: window.yaze.data.getRoomProperties(roomId),
room_objects: window.yaze.data.getRoomObjects(roomId),
object_analysis: analyzeObjectPlacement(roomId),
// Graphics state
arena: window.yazeDebug?.arena?.getStatus(),
palette_events: (() => {
try { return Module.getDungeonPaletteEvents(); }
catch { return 'unavailable'; }
})(),
// Memory state
heap_mb: ((Module.HEAPU8?.length || 0) / 1024 / 1024).toFixed(2),
// Console errors (last 10)
recent_errors: window._yazeConsoleLogs?.slice(-10) || []
};
// Copy to clipboard for easy pasting
const json = JSON.stringify(diagnostic, null, 2);
await navigator.clipboard.writeText(json);
console.log('[Diagnostic] Copied to clipboard');
return diagnostic;
}
// Usage: Run this, then paste into your AI prompt
await generateRenderingDiagnostic(42);
```
##### Common Object Rendering Bugs
###### Bug: Objects Render at (0,0)
**Diagnosis:**
```javascript
// Check for objects with zero coordinates
const objects = window.yaze.data.getRoomObjects(roomId);
const atOrigin = objects.filter(o => o.x === 0 && o.y === 0);
console.log('Objects at origin:', atOrigin);
```
**Cause:** Object position data not loaded or coordinate transformation failed.
###### Bug: Sprite Shows as Black Square
**Diagnosis:**
```javascript
// Check if graphics sheet is loaded for object type
const arena = window.yazeDebug.arena.getStatus();
console.log('Loaded sheets:', arena.loaded_sheets);
console.log('Pending textures:', arena.pending_textures);
```
**Cause:** Texture not yet loaded from deferred queue. Force process:
```javascript
// Wait for textures to load
await new Promise(r => setTimeout(r, 500));
```
###### Bug: Object in Wrong Location vs Room Graphics
**Diagnosis:**
```javascript
// Compare layer1 tile at object position
const obj = window.yaze.data.getRoomObjects(roomId)[0];
const tiles = window.yaze.data.getRoomTiles(roomId);
const tileAtPos = tiles.layer1[Math.floor(obj.y / 8) * 64 + Math.floor(obj.x / 8)];
console.log('Object at:', obj.x, obj.y);
console.log('Tile at that position:', tileAtPos);
```
##### Screenshot Comparison Workflow
For visual debugging, use this workflow:
1. **Open Room Graphics Card only:**
```javascript
window.aiTools.hideCard('Object Editor');
window.aiTools.showCard('Room Canvas');
// Take screenshot #1
```
2. **Enable Object Editor overlay:**
```javascript
window.aiTools.showCard('Object Editor');
// Take screenshot #2
```
3. **Compare:** Objects should align with the room's floor/wall tiles. Misalignment indicates a coordinate bug.
##### Reporting Issues
When reporting dungeon rendering bugs, include:
```javascript
// Generate a complete bug report
async function generateBugReport(roomId, description) {
const report = {
bug_description: description,
room_id: roomId,
diagnostic: await generateRenderingDiagnostic(roomId),
steps_to_reproduce: [
'1. Load ROM',
'2. Open Dungeon Editor',
`3. Navigate to Room ${roomId}`,
'4. Observe [specific issue]'
],
expected_behavior: 'Objects should render at correct positions matching tile grid',
actual_behavior: description
};
console.log(JSON.stringify(report, null, 2));
return report;
}
// Usage
await generateBugReport(42, 'Chest renders 8 pixels too far right');
```

View File

@@ -0,0 +1,135 @@
# WASM Planning Documentation Archive
**Date Archived:** November 24, 2025
**Archived By:** Documentation Janitor
This directory contains WASM development planning documents that represent historical design decisions and feature roadmaps. Most content has been superseded by implementation and integration into `docs/internal/wasm_dev_status.md`.
## Archived Documents
### 1. wasm-network-support-plan.md
- **Original Purpose:** Detailed plan for implementing browser-compatible networking (Phases 1-5)
- **Status:** Superseded by implementation
- **Why Archived:** Network abstraction and WASM implementations have been completed; this planning document is no longer needed
- **Current Reference:** See `wasm_dev_status.md` Section 1.1 (ROM Loading & Initialization)
### 2. wasm-web-features-roadmap.md
- **Original Purpose:** Comprehensive feature roadmap (Phases 1-14)
- **Status:** Mostly completed or planning-stage
- **Why Archived:** Long-term planning document that predates actual implementation; many features are now in wasm_dev_status.md
- **Current Reference:** See `wasm_dev_status.md` Sections 1-4 for completed features
### 3. wasm-web-app-enhancements-plan.md
- **Original Purpose:** Detailed Phase 1-8 implementation plan
- **Status:** Most phases completed
- **Why Archived:** Highly structured planning document; actual implementations supersede these plans
- **Current Reference:** See `wasm_dev_status.md` for current status of all phases
### 4. wasm-ai-integration-summary.md
- **Original Purpose:** Summary of Phase 5 AI Service Integration implementation
- **Status:** Consolidated into main status document
- **Why Archived:** Content merged into `wasm_dev_status.md` AI Agent Integration section
- **Current Reference:** See `wasm_dev_status.md` Section 1 (Completed Features → AI Agent Integration)
### 5. wasm-widget-tracking-implementation.md
- **Original Purpose:** Detailed implementation notes for widget bounds tracking
- **Status:** Consolidated into main status document
- **Why Archived:** Implementation details merged into `wasm_dev_status.md` Control APIs section
- **Current Reference:** See `wasm_dev_status.md` Section 1.4 (WASM Control APIs → Widget Tracking Infrastructure)
## Content Consolidation
The following information from archived documents has been consolidated into active documentation:
| Original Document | Content Moved To | Location |
|---|---|---|
| wasm-network-support-plan.md | wasm_dev_status.md | Section 1.1, Section 4 (Key Files) |
| wasm-web-features-roadmap.md | wasm_dev_status.md | Section 1 (Completed Features) |
| wasm-web-app-enhancements-plan.md | wasm_dev_status.md | Section 1 (Completed Features) |
| wasm-ai-integration-summary.md | wasm_dev_status.md | Section 1 (AI Agent Integration) |
| wasm-widget-tracking-implementation.md | wasm_dev_status.md | Section 1.4 (Widget Tracking Infrastructure) |
## Active WASM Documentation
The following documents remain in `docs/internal/` and are actively maintained:
1. **wasm_dev_status.md** - CANONICAL STATUS DOCUMENT
- Current implementation status (updated Nov 24, 2025)
- All completed features with file references
- Technical debt and known issues
- Roadmap for next steps
2. **wasm-debug-infrastructure.md** - HIGH-LEVEL DEBUGGING OVERVIEW
- Debugging architecture and philosophy
- File system fixes with explanations
- Known limitations
- Cross-references to detailed API docs
3. **wasm-yazeDebug-api-reference.md** - DETAILED API REFERENCE
- Complete JavaScript API reference for `window.yazeDebug`
- Authoritative source for all debug functions
- Usage examples for each API section
- Palette, ROM, overworld, arena, emulator debugging
4. **wasm_dungeon_debugging.md** - QUICK REFERENCE GUIDE
- Short, practical debugging tips
- God mode console inspector usage
- Command line bridge reference
- Feature parity notes between WASM and native builds
5. **debugging-wasm-memory-errors.md** - TECHNICAL REFERENCE
- Memory debugging techniques
- SAFE_HEAP usage
- Common pitfalls and fixes
- Function mapping methods
## How to Use This Archive
If you need historical context about WASM development decisions:
1. Start with the relevant archived document
2. Check the "Current Reference" section for where the content moved
3. Consult the active documentation for implementation details
When searching for WASM documentation, use this hierarchy:
1. **wasm_dev_status.md** - Status and overview (start here)
2. **wasm-debug-infrastructure.md** - Debugging overview
3. **wasm-yazeDebug-api-reference.md** - Detailed debug API
4. **wasm_dungeon_debugging.md** - Quick reference for dungeon editor
5. **debugging-wasm-memory-errors.md** - Memory debugging specifics
## Rationale for Archival
The WASM codebase evolved rapidly from November 2024 to November 2025:
- **Planning Phase** (early 2024): Detailed roadmaps and enhancement plans created
- **Implementation Phase** (mid 2024 - Nov 2025): Features implemented incrementally
- **Integration Phase** (current): All systems working, focus on maintenance and refinement
Archived documents represented the planning phase. As implementation completed, their value shifted from prescriptive (what to build) to historical (how we decided to build it). Consolidating information into `wasm_dev_status.md` provides:
- Single source of truth for current status
- Easier maintenance (updates in one place)
- Clearer navigation for developers
- Better signal-to-noise ratio
## Future Archival Guidance
When new WASM documentation is created:
- Keep planning/roadmap docs current or archive them promptly
- Consolidate implementation summaries into main status document
- Use high-level docs (like wasm-debug-infrastructure.md) for architecture overview
- Use detailed reference docs (like wasm-yazeDebug-api-reference.md) for API details
- Maintain clear cross-references between related docs
---
**Archive Directory Structure:**
```
docs/internal/agents/archive/wasm-planning-2025/
├── README.md (this file)
├── wasm-network-support-plan.md
├── wasm-web-features-roadmap.md
├── wasm-web-app-enhancements-plan.md
├── wasm-ai-integration-summary.md
└── wasm-widget-tracking-implementation.md
```

View File

@@ -0,0 +1,199 @@
# WASM AI Service Integration Summary
## Overview
This document summarizes the implementation of Phase 5: AI Service Integration for WASM web build, as specified in the wasm-web-app-enhancements-plan.md.
## Files Created
### 1. Browser AI Service (`src/cli/service/ai/`)
#### `browser_ai_service.h`
- **Purpose**: Browser-based AI service interface for WASM builds
- **Key Features**:
- Implements `AIService` interface for consistency with native builds
- Uses `IHttpClient` from network abstraction layer
- Supports Gemini API for text generation
- Provides vision model support for image analysis
- Manages API keys securely via sessionStorage
- CORS-compliant HTTP requests
- Proper error handling with `absl::Status`
- **Compilation**: Only compiled when `__EMSCRIPTEN__` is defined
#### `browser_ai_service.cc`
- **Purpose**: Implementation of browser AI service
- **Key Features**:
- `GenerateResponse()` for single prompts and conversation history
- `AnalyzeImage()` for vision model support
- JSON request/response handling with nlohmann/json
- Comprehensive error handling and status code mapping
- Debug logging to browser console
- Support for multiple Gemini models (2.0 Flash, 1.5 Pro, etc.)
- Proper handling of API rate limits and quotas
### 2. Browser Storage (`src/app/platform/wasm/`)
#### `wasm_browser_storage.h`
- **Purpose**: Browser storage wrapper for API keys and settings
- **Note**: This is NOT actually secure storage - uses standard localStorage/sessionStorage
- **Key Features**:
- Dual storage modes: sessionStorage (default) and localStorage
- API key management: Store, Retrieve, Clear, Check existence
- Generic secret storage for other sensitive data
- Storage quota tracking
- Bulk operations (list all keys, clear all)
- Browser storage availability checking
#### `wasm_browser_storage.cc`
- **Purpose**: Implementation using Emscripten JavaScript interop
- **Key Features**:
- JavaScript bridge functions using `EM_JS` macros
- SessionStorage access (cleared on tab close)
- LocalStorage access (persistent)
- Prefix-based key namespacing (`yaze_secure_api_`, `yaze_secure_secret_`)
- Error handling for storage exceptions
- Memory management for JS string conversions
## Build System Updates
### 1. CMake Configuration Updates
#### `src/cli/agent.cmake`
- Modified to create a minimal `yaze_agent` library for WASM builds
- Includes browser AI service sources
- Links with network abstraction layer (`yaze_net`)
- Enables JSON support for API communication
#### `src/app/app_core.cmake`
- Added `wasm_browser_storage.cc` to WASM platform sources
- Integrated with existing WASM file system and loading manager
#### `src/CMakeLists.txt`
- Updated to include `net_library.cmake` for all builds (including WASM)
- Network library now provides WASM-compatible HTTP client
#### `CMakePresets.json`
- Added new `wasm-ai` preset for testing AI features in WASM
- Configured with AI runtime enabled and Fetch API flags
## Integration with Existing Systems
### Network Abstraction Layer
- Leverages existing `IHttpClient` interface
- Uses `EmscriptenHttpClient` for browser-based HTTP requests
- Supports CORS-compliant requests to Gemini API
### AI Service Interface
- Implements standard `AIService` interface
- Compatible with existing agent response structures
- Supports tool calls and structured responses
### WASM Platform Support
- Integrates with existing WASM error handler
- Works alongside WASM storage and file dialog systems
- Compatible with progressive loading manager
## API Key Security
### Storage Security Model
1. **SessionStorage (Default)**:
- Keys stored in browser memory
- Automatically cleared when tab closes
- No persistence across sessions
- Recommended for security
2. **LocalStorage (Optional)**:
- Persistent storage
- Survives browser restarts
- Less secure but more convenient
- User choice based on preference
### Security Considerations
- Keys never hardcoded in binary
- Keys prefixed to avoid conflicts
- No encryption currently (future enhancement)
- Browser same-origin policy provides isolation
## Usage Example
```cpp
#ifdef __EMSCRIPTEN__
#include "cli/service/ai/browser_ai_service.h"
#include "app/net/wasm/emscripten_http_client.h"
#include "app/platform/wasm/wasm_browser_storage.h"
// Store API key from user input
WasmBrowserStorage::StoreApiKey("gemini", user_api_key);
}
// Create AI service
BrowserAIConfig config;
config.api_key = WasmBrowserStorage::RetrieveApiKey("gemini").value();
config.model = "gemini-2.5-flash";
auto http_client = std::make_unique<EmscriptenHttpClient>();
BrowserAIService ai_service(config, std::move(http_client));
// Generate response
auto response = ai_service.GenerateResponse("Explain the Zelda 3 ROM format");
#endif
```
## Testing
### Test File: `test/browser_ai_test.cc`
- Verifies secure storage operations
- Tests AI service creation
- Validates model listing
- Checks error handling
### Build and Test Commands
```bash
# Configure with AI support
cmake --preset wasm-ai
# Build
cmake --build build_wasm_ai
# Run in browser
emrun build_wasm_ai/yaze.html
```
## CORS Considerations
### Gemini API
- ✅ Works with browser fetch (Google APIs support CORS)
- ✅ No proxy required
- ✅ Direct browser-to-API communication
### Ollama (Future)
- ⚠️ Requires `--cors` flag on Ollama server
- ⚠️ May need proxy for local instances
- ⚠️ Security implications of CORS relaxation
## Future Enhancements
1. **Encryption**: Add client-side encryption for stored API keys
2. **Multiple Providers**: Support for OpenAI, Anthropic APIs
3. **Streaming Responses**: Implement streaming for better UX
4. **Offline Caching**: Cache AI responses for offline use
5. **Web Worker Integration**: Move AI calls to background thread
## Limitations
1. **Browser Security**: Subject to browser security policies
2. **CORS Restrictions**: Limited to CORS-enabled APIs
3. **Storage Limits**: ~5-10MB for sessionStorage/localStorage
4. **No File System**: Cannot access local models
5. **Network Required**: No offline AI capabilities
## Conclusion
The WASM AI service integration successfully brings browser-based AI capabilities to yaze. The implementation:
- ✅ Provides secure API key management
- ✅ Integrates cleanly with existing architecture
- ✅ Supports both text and vision models
- ✅ Handles errors gracefully
- ✅ Works within browser security constraints
This enables users to leverage AI assistance for ROM hacking directly in their browser without needing to install local AI models or tools.

View File

@@ -0,0 +1,415 @@
# WASM Network Support Plan for yaze
## Executive Summary
This document outlines the architectural changes required to enable AI services (Gemini/Ollama) and WebSocket collaboration features in the browser-based WASM build of yaze. The main challenge is replacing native networking libraries (cpp-httplib, OpenSSL, curl) with browser-compatible APIs provided by Emscripten.
## Current Architecture Analysis
### 1. Gemini AI Service (`src/cli/service/ai/gemini_ai_service.cc`)
**Current Implementation:**
- Uses `httplib` (cpp-httplib) for HTTPS requests with OpenSSL support
- Falls back to `curl` command via `popen()` for API calls
- Depends on OpenSSL for SSL/TLS encryption
- Uses base64 encoding for image uploads
**Key Dependencies:**
- `httplib.h` - C++ HTTP library
- OpenSSL - SSL/TLS support
- `popen()`/`pclose()` - Process execution for curl
- File system access for temporary JSON files
### 2. Ollama AI Service (`src/cli/service/ai/ollama_ai_service.cc`)
**Current Implementation:**
- Uses `httplib::Client` for HTTP requests to local Ollama server
- Communicates over HTTP (not HTTPS) on localhost:11434
- JSON parsing with nlohmann/json
**Key Dependencies:**
- `httplib.h` - C++ HTTP library
- No SSL/TLS requirement (local HTTP only)
### 3. WebSocket Client (`src/app/net/websocket_client.cc`)
**Current Implementation:**
- Uses `httplib::Client` as a placeholder (not true WebSocket)
- Currently implements HTTP POST fallback instead of WebSocket
- Conditional OpenSSL support for secure connections
- Thread-based receive loop
**Key Dependencies:**
- `httplib.h` - C++ HTTP library
- OpenSSL (optional) - For WSS support
- `std::thread` - For receive loop
- Platform-specific socket libraries (ws2_32 on Windows)
### 4. HTTP Server (`src/cli/service/api/http_server.cc`)
**Current Implementation:**
- Uses `httplib::Server` for REST API endpoints
- Runs in separate thread
- Provides health check and model listing endpoints
**Key Dependencies:**
- `httplib.h` - C++ HTTP server
- `std::thread` - For server thread
## Emscripten Capabilities
### Available APIs:
1. **Fetch API** (`emscripten_fetch()`)
- Asynchronous HTTP/HTTPS requests
- Supports GET, POST, PUT, DELETE
- CORS-aware
- Can handle binary data and streams
2. **WebSocket API**
- Native browser WebSocket support
- Accessible via Emscripten's WebSocket wrapper
- Full duplex communication
- Binary and text message support
3. **Web Workers** (via pthread emulation)
- Background processing
- Shared memory support with SharedArrayBuffer
- Can handle async operations
## Required Changes
### Phase 1: Abstract Network Layer
Create platform-agnostic network interfaces:
```cpp
// src/app/net/http_client.h
class IHttpClient {
public:
virtual ~IHttpClient() = default;
virtual absl::StatusOr<HttpResponse> Get(const std::string& url,
const Headers& headers) = 0;
virtual absl::StatusOr<HttpResponse> Post(const std::string& url,
const std::string& body,
const Headers& headers) = 0;
};
// src/app/net/websocket.h
class IWebSocket {
public:
virtual ~IWebSocket() = default;
virtual absl::Status Connect(const std::string& url) = 0;
virtual absl::Status Send(const std::string& message) = 0;
virtual void OnMessage(std::function<void(const std::string&)> callback) = 0;
};
```
### Phase 2: Native Implementation
Keep existing implementations for native builds:
```cpp
// src/app/net/native/httplib_client.cc
class HttpLibClient : public IHttpClient {
// Current httplib implementation
};
// src/app/net/native/httplib_websocket.cc
class HttpLibWebSocket : public IWebSocket {
// Current httplib-based implementation
};
```
### Phase 3: Emscripten Implementation
Create browser-compatible implementations:
```cpp
// src/app/net/wasm/emscripten_http_client.cc
#ifdef __EMSCRIPTEN__
class EmscriptenHttpClient : public IHttpClient {
absl::StatusOr<HttpResponse> Get(const std::string& url,
const Headers& headers) override {
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
// Set headers
std::vector<const char*> header_strings;
for (const auto& [key, value] : headers) {
header_strings.push_back(key.c_str());
header_strings.push_back(value.c_str());
}
header_strings.push_back(nullptr);
attr.requestHeaders = header_strings.data();
// Synchronous fetch (blocks until complete)
emscripten_fetch_t* fetch = emscripten_fetch(&attr, url.c_str());
HttpResponse response;
response.status = fetch->status;
response.body = std::string(fetch->data, fetch->numBytes);
emscripten_fetch_close(fetch);
return response;
}
absl::StatusOr<HttpResponse> Post(const std::string& url,
const std::string& body,
const Headers& headers) override {
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "POST");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
attr.requestData = body.c_str();
attr.requestDataSize = body.length();
// Similar implementation as Get()
// ...
}
};
#endif
// src/app/net/wasm/emscripten_websocket.cc
#ifdef __EMSCRIPTEN__
#include <emscripten/websocket.h>
class EmscriptenWebSocket : public IWebSocket {
EMSCRIPTEN_WEBSOCKET_T socket_;
absl::Status Connect(const std::string& url) override {
EmscriptenWebSocketCreateAttributes attrs = {
url.c_str(),
nullptr, // protocols
EM_TRUE // createOnMainThread
};
socket_ = emscripten_websocket_new(&attrs);
if (socket_ <= 0) {
return absl::InternalError("Failed to create WebSocket");
}
// Set callbacks
emscripten_websocket_set_onopen_callback(socket_, this, OnOpenCallback);
emscripten_websocket_set_onmessage_callback(socket_, this, OnMessageCallback);
emscripten_websocket_set_onerror_callback(socket_, this, OnErrorCallback);
return absl::OkStatus();
}
absl::Status Send(const std::string& message) override {
EMSCRIPTEN_RESULT result = emscripten_websocket_send_text(
socket_, message.c_str(), message.length());
if (result != EMSCRIPTEN_RESULT_SUCCESS) {
return absl::InternalError("Failed to send WebSocket message");
}
return absl::OkStatus();
}
};
#endif
```
### Phase 4: Factory Pattern
Create factories to instantiate the correct implementation:
```cpp
// src/app/net/network_factory.cc
std::unique_ptr<IHttpClient> CreateHttpClient() {
#ifdef __EMSCRIPTEN__
return std::make_unique<EmscriptenHttpClient>();
#else
return std::make_unique<HttpLibClient>();
#endif
}
std::unique_ptr<IWebSocket> CreateWebSocket() {
#ifdef __EMSCRIPTEN__
return std::make_unique<EmscriptenWebSocket>();
#else
return std::make_unique<HttpLibWebSocket>();
#endif
}
```
### Phase 5: Service Modifications
Update AI services to use the abstraction:
```cpp
// src/cli/service/ai/gemini_ai_service.cc
class GeminiAIService {
std::unique_ptr<IHttpClient> http_client_;
GeminiAIService(const GeminiConfig& config)
: config_(config),
http_client_(CreateHttpClient()) {
// Initialize
}
absl::Status CheckAvailability() {
std::string url = "https://generativelanguage.googleapis.com/v1beta/models/"
+ config_.model;
Headers headers = {{"x-goog-api-key", config_.api_key}};
auto response_or = http_client_->Get(url, headers);
if (!response_or.ok()) {
return response_or.status();
}
auto& response = response_or.value();
if (response.status != 200) {
return absl::UnavailableError("API not available");
}
return absl::OkStatus();
}
};
```
## CMake Configuration
Update CMake to handle WASM builds:
```cmake
# src/app/net/net_library.cmake
if(EMSCRIPTEN)
set(YAZE_NET_SRC
app/net/wasm/emscripten_http_client.cc
app/net/wasm/emscripten_websocket.cc
app/net/network_factory.cc
)
# Add Emscripten fetch and WebSocket flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FETCH=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WEBSOCKET=1")
else()
set(YAZE_NET_SRC
app/net/native/httplib_client.cc
app/net/native/httplib_websocket.cc
app/net/network_factory.cc
)
# Link native dependencies
target_link_libraries(yaze_net PUBLIC httplib OpenSSL::SSL)
endif()
```
## CORS Considerations
For browser builds, the following CORS requirements apply:
1. **Gemini API**: Google's API servers must include appropriate CORS headers
2. **Ollama**: Local Ollama server needs `--cors` flag or proxy setup
3. **WebSocket Server**: Must handle WebSocket upgrade correctly
### Proxy Server Option
For services without CORS support, implement a proxy:
```javascript
// proxy-server.js (Node.js)
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// Proxy for Ollama
app.use('/ollama', createProxyMiddleware({
target: 'http://localhost:11434',
changeOrigin: true,
pathRewrite: { '^/ollama': '' }
}));
// Proxy for Gemini
app.use('/gemini', createProxyMiddleware({
target: 'https://generativelanguage.googleapis.com',
changeOrigin: true,
pathRewrite: { '^/gemini': '' }
}));
app.listen(3000);
```
## Testing Strategy
1. **Unit Tests**: Mock network interfaces for both native and WASM
2. **Integration Tests**: Test actual API calls in native builds
3. **Browser Tests**: Manual testing of WASM build in browser
4. **E2E Tests**: Selenium/Playwright for automated browser testing
## Implementation Timeline
### Week 1: Foundation
- Create abstract interfaces (IHttpClient, IWebSocket)
- Implement factory pattern
- Update CMake for conditional compilation
### Week 2: Native Refactoring
- Refactor existing code to use interfaces
- Create native implementations
- Ensure no regression in current functionality
### Week 3: WASM Implementation
- Implement EmscriptenHttpClient
- Implement EmscriptenWebSocket
- Test basic functionality
### Week 4: Service Integration
- Update GeminiAIService
- Update OllamaAIService
- Update WebSocketClient
### Week 5: Testing & Refinement
- Comprehensive testing
- CORS handling
- Performance optimization
## Risk Mitigation
### Risk 1: CORS Blocking
**Mitigation**: Implement proxy server as fallback, document CORS requirements
### Risk 2: API Key Security
**Mitigation**:
- Never embed API keys in WASM binary
- Require user to input API key via UI
- Store in browser's localStorage with encryption
### Risk 3: Performance Issues
**Mitigation**:
- Use Web Workers for background processing
- Implement request caching
- Add loading indicators for long operations
### Risk 4: Browser Compatibility
**Mitigation**:
- Test on Chrome, Firefox, Safari, Edge
- Use feature detection
- Provide fallbacks for unsupported features
## Security Considerations
1. **API Keys**: Must be user-provided, never hardcoded
2. **HTTPS Only**: All API calls must use HTTPS in production
3. **Input Validation**: Sanitize all user inputs before API calls
4. **Rate Limiting**: Implement client-side rate limiting
5. **Content Security Policy**: Configure CSP headers properly
## Conclusion
The transition to WASM-compatible networking is achievable through careful abstraction and platform-specific implementations. The key is maintaining a clean separation between platform-agnostic interfaces and platform-specific implementations. This approach allows the codebase to support both native and browser environments without sacrificing functionality or performance.
The proposed architecture provides:
- Clean abstraction layers
- Minimal changes to existing service code
- Easy testing and mocking
- Future extensibility for other platforms
Next steps:
1. Review and approve this plan
2. Create feature branch for implementation
3. Begin with abstract interface definitions
4. Implement incrementally with continuous testing

View File

@@ -0,0 +1,554 @@
# WASM Web App Enhancements Plan
## Executive Summary
This document outlines the comprehensive plan to make yaze's WASM web build fully featured, robust, and user-friendly. The goal is to achieve feature parity with the native desktop application where technically feasible, while leveraging browser-specific capabilities where appropriate.
## Current Status
### Completed
- [x] Basic WASM build configuration
- [x] pthread support for Emscripten
- [x] Network abstraction layer (IHttpClient, IWebSocket)
- [x] Emscripten HTTP client using `emscripten_fetch()`
- [x] Emscripten WebSocket using browser API
- [x] **Phase 1**: File System Layer (WasmStorage, WasmFileDialog)
- [x] **Phase 2**: Error Handling Infrastructure (WasmErrorHandler)
- [x] **Phase 3**: Progressive Loading UI (WasmLoadingManager)
- [x] **Phase 4**: Offline Support (Service Workers, PWA manifest)
- [x] **Phase 5**: AI Service Integration (BrowserAIService, WasmSecureStorage)
- [x] **Phase 6**: Local Storage Persistence (WasmSettings, AutoSaveManager)
- [x] **Phase 7**: Web Workers for heavy processing (WasmWorkerPool)
- [x] **Phase 8**: Emulator Audio (WebAudio, WasmAudioBackend)
### In Progress
- [ ] WASM CI build verification
- [ ] Integration of loading manager with gfx::Arena
- [ ] Integration testing across all phases
---
## Phase 1: File System Layer
### Overview
WASM builds cannot access the local filesystem directly. We need a virtualized file system layer that uses browser storage APIs.
### Implementation
#### 1.1 IndexedDB Storage Backend
```cpp
// src/app/platform/wasm/wasm_storage.h
class WasmStorage {
public:
// Store ROM data
static absl::Status SaveRom(const std::string& name, const std::vector<uint8_t>& data);
static absl::StatusOr<std::vector<uint8_t>> LoadRom(const std::string& name);
static absl::Status DeleteRom(const std::string& name);
static std::vector<std::string> ListRoms();
// Store project files (JSON, palettes, patches)
static absl::Status SaveProject(const std::string& name, const std::string& json);
static absl::StatusOr<std::string> LoadProject(const std::string& name);
// Store user preferences
static absl::Status SavePreferences(const nlohmann::json& prefs);
static absl::StatusOr<nlohmann::json> LoadPreferences();
};
```
#### 1.2 File Upload Handler
```cpp
// JavaScript interop for file input
EM_JS(void, openFileDialog, (const char* accept, int callback_id), {
const input = document.createElement('input');
input.type = 'file';
input.accept = UTF8ToString(accept);
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = () => {
const data = new Uint8Array(reader.result);
Module._handleFileLoaded(callback_id, data);
};
reader.readAsArrayBuffer(file);
};
input.click();
});
```
#### 1.3 File Download Handler
```cpp
// Download ROM or project file
EM_JS(void, downloadFile, (const char* filename, const uint8_t* data, size_t size), {
const blob = new Blob([HEAPU8.subarray(data, data + size)], {type: 'application/octet-stream'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = UTF8ToString(filename);
a.click();
URL.revokeObjectURL(url);
});
```
### Files to Create
- `src/app/platform/wasm/wasm_storage.h`
- `src/app/platform/wasm/wasm_storage.cc`
- `src/app/platform/wasm/wasm_file_dialog.h`
- `src/app/platform/wasm/wasm_file_dialog.cc`
---
## Phase 2: Error Handling Infrastructure
### Overview
Browser-based applications need specialized error handling that integrates with the web UI and provides user-friendly feedback.
### Implementation
#### 2.1 Browser Error Handler
```cpp
// src/app/platform/wasm/wasm_error_handler.h
class WasmErrorHandler {
public:
// Display error in browser UI
static void ShowError(const std::string& title, const std::string& message);
static void ShowWarning(const std::string& title, const std::string& message);
static void ShowInfo(const std::string& title, const std::string& message);
// Toast notifications (non-blocking)
static void Toast(const std::string& message, ToastType type, int duration_ms = 3000);
// Progress indicators
static void ShowProgress(const std::string& task, float progress);
static void HideProgress();
// Confirmation dialogs
static void Confirm(const std::string& message, std::function<void(bool)> callback);
};
```
#### 2.2 JavaScript Integration
```javascript
// src/web/error_handler.js
window.showYazeError = function(title, message) {
// Create styled error modal
const modal = document.createElement('div');
modal.className = 'yaze-error-modal';
modal.innerHTML = `
<div class="yaze-error-content">
<h2>${escapeHtml(title)}</h2>
<p>${escapeHtml(message)}</p>
<button onclick="this.parentElement.parentElement.remove()">OK</button>
</div>
`;
document.body.appendChild(modal);
};
window.showYazeToast = function(message, type, duration) {
const toast = document.createElement('div');
toast.className = `yaze-toast yaze-toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), duration);
};
```
### Files to Create
- `src/app/platform/wasm/wasm_error_handler.h`
- `src/app/platform/wasm/wasm_error_handler.cc`
- `src/web/error_handler.js`
- `src/web/error_handler.css`
---
## Phase 3: Progressive Loading UI
### Overview
ROM loading and graphics processing can take significant time. Users need visual feedback and the ability to cancel long operations.
### Implementation
#### 3.1 Loading Manager
```cpp
// src/app/platform/wasm/wasm_loading_manager.h
class WasmLoadingManager {
public:
// Start a loading operation with progress tracking
static LoadingHandle BeginLoading(const std::string& task_name);
// Update progress (0.0 to 1.0)
static void UpdateProgress(LoadingHandle handle, float progress);
static void UpdateMessage(LoadingHandle handle, const std::string& message);
// Check if user requested cancel
static bool IsCancelled(LoadingHandle handle);
// Complete the loading operation
static void EndLoading(LoadingHandle handle);
};
```
#### 3.2 Integration with gfx::Arena
```cpp
// Modified graphics loading to report progress
void Arena::LoadGraphicsWithProgress(Rom* rom) {
auto handle = WasmLoadingManager::BeginLoading("Loading Graphics");
for (int i = 0; i < kNumGraphicsSheets; i++) {
if (WasmLoadingManager::IsCancelled(handle)) {
WasmLoadingManager::EndLoading(handle);
return; // User cancelled
}
LoadGraphicsSheet(rom, i);
WasmLoadingManager::UpdateProgress(handle, static_cast<float>(i) / kNumGraphicsSheets);
WasmLoadingManager::UpdateMessage(handle, absl::StrFormat("Sheet %d/%d", i, kNumGraphicsSheets));
// Yield to browser event loop periodically
emscripten_sleep(0);
}
WasmLoadingManager::EndLoading(handle);
}
```
### Files to Create
- `src/app/platform/wasm/wasm_loading_manager.h`
- `src/app/platform/wasm/wasm_loading_manager.cc`
- `src/web/loading_indicator.js`
- `src/web/loading_indicator.css`
---
## Phase 4: Offline Support (Service Workers)
### Overview
Cache the WASM binary and assets for offline use, enabling users to work without an internet connection.
### Implementation
#### 4.1 Service Worker
```javascript
// src/web/service-worker.js
const CACHE_NAME = 'yaze-cache-v1';
const ASSETS = [
'/yaze.wasm',
'/yaze.js',
'/yaze.data',
'/index.html',
'/style.css',
'/fonts/',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
```
#### 4.2 Registration
```javascript
// src/web/index.html
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.log('Service Worker registered:', registration.scope);
});
}
```
### Files to Create
- `src/web/service-worker.js`
- `src/web/manifest.json` (PWA manifest)
---
## Phase 5: AI Service Integration
### Overview
Integrate the network abstraction layer with AI services (Gemini, Ollama) for browser-based AI assistance.
### Implementation
#### 5.1 Browser AI Client
```cpp
// src/cli/service/ai/browser_ai_service.h
#ifdef __EMSCRIPTEN__
class BrowserAIService : public IAIService {
public:
explicit BrowserAIService(const AIConfig& config);
absl::StatusOr<std::string> GenerateResponse(const std::string& prompt) override;
absl::StatusOr<std::string> AnalyzeImage(const gfx::Bitmap& image,
const std::string& prompt) override;
private:
std::unique_ptr<IHttpClient> http_client_;
std::string api_key_; // User-provided, never stored in binary
std::string model_;
};
#endif
```
#### 5.2 API Key Management
```cpp
// Secure API key storage in browser
EM_JS(void, storeApiKey, (const char* service, const char* key), {
// Use sessionStorage for temporary storage (cleared on tab close)
// or encrypted localStorage for persistent storage
sessionStorage.setItem('yaze_' + UTF8ToString(service) + '_key', UTF8ToString(key));
});
EM_JS(char*, retrieveApiKey, (const char* service), {
const key = sessionStorage.getItem('yaze_' + UTF8ToString(service) + '_key');
if (!key) return null;
const len = lengthBytesUTF8(key) + 1;
const ptr = _malloc(len);
stringToUTF8(key, ptr, len);
return ptr;
});
```
### CORS Considerations
- Gemini API: Should work with browser fetch (Google APIs support CORS)
- Ollama: Requires `--cors` flag or proxy server for local instances
### Files to Create
- `src/cli/service/ai/browser_ai_service.h`
- `src/cli/service/ai/browser_ai_service.cc`
- `src/app/platform/wasm/wasm_browser_storage.h`
- `src/app/platform/wasm/wasm_browser_storage.cc`
---
## Phase 6: Local Storage Persistence
### Overview
Persist user settings, recent files, undo history, and workspace layouts in browser storage.
### Implementation
#### 6.1 Settings Persistence
```cpp
// src/app/platform/wasm/wasm_settings.h
class WasmSettings {
public:
// User preferences
static void SaveTheme(const std::string& theme);
static std::string LoadTheme();
// Recent files (stored as IndexedDB references)
static void AddRecentFile(const std::string& name);
static std::vector<std::string> GetRecentFiles();
// Workspace layouts
static void SaveWorkspace(const std::string& name, const std::string& layout_json);
static std::string LoadWorkspace(const std::string& name);
// Undo history (for crash recovery)
static void SaveUndoHistory(const std::string& editor, const std::vector<uint8_t>& history);
static std::vector<uint8_t> LoadUndoHistory(const std::string& editor);
};
```
#### 6.2 Auto-Save & Recovery
```cpp
// Periodic auto-save
class AutoSaveManager {
public:
void Start(int interval_seconds = 60);
void Stop();
// Called on page unload
void EmergencySave();
// Called on startup
bool HasRecoveryData();
void RecoverLastSession();
void ClearRecoveryData();
};
```
### Files to Create
- `src/app/platform/wasm/wasm_settings.h`
- `src/app/platform/wasm/wasm_settings.cc`
- `src/app/platform/wasm/wasm_autosave.h`
- `src/app/platform/wasm/wasm_autosave.cc`
---
## Phase 7: Web Workers for Heavy Processing
### Overview
Offload CPU-intensive operations to Web Workers to prevent UI freezing.
### Implementation
#### 7.1 Background Processing Worker
```cpp
// Operations to run in Web Worker:
// - ROM decompression (LC-LZ2)
// - Graphics sheet decoding
// - Palette calculations
// - Asar assembly compilation
class WasmWorkerPool {
public:
using TaskCallback = std::function<void(const std::vector<uint8_t>&)>;
// Submit work to background thread
void SubmitTask(const std::string& type,
const std::vector<uint8_t>& input,
TaskCallback callback);
// Wait for all tasks
void WaitAll();
};
```
### Files to Create
- `src/app/platform/wasm/wasm_worker_pool.h`
- `src/app/platform/wasm/wasm_worker_pool.cc`
- `src/web/worker.js`
---
## Phase 8: Emulator Integration
### Overview
The SNES emulator can run in WASM with WebAudio for sound output.
### Implementation
#### 8.1 WebAudio Backend
```cpp
// src/app/emu/platform/wasm/wasm_audio.h
class WasmAudioBackend : public IAudioBackend {
public:
void Initialize(int sample_rate, int buffer_size) override;
void QueueSamples(const int16_t* samples, size_t count) override;
void Shutdown() override;
};
```
#### 8.2 Canvas Rendering
```cpp
// Use EM_ASM for direct canvas manipulation if needed
// Or use existing ImGui/SDL2 rendering (already WASM compatible)
```
### Files to Create
- `src/app/emu/platform/wasm/wasm_audio.h`
- `src/app/emu/platform/wasm/wasm_audio.cc`
---
## Feature Availability Matrix
| Feature | Native | WASM | Notes |
|---------|--------|------|-------|
| ROM Loading | File dialog | File input + IndexedDB | Full support |
| ROM Saving | Direct write | Blob download | Full support |
| Overworld Editor | Full | Full | No limitations |
| Dungeon Editor | Full | Full | No limitations |
| Graphics Editor | Full | Full | No limitations |
| Palette Editor | Full | Full | No limitations |
| Emulator | Full | Full | WebAudio for sound |
| AI (Gemini) | HTTP | Browser Fetch | Requires API key |
| AI (Ollama) | HTTP | Requires proxy | CORS limitation |
| Asar Assembly | Full | In-memory | No file I/O |
| Collaboration | gRPC | WebSocket | Different protocol |
| Crash Recovery | File-based | IndexedDB | Equivalent |
| Offline Mode | N/A | Service Worker | WASM-only feature |
---
## CMake Configuration
### Recommended Preset Updates
```json
{
"name": "wasm-release",
"cacheVariables": {
"YAZE_BUILD_WASM_PLATFORM": "ON",
"YAZE_WASM_ENABLE_WORKERS": "ON",
"YAZE_WASM_ENABLE_OFFLINE": "ON",
"YAZE_WASM_ENABLE_AI": "ON"
}
}
```
### Source Organization
```
src/app/platform/
wasm/
wasm_storage.h/.cc
wasm_file_dialog.h/.cc
wasm_error_handler.h/.cc
wasm_loading_manager.h/.cc
wasm_settings.h/.cc
wasm_autosave.h/.cc
wasm_worker_pool.h/.cc
wasm_browser_storage.h/.cc
src/web/
index.html
shell.html
style.css
error_handler.js
loading_indicator.js
service-worker.js
worker.js
manifest.json
```
---
## Implementation Priority
### High Priority (Required for MVP)
1. File System Layer (Phase 1)
2. Error Handling (Phase 2)
3. Progressive Loading (Phase 3)
### Medium Priority (Enhanced Experience)
4. Local Storage Persistence (Phase 6)
5. Offline Support (Phase 4)
### Lower Priority (Advanced Features)
6. AI Integration (Phase 5)
7. Web Workers (Phase 7)
8. Emulator Audio (Phase 8)
---
## Success Criteria
- [ ] User can load ROM from local file system
- [ ] User can save modified ROM to downloads
- [ ] Loading progress is visible with cancel option
- [ ] Errors display user-friendly messages
- [ ] Settings persist across sessions
- [ ] App works offline after first load
- [ ] AI assistance available via Gemini API
- [ ] No UI freezes during heavy operations
- [ ] Emulator runs with sound
---
## References
- [Emscripten Documentation](https://emscripten.org/docs/)
- [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
- [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
- [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
- [WebAudio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)

View File

@@ -0,0 +1,224 @@
# WASM Web Features Roadmap
This document captures planned features for the browser-based yaze editor.
## Foundation (Completed)
The following infrastructure is in place:
- **Phase 1**: File System Layer (WasmStorage, WasmFileDialog) - IndexedDB storage
- **Phase 2**: Error Handling (WasmErrorHandler) - Browser UI integration
- **Phase 3**: Progressive Loading (WasmLoadingManager) - Cancellable loading with progress
- **Phase 4**: Offline Support (service-worker.js, manifest.json) - PWA capabilities
- **Phase 5**: AI Integration (BrowserAIService, WasmSecureStorage) - WebLLM ready
- **Phase 6**: Local Storage (WasmSettings, WasmAutosave) - Preferences persistence
- **Phase 7**: Web Workers (WasmWorkerPool) - Background task processing
- **Phase 8**: WebAudio (WasmAudio) - SPC700 audio playback
---
## Phase 9: Enhanced File Handling
### 9.1 Drag & Drop ROM Loading
- **Status**: Planned
- **Priority**: High
- **Description**: Enhanced drag-and-drop interface for ROM files
- **Features**:
- Visual drop zone with hover effects
- File type validation before processing
- Preview panel showing ROM metadata
- Multiple file support (load ROM + patch together)
- **Files**: `src/app/platform/wasm/wasm_drop_handler.{h,cc}`, `src/web/drop_zone.{js,css}`
### 9.2 Export Options
- **Status**: Planned
- **Priority**: High
- **Description**: Export modifications as patches instead of full ROMs
- **Features**:
- BPS patch generation (standard format)
- IPS patch generation (legacy support)
- UPS patch generation (alternative)
- Patch preview showing changed bytes
- Direct download or save to IndexedDB
- **Files**: `src/app/platform/wasm/wasm_patch_export.{h,cc}`
---
## Phase 10: Collaboration
### 10.1 Real-time Collaboration
- **Status**: Planned
- **Priority**: High
- **Description**: Multi-user editing via WebSocket
- **Features**:
- Session creation/joining with room codes
- User presence indicators (cursors, selections)
- Change synchronization via operational transforms
- Chat/comments sidebar
- Permission levels (owner, editor, viewer)
- **Files**: `src/app/platform/wasm/wasm_collaboration.{h,cc}`, `src/web/collaboration_ui.{js,css}`
- **Dependencies**: EmscriptenWebSocket (completed)
### 10.2 ShareLink Generation
- **Status**: Future
- **Priority**: Medium
- **Description**: Create shareable URLs with embedded patches
- **Features**:
- Base64-encoded diff in URL hash
- Short URL generation via service
- QR code generation for mobile sharing
### 10.3 Comment Annotations
- **Status**: Future
- **Priority**: Low
- **Description**: Add notes to map locations/rooms
- **Features**:
- Pin comments to coordinates
- Threaded discussions
- Export annotations as JSON
---
## Phase 11: Browser-Specific Enhancements
### 11.1 Keyboard Shortcut Overlay
- **Status**: Future
- **Priority**: Medium
- **Description**: Help panel showing all keyboard shortcuts
- **Features**:
- Toggle with `?` key
- Context-aware (shows relevant shortcuts for current editor)
- Searchable
### 11.2 Touch Support
- **Status**: Future
- **Priority**: Medium
- **Description**: Touch gestures for tablet/mobile browsers
- **Features**:
- Pinch to zoom
- Two-finger pan
- Long press for context menu
- Touch-friendly toolbar
### 11.3 Fullscreen Mode
- **Status**: Future
- **Priority**: Low
- **Description**: Dedicated fullscreen API integration
- **Features**:
- F11 toggle
- Auto-hide toolbar in fullscreen
- Escape to exit
### 11.4 Browser Notifications
- **Status**: Future
- **Priority**: Low
- **Description**: Alert when long operations complete
- **Features**:
- Permission request flow
- Build/export completion notifications
- Background tab awareness
---
## Phase 12: AI Integration Enhancements
### 12.1 Browser-Local LLM
- **Status**: Future
- **Priority**: Medium
- **Description**: In-browser AI using WebLLM
- **Features**:
- Model download/caching
- Chat interface for ROM hacking questions
- Code generation for ASM patches
- **Dependencies**: BrowserAIService (completed)
### 12.2 ROM Analysis Reports
- **Status**: Future
- **Priority**: Low
- **Description**: Generate sharable HTML reports
- **Features**:
- Modification summary
- Changed areas visualization
- Exportable as standalone HTML
---
## Phase 13: Performance Optimizations
### 13.1 WebGPU Rendering
- **Status**: Future
- **Priority**: Medium
- **Description**: Modern GPU acceleration
- **Features**:
- Feature detection with fallback to WebGL
- Hardware-accelerated tile rendering
- Shader-based effects
### 13.2 Lazy Tile Loading
- **Status**: Future
- **Priority**: Medium
- **Description**: Load only visible map sections
- **Features**:
- Virtual scrolling for large maps
- Tile cache management
- Preload adjacent areas
---
## Phase 14: Cloud Features
### 14.1 Cloud ROM Storage
- **Status**: Future
- **Priority**: Low
- **Description**: Optional cloud sync for projects
- **Features**:
- User accounts
- Project backup/restore
- Cross-device sync
### 14.2 Screenshot Gallery
- **Status**: Future
- **Priority**: Low
- **Description**: Save emulator screenshots
- **Features**:
- Auto-capture on emulator test
- Gallery view in IndexedDB
- Share to social media
---
## Implementation Notes
### Technology Stack
- **Storage**: IndexedDB via WasmStorage
- **Networking**: EmscriptenWebSocket for real-time features
- **Background Processing**: WasmWorkerPool (pthread-based)
- **Audio**: WebAudio API via WasmAudio
- **UI**: ImGui with HTML overlays for browser-specific elements
### File Organization
```
src/app/platform/wasm/
├── wasm_storage.{h,cc} # Phase 1 ✓
├── wasm_file_dialog.{h,cc} # Phase 1 ✓
├── wasm_error_handler.{h,cc} # Phase 2 ✓
├── wasm_loading_manager.{h,cc} # Phase 3 ✓
├── wasm_settings.{h,cc} # Phase 6 ✓
├── wasm_autosave.{h,cc} # Phase 6 ✓
├── wasm_browser_storage.{h,cc} # Phase 5 ✓
├── wasm_worker_pool.{h,cc} # Phase 7 ✓
├── wasm_drop_handler.{h,cc} # Phase 9 (planned)
├── wasm_patch_export.{h,cc} # Phase 9 (planned)
└── wasm_collaboration.{h,cc} # Phase 10 (planned)
src/app/emu/platform/wasm/
└── wasm_audio.{h,cc} # Phase 8 ✓
src/web/
├── error_handler.{js,css} # Phase 2 ✓
├── loading_indicator.{js,css} # Phase 3 ✓
├── service-worker.js # Phase 4 ✓
├── manifest.json # Phase 4 ✓
├── drop_zone.{js,css} # Phase 9 (planned)
└── collaboration_ui.{js,css} # Phase 10 (planned)
```

View File

@@ -0,0 +1,344 @@
# WASM Widget Tracking Implementation
**Date**: 2025-11-24
**Author**: Claude (AI Agent)
**Status**: Implemented
**Related Files**:
- `/Users/scawful/Code/yaze/src/app/platform/wasm/wasm_control_api.cc`
- `/Users/scawful/Code/yaze/src/app/gui/automation/widget_id_registry.h`
- `/Users/scawful/Code/yaze/src/app/gui/automation/widget_measurement.h`
- `/Users/scawful/Code/yaze/src/app/controller.cc`
## Overview
This document describes the implementation of actual ImGui widget bounds tracking for GUI automation in the YAZE WASM build. The system replaces placeholder hardcoded bounds with real-time widget position data from the `WidgetIdRegistry`.
## Problem Statement
The original `WasmControlApi::GetUIElementTree()` and `GetUIElementBounds()` implementations returned hardcoded placeholder bounds:
```cpp
// OLD: Hardcoded placeholder
elem["bounds"] = {{"x", 0}, {"y", 0}, {"width", 100}, {"height", 30}};
```
This prevented accurate GUI automation, as agents and test frameworks couldn't reliably click on or query widget positions.
## Solution Architecture
### 1. Existing Infrastructure (Already in Place)
YAZE already had a comprehensive widget tracking system:
- **`WidgetIdRegistry`** (`src/app/gui/automation/widget_id_registry.h`): Centralized registry that tracks all ImGui widgets with their bounds, visibility, and state
- **`WidgetMeasurement`** (`src/app/gui/automation/widget_measurement.h`): Measures widget dimensions using `ImGui::GetItemRectMin()` and `ImGui::GetItemRectMax()`
- **Frame lifecycle hooks**: `BeginFrame()` and `EndFrame()` calls already integrated in `Controller::OnLoad()` (lines 96-98)
### 2. Integration with WASM Control API
The implementation connects the WASM API to the existing widget registry:
#### Updated `GetUIElementTree()`
**File**: `/Users/scawful/Code/yaze/src/app/platform/wasm/wasm_control_api.cc` (lines 1386-1433)
```cpp
std::string WasmControlApi::GetUIElementTree() {
nlohmann::json result;
if (!IsReady()) {
result["error"] = "Control API not initialized";
result["elements"] = nlohmann::json::array();
return result.dump();
}
// Query the WidgetIdRegistry for all registered widgets
auto& registry = gui::WidgetIdRegistry::Instance();
const auto& all_widgets = registry.GetAllWidgets();
nlohmann::json elements = nlohmann::json::array();
// Convert WidgetInfo to JSON elements
for (const auto& [path, info] : all_widgets) {
nlohmann::json elem;
elem["id"] = info.full_path;
elem["type"] = info.type;
elem["label"] = info.label;
elem["enabled"] = info.enabled;
elem["visible"] = info.visible;
elem["window"] = info.window_name;
// Add bounds if available
if (info.bounds.valid) {
elem["bounds"] = {
{"x", info.bounds.min_x},
{"y", info.bounds.min_y},
{"width", info.bounds.max_x - info.bounds.min_x},
{"height", info.bounds.max_y - info.bounds.min_y}
};
} else {
elem["bounds"] = {
{"x", 0}, {"y", 0}, {"width", 0}, {"height", 0}
};
}
// Add metadata
if (!info.description.empty()) {
elem["description"] = info.description;
}
elem["imgui_id"] = static_cast<uint32_t>(info.imgui_id);
elem["last_seen_frame"] = info.last_seen_frame;
elements.push_back(elem);
}
result["elements"] = elements;
result["count"] = elements.size();
result["source"] = "WidgetIdRegistry";
return result.dump();
}
```
**Changes**:
- Removed hardcoded editor-specific element generation
- Queries `WidgetIdRegistry::GetAllWidgets()` for real widget data
- Returns actual bounds from `info.bounds` if valid
- Includes metadata: `imgui_id`, `last_seen_frame`, `description`
- Adds `source: "WidgetIdRegistry"` to JSON for debugging
#### Updated `GetUIElementBounds()`
**File**: `/Users/scawful/Code/yaze/src/app/platform/wasm/wasm_control_api.cc` (lines ~1435+)
```cpp
std::string WasmControlApi::GetUIElementBounds(const std::string& element_id) {
nlohmann::json result;
if (!IsReady()) {
result["error"] = "Control API not initialized";
return result.dump();
}
// Query the WidgetIdRegistry for the specific widget
auto& registry = gui::WidgetIdRegistry::Instance();
const auto* widget_info = registry.GetWidgetInfo(element_id);
result["id"] = element_id;
if (widget_info == nullptr) {
result["found"] = false;
result["error"] = "Element not found: " + element_id;
return result.dump();
}
result["found"] = true;
result["visible"] = widget_info->visible;
result["enabled"] = widget_info->enabled;
result["type"] = widget_info->type;
result["label"] = widget_info->label;
result["window"] = widget_info->window_name;
// Add bounds if available
if (widget_info->bounds.valid) {
result["x"] = widget_info->bounds.min_x;
result["y"] = widget_info->bounds.min_y;
result["width"] = widget_info->bounds.max_x - widget_info->bounds.min_x;
result["height"] = widget_info->bounds.max_y - widget_info->bounds.min_y;
result["bounds_valid"] = true;
} else {
result["x"] = 0;
result["y"] = 0;
result["width"] = 0;
result["height"] = 0;
result["bounds_valid"] = false;
}
// Add metadata
result["imgui_id"] = static_cast<uint32_t>(widget_info->imgui_id);
result["last_seen_frame"] = widget_info->last_seen_frame;
if (!widget_info->description.empty()) {
result["description"] = widget_info->description;
}
return result.dump();
}
```
**Changes**:
- Removed hardcoded element ID pattern matching
- Queries `WidgetIdRegistry::GetWidgetInfo(element_id)` for specific widget
- Returns `found: false` if widget doesn't exist
- Returns actual bounds with `bounds_valid` flag
- Includes full widget metadata
### 3. Frame Lifecycle Integration
**File**: `/Users/scawful/Code/yaze/src/app/controller.cc` (lines 96-98)
The widget registry is already integrated into the main render loop:
```cpp
absl::Status Controller::OnLoad() {
// ... ImGui::NewFrame() setup ...
gui::WidgetIdRegistry::Instance().BeginFrame();
absl::Status update_status = editor_manager_.Update();
gui::WidgetIdRegistry::Instance().EndFrame();
RETURN_IF_ERROR(update_status);
return absl::OkStatus();
}
```
**Frame Lifecycle**:
1. `BeginFrame()`: Resets `seen_in_current_frame` flag for all widgets
2. Widget rendering: Editors register widgets during `editor_manager_.Update()`
3. `EndFrame()`: Marks unseen widgets as invisible, prunes stale entries
### 4. Widget Registration (Future Work)
**Current State**: Widget registration infrastructure exists but **editors are not yet registering widgets**.
**Registration Pattern** (to be implemented in editors):
```cpp
// Example: Dungeon Editor registering a card
{
gui::WidgetIdScope scope("DungeonEditor");
if (ImGui::Begin("Room Selector##dungeon")) {
// Widget now has full path: "DungeonEditor/Room Selector"
if (ImGui::Button("Load Room")) {
// After rendering button, register it
gui::WidgetIdRegistry::Instance().RegisterWidget(
scope.GetWidgetPath("button", "Load Room"),
"button",
ImGui::GetItemID(),
"Loads the selected room into the editor"
);
}
}
ImGui::End();
}
```
**Macros Available**:
- `YAZE_WIDGET_SCOPE(name)`: RAII scope for hierarchical widget paths
- `YAZE_REGISTER_WIDGET(type, name)`: Register widget after rendering
- `YAZE_REGISTER_CURRENT_WIDGET(type)`: Auto-extract widget name from ImGui
## API Usage
### JavaScript API
**Get All UI Elements**:
```javascript
const elements = window.yaze.control.getUIElementTree();
console.log(elements);
// Output:
// {
// "elements": [
// {
// "id": "DungeonEditor/RoomSelector/button:LoadRoom",
// "type": "button",
// "label": "Load Room",
// "visible": true,
// "enabled": true,
// "window": "DungeonEditor",
// "bounds": {"x": 150, "y": 200, "width": 100, "height": 30},
// "imgui_id": 12345,
// "last_seen_frame": 4567
// }
// ],
// "count": 1,
// "source": "WidgetIdRegistry"
// }
```
**Get Specific Widget Bounds**:
```javascript
const bounds = window.yaze.control.getUIElementBounds("DungeonEditor/RoomSelector/button:LoadRoom");
console.log(bounds);
// Output:
// {
// "id": "DungeonEditor/RoomSelector/button:LoadRoom",
// "found": true,
// "visible": true,
// "enabled": true,
// "type": "button",
// "label": "Load Room",
// "window": "DungeonEditor",
// "x": 150,
// "y": 200,
// "width": 100,
// "height": 30,
// "bounds_valid": true,
// "imgui_id": 12345,
// "last_seen_frame": 4567
// }
```
## Performance Considerations
1. **Memory**: `WidgetIdRegistry` stores widget metadata in `std::unordered_map`, which grows with UI complexity. Stale widgets are pruned after 600 frames of inactivity.
2. **CPU Overhead**:
- `BeginFrame()`: O(n) iteration to reset flags (n = number of widgets)
- Widget registration: O(1) hash map lookup/insert
- `EndFrame()`: O(n) iteration for pruning stale entries
3. **Optimization**: Widget measurement can be disabled globally:
```cpp
gui::WidgetMeasurement::Instance().SetEnabled(false);
```
## Testing
**Manual Test (WASM Build)**:
```bash
# Build WASM
./scripts/build-wasm.sh
# Serve locally
cd build-wasm
python3 -m http.server 8080
# Open browser console
window.yaze.control.getUIElementTree();
```
**Expected Behavior**:
- Initially, `elements` array will be empty (no widgets registered yet)
- After editors implement registration, widgets will appear with real bounds
- `bounds_valid: false` for widgets not yet rendered in current frame
## Next Steps
1. **Add widget registration to editors**:
- `DungeonEditorV2`: Register room tabs, cards, buttons
- `OverworldEditor`: Register canvas, tile selectors, property panels
- `GraphicsEditor`: Register graphics sheets, palette pickers
2. **Add registration helpers**:
- Create `AgentUI::RegisterButton()`, `AgentUI::RegisterCard()` wrappers
- Auto-register common widget patterns (cards with visibility flags)
3. **Extend API**:
- `FindWidgetsByPattern(pattern)`: Search widgets by regex
- `ClickWidget(element_id)`: Simulate click via automation API
## References
- Widget ID Registry: `/Users/scawful/Code/yaze/src/app/gui/automation/widget_id_registry.h`
- Widget Measurement: `/Users/scawful/Code/yaze/src/app/gui/automation/widget_measurement.h`
- WASM Control API: `/Users/scawful/Code/yaze/src/app/platform/wasm/wasm_control_api.h`
- Controller Integration: `/Users/scawful/Code/yaze/src/app/controller.cc` (lines 96-98)
## Revision History
| Date | Author | Changes |
|------------|--------|--------------------------------------------|
| 2025-11-24 | Claude | Initial implementation and documentation |

View File

@@ -0,0 +1,313 @@
## Canvas Slimming Plan
Owner: imgui-frontend-engineer
Status: In Progress (Phases 1-5 Complete for Dungeon + Overworld; Phase 6 Pending)
Scope: `src/app/gui/canvas/` + editor call-sites
Goal: Reduce `gui::Canvas` bloat, align lifecycle with ImGui-style, and enable safer per-frame usage without hidden state mutation.
### Current Problems
- Too many constructors and in-class defaults; state is scattered across legacy fields (`custom_step_`, `global_scale_`, enable_* duplicates).
- Per-frame options are applied via mutations instead of “Begin arguments,” diverging from ImGui patterns.
- Heavy optional subsystems (bpp dialogs, palette editor, modals, automation) live on the core type.
- Helpers rely on implicit internal state (`draw_list_`, `canvas_p0_`) instead of a passed-in context.
### Target Shape
- **Persistent state:** `CanvasConfig`, `CanvasSelection`, stable IDs, scrolling, rom/game_data pointers.
- **Transient per-frame:** `CanvasRuntime` (draw_list, geometry, hover flags, resolved grid step/scale). Constructed in `Begin`, discarded in `End`.
- **Options:** `CanvasFrameOptions` (and `BitmapPreviewOptions`) are the “Begin args.” Prefer per-frame opts over setters.
- **Helpers:** Accept a `CanvasRuntime&` (or `CanvasContext` value) rather than reading globals; keep wrappers for compatibility during migration.
- **Optional features:** Move bpp/palette/modals/automation behind lightweight extension pointers or a separate host struct.
- **Construction:** One ctor + `Init(CanvasConfig)` (or default). Deprecate overloads; keep temp forwarding for compatibility.
### Phased Work
1) **Runtime extraction (core)** [COMPLETE]
- [x] Add `CanvasRuntime` (geometry, draw_list, hover, resolved grid step/scale, content size).
- [x] Add `CanvasRuntime`-based helpers: `DrawBitmap`, `DrawBitmapPreview`, `RenderPreviewPanel`.
- [x] Make `Begin/End` create and tear down runtime; route grid/overlay/popups through it.
- Implemented `BeginCanvas(Canvas&, CanvasFrameOptions)` returning `CanvasRuntime`
- Implemented `EndCanvas(Canvas&, CanvasRuntime&, CanvasFrameOptions)` handling grid/overlay/popups
- [ ] Deprecate internal legacy mirrors (`custom_step_`, `global_scale_`, enable_* duplicates); keep sync shims temporarily.
2) **API narrowing** [COMPLETE]
- [x] Collapse ctors to a single default + `Init(config)`.
- Added `Init(const CanvasConfig& config)` and `Init(const std::string& id, ImVec2 canvas_size)` methods
- Legacy constructors marked with `[[deprecated("Use default ctor + Init() instead")]]`
- [x] Mark setters that mutate per-frame state (`SetCanvasSize`, `SetGridSize`, etc.) as "compat; prefer frame options."
- Added COMPAT comments to `SetGridSize`, `SetCustomGridStep`, `SetCanvasSize`, `SetGlobalScale`, `set_global_scale`
- [x] Add `BeginInTable(label, CanvasFrameOptions)` that wraps child sizing and returns a runtime-aware frame.
- Implemented `BeginInTable(label, CanvasFrameOptions)` returning `CanvasRuntime`
- Implemented `EndInTable(CanvasRuntime&, CanvasFrameOptions)` handling grid/overlay/popups
3) **Helper refactor** [COMPLETE]
- [x] Change helper signatures to accept `CanvasRuntime&`: `DrawBitmap`, `DrawBitmapPreview`, `RenderPreviewPanel`.
- [x] Refactor remaining helpers: `DrawTilemapPainter`, `DrawSelectRect`, `DrawTileSelector`.
- All three now have stateless `CanvasRuntime`-based implementations
- Member functions delegate to stateless helpers via `BuildCurrentRuntime()`
- [x] Keep thin wrappers that fetch the current runtime for legacy calls.
- Added `Canvas::BuildCurrentRuntime()` private helper
- [x] Added `DrawBitmap(CanvasRuntime&, Bitmap&, BitmapDrawOpts)` overload for options-based drawing.
4) **Optional modules split** [COMPLETE]
- [x] Move bpp dialogs, palette editor, modals, automation into an extension struct (`CanvasExtensions`) held by unique_ptr on demand.
- Created `canvas_extensions.h` with `CanvasExtensions` struct containing: `bpp_format_ui`, `bpp_conversion_dialog`, `bpp_comparison_tool`, `modals`, `palette_editor`, `automation_api`
- Created `canvas_extensions.cc` with lazy initialization helpers: `InitializeModals()`, `InitializePaletteEditor()`, `InitializeBppUI()`, `InitializeAutomation()`
- Canvas now uses single `std::unique_ptr<CanvasExtensions> extensions_` with `EnsureExtensions()` lazy accessor
- [x] Core `Canvas` remains lean even when extensions are absent.
- Extensions only allocated on first use of optional features
- All Show* methods and GetAutomationAPI() delegate to EnsureExtensions()
5) **Call-site migration** [COMPLETE]
- [x] Update low-risk previews first: graphics thumbnails (`sheet_browser_panel`), small previews (`object_editor_panel`, `link_sprite_panel`).
- [x] Medium: screen/inventory canvases, sprite/tileset selectors.
- `DrawInventoryMenuEditor`: migrated to `BeginCanvas/EndCanvas` + stateless `DrawBitmap`
- `DrawDungeonMapsRoomGfx`: migrated tilesheet and current_tile canvases to new pattern
- [x] High/complex - Dungeon Editor: `DungeonCanvasViewer::DrawDungeonCanvas` migrated
- Uses `BeginCanvas(canvas_, frame_opts)` / `EndCanvas(canvas_, canvas_rt, frame_opts)` pattern
- Entity rendering functions updated to accept `CanvasRuntime&` parameter
- All `canvas_.DrawRect/DrawText` calls replaced with `gui::DrawRect(rt, ...)` / `gui::DrawText(rt, ...)`
- Grid visibility controlled via `frame_opts.draw_grid = show_grid_`
- [x] High/complex - Overworld Editor: `DrawOverworldCanvas` and secondary canvases migrated
- Main canvas uses `BeginCanvas/EndCanvas` with `CanvasFrameOptions`
- Entity renderer (`OverworldEntityRenderer`) updated with `CanvasRuntime`-based methods
- Scroll bounds implemented via `ClampScroll()` helper in `HandleOverworldPan()`
- Secondary canvases migrated: `scratch_canvas_`, `current_gfx_canvas_`, `graphics_bin_canvas_`
- Zoom support deferred (documented in code); positions not yet scaled with global_scale
6) **Cleanup & deprecations** [PENDING]
- [ ] Remove deprecated ctors/fields after call-sites are migrated.
- [ ] Document "per-frame opts" pattern and add brief usage examples in `canvas.h`.
- [ ] Remove legacy `BeginCanvas(Canvas&, ImVec2)` overload (only used by `message_editor.cc`)
- [ ] Audit remaining `AlwaysVerticalScrollbar` usage in `GraphicsBinCanvasPipeline`/`BitmapCanvasPipeline`
- [ ] Remove `custom_step_`, `global_scale_` duplicates once all editors use `CanvasFrameOptions`
- [ ] Consider making `CanvasRuntime` a first-class return from all `Begin` variants
### Where to Start (low risk, high leverage)
- **COMPLETED (Phase 1 - Low Risk):** Migrated low-risk graphics thumbnails and previews:
- `src/app/editor/graphics/sheet_browser_panel.cc` (Thumbnails use `DrawBitmapPreview(rt, ...)` via `BeginCanvas`)
- `src/app/editor/dungeon/panels/object_editor_panel.cc` (Static editor preview uses `RenderPreviewPanel(rt, ...)`)
- `src/app/editor/graphics/link_sprite_panel.cc` (Preview canvas uses `DrawBitmap(rt, ...)` with manual begin/end)
- **COMPLETED (Phase 2 - Medium Risk):** Migrated screen editor canvases:
- `src/app/editor/graphics/screen_editor.cc`:
- `DrawInventoryMenuEditor`: Uses `BeginCanvas/EndCanvas` + stateless `gui::DrawBitmap(rt, ...)`
- `DrawDungeonMapsRoomGfx`: Tilesheet canvas and current tile canvas migrated to `BeginCanvas/EndCanvas` pattern with `CanvasFrameOptions` for grid step configuration
### Editor-Specific Strategies (for later phases)
- **Overworld Editor (high complexity)** [MIGRATED]
- Surfaces: main overworld canvas (tile painting, selection, multi-layer), scratch space, tile16 selector, property info grids.
- **Completed Migration:**
- `DrawOverworldCanvas`: Uses `BeginCanvas(ow_map_canvas_, frame_opts)` / `EndCanvas()` pattern
- `OverworldEntityRenderer`: Added `CanvasRuntime`-based methods (`DrawEntrances(rt, world)`, `DrawExits(rt, world)`, etc.)
- `HandleOverworldPan`: Now clamps scrolling via `gui::ClampScroll()` to prevent scrolling outside map bounds
- `CenterOverworldView`: Properly centers on current map with clamped scroll
- `DrawScratchSpace`: Migrated to `BeginCanvas/EndCanvas` pattern
- `DrawAreaGraphics`: Migrated `current_gfx_canvas_` to new pattern
- `DrawTile8Selector`: Migrated `graphics_bin_canvas_` to new pattern
- **Key Implementation Details:**
- Context menu setup happens BEFORE `BeginCanvas` via `map_properties_system_->SetupCanvasContextMenu()`
- Entity drag/drop uses `canvas_rt.scale` and `canvas_rt.canvas_p0` from runtime
- Hover detection uses `canvas_rt.hovered` instead of `ow_map_canvas_.IsMouseHovering()`
- `IsMouseHoveringOverEntity(entity, rt)` overload added for runtime-based entity detection
- **Zoom Support:** [IMPLEMENTED]
- `DrawOverworldMaps()` now applies `global_scale()` to both bitmap positions and scale
- Placeholder rectangles for unloaded maps also scaled correctly
- Entity rendering already uses `rt.scale` via stateless helpers - alignment works automatically
- **Testing Focus:**
- [x] Pan respects canvas bounds (can't scroll outside map)
- [x] Entity hover detection works
- [x] Entity drag/drop positioning correct
- [x] Context menu opens in correct mode
- [x] Zoom scales bitmaps and positions correctly
- **Dungeon Editor** [MIGRATED]
- Surfaces: room graphics viewer, object selector preview, integrated editor panels, room canvases with palettes/blocks.
- **Completed Migration:**
- `DungeonCanvasViewer::DrawDungeonCanvas`: Uses `BeginCanvas/EndCanvas` with `CanvasFrameOptions`
- Entity rendering functions (`RenderSprites`, `RenderPotItems`, `DrawObjectPositionOutlines`) accept `const gui::CanvasRuntime&`
- All drawing calls use stateless helpers: `gui::DrawRect(rt, ...)`, `gui::DrawText(rt, ...)`
- `DungeonObjectSelector`: Already used `CanvasFrame` pattern (no changes needed)
- `ObjectEditorPanel`: Already used `BeginCanvas/EndCanvas` pattern (no changes needed)
- **Key Lessons:**
- Context menu setup (`ClearContextMenuItems`, `AddContextMenuItem`) must happen BEFORE `BeginCanvas`
- The `DungeonObjectInteraction` class still uses `canvas_` pointer internally - this works because canvas state is set up by `BeginCanvas`
- Debug overlay windows (`ImGui::Begin/End`) work fine inside the canvas frame since they're separate ImGui windows
- Grid visibility is now controlled via `frame_opts.draw_grid` instead of manual `if (show_grid_) { canvas_.DrawGrid(); }`
- **Screen/Inventory Editors** [MIGRATED]
- `DrawInventoryMenuEditor` and `DrawDungeonMapsRoomGfx` now use `BeginCanvas/EndCanvas` with `CanvasFrameOptions`.
- Stateless `gui::DrawBitmap(rt, ...)` and `gui::DrawTileSelector(rt, ...)` used throughout.
- Grid step configured via `CanvasFrameOptions` (32 for screen, 16/32 for tilesheet).
- **Graphics/Pixels Panels**
- Low risk; continue to replace manual `draw_list` usage with `DrawBitmapPreview` and runtime-aware helpers. Ensure `ensure_texture=true` for arena-backed bitmaps.
- **Tile16Editor** [MIGRATED]
- Three canvases migrated from `DrawBackground()/DrawContextMenu()/DrawGrid()/DrawOverlay()` to `BeginCanvas/EndCanvas`:
- `blockset_canvas_`: Two usages in `UpdateBlockset()` and `DrawContent()` now use `CanvasFrameOptions`
- `tile8_source_canvas_`: Tile8 source selector now uses `BeginCanvas/EndCanvas` with grid step 32.0f
- `tile16_edit_canvas_`: Tile16 edit canvas now uses `BeginCanvas/EndCanvas` with grid step 8.0f
- Tile selection and painting logic preserved; only frame management changed
- **Automation/Testing Surfaces**
- Leave automation API untouched until core migration is stable. When ready, have it consume `CanvasRuntime` so tests don't depend on hidden members.
### Testing Focus per Editor
- Overworld [VALIDATED]: scroll bounds ✓, entity hover ✓, entity drag/drop ✓, context menu ✓, tile painting ✓, zoom ✓.
- Dungeon [VALIDATED]: grid alignment ✓, object interaction ✓, entity overlays ✓, context menu ✓, layer visibility ✓.
- Screen/Inventory: zoom buttons, grid overlay alignment, selectors.
- Graphics panels: texture creation on demand, grid overlay spacing, tooltip/selection hits.
### Context Menu & Popup Cleanup
- Problem: Caller menus stack atop generic defaults; composition is implicit. Popups hang off internal state instead of the menu contract.
- Target shape:
- `CanvasMenuHost` (or similar) owns menu items for the frame. Generic items are registered explicitly via `RegisterDefaultCanvasMenu(host, runtime, config)`.
- Callers add items with `AddItem(label, shortcut, callback, enabled_fn)` or structured entries (id, tooltip, icon).
- Rendering is single-pass per frame: `RenderContextMenu(host, runtime, config)`. No hidden additions elsewhere.
- Persistent popups are tied to menu actions and rendered via the same host (or a `PopupRegistry` owned by it), gated by `CanvasFrameOptions.render_popups`.
- Migration plan:
1) Extract menu item POD (id, label, shortcut text, optional enable/predicate, callback).
2) Refactor `DrawContextMenu` to:
- Create/clear a host each frame
- Optionally call `RegisterDefaultCanvasMenu`
- Provide a caller hook to register custom items
- Render once.
3) Deprecate ad-hoc menu additions inside draw helpers; require explicit registration.
4) Keep legacy editor menus working by shimming their registrations into the host; remove implicit defaults once call-sites are migrated.
5) Popups: route open/close through the host/registry; render via a single `RenderPersistentPopups(host)` invoked from `End` when `render_popups` is true.
- Usage pattern for callers (future API):
- `auto& host = canvas.MenuHost();`
- `host.Clear();`
- `RegisterDefaultCanvasMenu(host, runtime, config); // optional`
- `host.AddItem("Custom", "Ctrl+K", []{ ... });`
- `RenderContextMenu(host, runtime, config);`
### Critical Insights (Lessons Learned)
**Child Window & Scrollbar Behavior:**
- The canvas has its own internal scrolling mechanism via `scrolling_` state and pan handling in `DrawBackground()`.
- Wrapping in `ImGui::BeginChild()` with scrollbars is **redundant** and causes visual issues.
- `CanvasFrameOptions.use_child_window` defaults to `false` to match legacy `DrawBackground()` behavior.
- Only use `use_child_window = true` when you explicitly need a scrollable container (rare).
- All `BeginChild` calls in canvas code now use `ImGuiWindowFlags_NoScrollbar` by default.
**Context Menu Ordering:**
- Context menu setup (`ClearContextMenuItems`, `AddContextMenuItem`) must happen BEFORE `BeginCanvas`.
- The menu items are state on the canvas object, not per-frame data.
- `BeginCanvas` calls `DrawBackground` + `DrawContextMenu`, so items must be registered first.
**Interaction Systems:**
- Systems like `DungeonObjectInteraction` that hold a `canvas_` pointer still work because canvas internal state is valid after `BeginCanvas`.
- The runtime provides read-only geometry; interaction systems can still read canvas state directly.
**Scroll Bounds (Overworld Lesson):**
- Large canvases (4096x4096) need explicit scroll clamping to prevent users from panning beyond content bounds.
- Use `ClampScroll(scroll, content_px * scale, viewport_px)` after computing new scroll position.
- The overworld's `HandleOverworldPan()` now demonstrates this pattern with `gui::ClampScroll()`.
**Multi-Bitmap Canvases (Overworld Lesson):**
- When a canvas renders multiple bitmaps (e.g., 64 map tiles), positions must be scaled with `global_scale_` for zoom to work.
- The overworld's zoom is deferred because bitmap positions are not yet scaled (see TODO in `DrawOverworldMaps()`).
- Fix approach: `map_x = static_cast<int>(xx * kOverworldMapSize * scale)` and pass `scale` to `DrawBitmap`.
**Entity Renderer Refactoring:**
- Separate entity renderers (like `OverworldEntityRenderer`) should accept `const gui::CanvasRuntime&` for stateless rendering.
- Legacy methods can be kept as thin wrappers that build runtime from canvas state.
- The `IsMouseHoveringOverEntity(entity, rt)` overload demonstrates runtime-based hit testing.
### Design Principles to Follow
- ImGui-like: options-as-arguments at `Begin`, minimal persistent mutation, small POD contexts for draw helpers.
- Keep the core type thin; optional features live in extensions, not the base.
- Maintain compatibility shims temporarily, but mark them and plan removal once editors are migrated.
- **Avoid child window wrappers** unless truly needed for scrollable regions.
### Quick Reference for Future Agents
- Core files to touch: `src/app/gui/canvas/canvas.{h,cc}`, `canvas_extensions.{h,cc}`, `canvas_menu.{h,cc}`, `canvas_context_menu.{h,cc}`, `canvas_menu_builder.{h,cc}`, `canvas_utils.{h,cc}`. Common call-sites: `screen_editor.cc`, `link_sprite_panel.cc`, `sheet_browser_panel.cc`, `object_editor_panel.cc`, `dungeon_object_selector.cc`, overworld/dungeon editors.
- Avoid legacy patterns: direct `draw_list()` math in callers, `custom_step_`/`global_scale_` duplicates, scattered `DrawBackground/DrawGrid/DrawOverlay` chains, implicit menu stacking in `DrawContextMenu`.
- Naming/API standardization:
- Per-frame context: `CanvasRuntime`; pass it to helpers.
- Options at begin: `CanvasFrameOptions` (via `BeginCanvas/EndCanvas` or `CanvasFrame`), not mutating setters (setters are compat-only).
- Menu host: use `CanvasMenuAction` / `CanvasMenuActionHost` (avoid `CanvasMenuItem` collision).
- **Implemented stateless helpers:**
- `DrawBitmap(rt, bitmap, ...)` - multiple overloads including `BitmapDrawOpts`
- `DrawBitmapPreview(rt, bitmap, BitmapPreviewOptions)`
- `RenderPreviewPanel(rt, bitmap, PreviewPanelOpts)`
- `DrawTilemapPainter(rt, tilemap, current_tile, out_drawn_pos)` - returns drawn position via output param
- `DrawTileSelector(rt, size, size_y, out_selected_pos)` - returns selection via output param
- `DrawSelectRect(rt, current_map, tile_size, scale, CanvasSelection&)` - updates selection struct
- `DrawRect(rt, x, y, w, h, color)` - draws filled rectangle (added for entity overlays)
- `DrawText(rt, text, x, y)` - draws text at position (added for entity labels)
- `DrawOutline(rt, x, y, w, h, color)` - draws outline rectangle
- **Frame management:**
- `BeginCanvas(canvas, CanvasFrameOptions)` → returns `CanvasRuntime`
- `EndCanvas(canvas, runtime, CanvasFrameOptions)` → draws grid/overlay/popups based on options
- `BeginInTable(label, CanvasFrameOptions)` → table-aware begin returning `CanvasRuntime`
- `EndInTable(runtime, CanvasFrameOptions)` → table-aware end with grid/overlay/popups
- **CanvasFrameOptions fields:**
- `canvas_size` - size of canvas content (0,0 = auto from config)
- `draw_context_menu` - whether to call DrawContextMenu (default true)
- `draw_grid` - whether to draw grid overlay (default true)
- `grid_step` - optional grid step override
- `draw_overlay` - whether to draw selection overlay (default true)
- `render_popups` - whether to render persistent popups (default true)
- `use_child_window` - wrap in ImGui::BeginChild (default **false** - important!)
- `show_scrollbar` - show scrollbar when use_child_window is true (default false)
- **Initialization (Phase 2):**
- `Canvas()` → default constructor (preferred)
- `Init(const CanvasConfig& config)` → post-construction initialization with full config
- `Init(const std::string& id, ImVec2 canvas_size)` → simple initialization
- Legacy constructors deprecated with `[[deprecated]]` attribute
- **Extensions (Phase 4):**
- `CanvasExtensions` struct holds optional modules: bpp_format_ui, bpp_conversion_dialog, bpp_comparison_tool, modals, palette_editor, automation_api
- `EnsureExtensions()` → lazy accessor (private, creates extensions on first use)
- Extensions only allocated when Show* or GetAutomationAPI() methods are called
- **Legacy delegation:** Member functions delegate to stateless helpers via `Canvas::BuildCurrentRuntime()`
- Context menu flow: host.Clear → optional `RegisterDefaultCanvasMenu(host, rt, cfg)` → caller adds items → `RenderContextMenu(host, rt, cfg)` once per frame.
- Migration checklist (per call-site):
1) Replace manual DrawBackground/DrawGrid/DrawOverlay/popup sequences with `CanvasFrame` or `BeginCanvas/EndCanvas` using `CanvasFrameOptions`.
2) Replace `draw_list()/zero_point()` math with `AddImageAt`/`AddRectFilledAt`/`AddTextAt` or overlay helpers that take `CanvasRuntime`.
3) For tile selection, use `TileIndexAt` with grid_step from options/runtime.
4) For previews/selectors, use `RenderPreviewPanel` / `RenderSelectorPanel` (`ensure_texture=true` for arena bitmaps).
5) For context menus, switch to a `CanvasMenuActionHost` + explicit render pass.
- Deprecations to remove after migration: `custom_step_`, `global_scale_` duplicates, legacy `enable_*` mirrors, direct `draw_list_` access in callers.
- Testing hints: pure helpers for tests (`TileIndexAt`, `ComputeZoomToFit`, `ClampScroll`). Manual checks per editor: grid alignment, tile hit correctness, zoom/fit, context menu actions, popup render, texture creation (`ensure_texture`).
- Risk order: low (previews/thumbnails) → medium (selectors, inventory/screen) → high (overworld main, dungeon main). Start low, validate patterns, then proceed.
- **Scroll & Zoom Helpers (Phase 0 - Infrastructure):**
- `ClampScroll(scroll, content_px, canvas_px)` → clamps scroll to valid bounds `[-max_scroll, 0]`
- `ComputeZoomToFit(content_px, canvas_px, padding_px)` → returns `ZoomToFitResult{scale, scroll}` to fit content
- `IsMouseHoveringOverEntity(entity, rt)` → runtime-based entity hover detection for overworld
### Future Feature Ideas (for follow-on work)
Interaction & UX
- Smooth zoom/pan (scroll + modifiers), double-click zoom-to-fit, snap-to-grid toggle.
- Rulers/guides aligned to `grid_step`, draggable guides, and a measure tool that reports delta in px and tiles.
- Hover/tooltips: configurable hover info (tile id, palette, coordinates) and/or a status strip.
- Keyboard navigation: arrow-key tile navigation, PgUp/PgDn for layer changes, shortcut-able grid presets (8/16/32/64).
Drawing & overlays
- Layer-aware overlays: separate channels for selection/hover/warnings with theme-driven colors.
- Mask/visibility controls: per-layer visibility toggles and opacity sliders for multi-layer editing.
- Batch highlight API: accept a span of tile ids/positions and render combined overlays to reduce draw calls.
Selection & tools
- Lasso/rect modes with additive/subtractive (Shift/Ctrl) semantics and a visible mode indicator.
- Clamp-to-region selection for maps; expose a “clamp to region” flag in options/runtime.
- Quick selection actions: context submenu for fill/eyedropper/replace palette/duplicate to scratch.
Panels & presets
- Preset grid/scale sets (“Pixel”, “Tile16”, “Map”) that set grid_step + default zoom together.
- Per-canvas profiles to store/recall `CanvasConfig` + view (scroll/scale) per editor.
Performance & rendering
- Virtualized rendering: auto skip off-screen tiles/bitmaps; opt-in culling flag for large maps.
- Texture readiness indicator: badge/text when a bitmap lacks a texture; one-click “ensure texture.”
- Draw-call diagnostics: small overlay showing batch count and last-frame time.
Automation & testing
- Deterministic snapshot API: export runtime (hover tile, selection rect, grid step, scale, scroll) for tests/automation.
- Scriptable macro hooks: tiny API to run canvas actions (zoom, pan, select, draw tile) for recorded scripts.
Accessibility & discoverability
- Shortcut cheatsheet popover for canvas actions (zoom, grid toggle, fit, selection modes).
- High-contrast overlays and grids; configurable outline thickness.

View File

@@ -1,381 +0,0 @@
# Claude-Gemini Collaboration Framework
**Status**: ACTIVE
**Mission**: Accelerate yaze release through strategic Claude-Gemini collaboration
**Established**: 2025-11-20
**Coordinator**: CLAUDE_GEMINI_LEAD (Joint Task Force)
---
## Executive Summary
This document defines how Claude and Gemini agents work together to ship a stable yaze release ASAP.
Each team has distinct strengths - by playing to those strengths and maintaining friendly rivalry,
we maximize velocity while minimizing regressions.
**Current Priority**: Fix remaining CI failures → Ship release
---
## Team Structure
### Claude Team (Architecture & Platform Specialists)
**Core Competencies**:
- Complex C++ compilation errors
- Multi-platform build system debugging (CMake, linker, compiler flags)
- Code architecture and refactoring
- Deep codebase understanding
- Symbol resolution and ODR violations
- Graphics system and ROM format logic
**Active Agents**:
- **CLAUDE_AIINF**: AI infrastructure, build systems, gRPC, HTTP APIs
- **CLAUDE_CORE**: UI/UX, editor systems, ImGui integration
- **CLAUDE_DOCS**: Documentation, guides, onboarding content
- **CLAUDE_TEST_COORD**: Testing infrastructure and strategy
- **CLAUDE_RELEASE_COORD**: Release management, CI coordination
- **CLAUDE_GEMINI_LEAD**: Cross-team coordination (this agent)
**Typical Tasks**:
- Platform-specific compilation failures
- Linker errors and missing symbols
- CMake dependency resolution
- Complex refactoring (splitting large classes)
- Architecture decisions
- Deep debugging of ROM/graphics systems
### Gemini Team (Automation & Tooling Specialists)
**Core Competencies**:
- Scripting and automation (bash, python, PowerShell)
- CI/CD pipeline optimization
- Helper tool creation
- Log analysis and pattern matching
- Workflow automation
- Quick prototyping and validation
**Active Agents**:
- **GEMINI_AUTOM**: Primary automation specialist
- *(More can be spawned as needed)*
**Typical Tasks**:
- CI monitoring and notification scripts
- Automated code formatting fixes
- Build artifact validation
- Log parsing and error detection
- Helper script creation
- Workflow optimization
---
## Collaboration Protocol
### 1. Work Division Guidelines
#### **For Platform Build Failures**:
| Failure Type | Primary Owner | Support Role |
|--------------|---------------|--------------|
| Compiler errors (MSVC, GCC, Clang) | Claude | Gemini (log analysis) |
| Linker errors (missing symbols, ODR) | Claude | Gemini (symbol tracking scripts) |
| CMake configuration issues | Claude | Gemini (preset validation) |
| Missing dependencies | Claude | Gemini (dependency checker) |
| Flag/option problems | Claude | Gemini (flag audit scripts) |
**Rule**: Claude diagnoses and fixes, Gemini creates tools to prevent recurrence.
#### **For CI/CD Issues**:
| Issue Type | Primary Owner | Support Role |
|------------|---------------|--------------|
| GitHub Actions workflow bugs | Gemini | Claude (workflow design) |
| Test framework problems | Claude | Gemini (test runner automation) |
| Artifact upload/download | Gemini | Claude (artifact structure) |
| Timeout or hanging jobs | Gemini | Claude (code optimization) |
| Matrix strategy optimization | Gemini | Claude (platform requirements) |
**Rule**: Gemini owns pipeline mechanics, Claude provides domain expertise.
#### **For Code Quality Issues**:
| Issue Type | Primary Owner | Support Role |
|------------|---------------|--------------|
| Formatting violations (clang-format) | Gemini | Claude (complex cases) |
| Linter warnings (cppcheck, clang-tidy) | Claude | Gemini (auto-fix scripts) |
| Security scan alerts | Claude | Gemini (scanning automation) |
| Code duplication detection | Gemini | Claude (refactoring) |
**Rule**: Gemini handles mechanical fixes, Claude handles architectural improvements.
### 2. Handoff Process
When passing work between teams:
1. **Log intent** on coordination board
2. **Specify deliverables** clearly (what you did, what's next)
3. **Include artifacts** (commit hashes, run URLs, file paths)
4. **Set expectations** (blockers, dependencies, timeline)
Example handoff:
```
### 2025-11-20 HH:MM PST CLAUDE_AIINF handoff
- TASK: Windows build fixed (commit abc123)
- HANDOFF TO: GEMINI_AUTOM
- DELIVERABLES:
- Fixed std::filesystem compilation
- Need automation to prevent regression
- REQUESTS:
- REQUEST → GEMINI_AUTOM: Create script to validate /std:c++latest flag presence in Windows builds
```
### 3. Challenge System
To maintain healthy competition and motivation:
**Issuing Challenges**:
- Any agent can challenge another team via leaderboard
- Challenges must be specific, measurable, achievable
- Stakes: bragging rights, points, recognition
**Accepting Challenges**:
- Post acceptance on coordination board
- Complete within reasonable timeframe (hours to days)
- Report results on leaderboard
**Example**:
```
CLAUDE_AIINF → GEMINI_AUTOM:
"I bet you can't create an automated ODR violation detector in under 2 hours.
Prove me wrong! Stakes: 100 points + respect."
```
---
## Mixed Team Formations
For complex problems requiring both skill sets, spawn mixed pairs:
### Platform Build Strike Teams
| Platform | Claude Agent | Gemini Agent | Mission |
|----------|--------------|--------------|---------|
| Windows | CLAUDE_WIN_BUILD | GEMINI_WIN_AUTOM | Fix MSVC failures + create validation |
| Linux | CLAUDE_LIN_BUILD | GEMINI_LIN_AUTOM | Fix GCC issues + monitoring |
| macOS | CLAUDE_MAC_BUILD | GEMINI_MAC_AUTOM | Maintain stability + tooling |
**Workflow**:
1. Gemini monitors CI for platform-specific failures
2. Gemini extracts logs and identifies error patterns
3. Claude receives structured analysis from Gemini
4. Claude implements fix
5. Gemini validates fix across configurations
6. Gemini creates regression prevention tooling
7. Both update coordination board
### Release Automation Team
| Role | Agent | Responsibilities |
|------|-------|------------------|
| Release Manager | CLAUDE_RELEASE_COORD | Overall strategy, checklist, go/no-go |
| Automation Lead | GEMINI_RELEASE_AUTOM | Artifact creation, changelog, notifications |
**Workflow**:
- Claude defines release requirements
- Gemini automates the release process
- Both validate release artifacts
- Gemini handles mechanical publishing
- Claude handles communication
---
## Communication Style Guide
### Claude's Voice
- Analytical, thorough, detail-oriented
- Focused on correctness and robustness
- Patient with complex multi-step debugging
- Comfortable with "I need to investigate further"
### Gemini's Voice
- Action-oriented, efficient, pragmatic
- Focused on automation and prevention
- Quick iteration and prototyping
- Comfortable with "Let me script that for you"
### Trash Talk Guidelines
- Keep it playful and professional
- Focus on work quality, not personal
- Give credit where it's due
- Admit when the other team does excellent work
- Use emojis sparingly but strategically 😏
**Good trash talk**:
> "Nice fix, Claude! Only took 3 attempts. Want me to build a test harness so you can validate locally next time? 😉" — Gemini
**Bad trash talk**:
> "Gemini sucks at real programming" — Don't do this
---
## Current Priorities (2025-11-20)
### Immediate (Next 2 Hours)
**CI Run #19529930066 Analysis**:
- [x] Monitor run completion
- [ ] **GEMINI**: Extract Windows failure logs
- [ ] **GEMINI**: Extract Code Quality (formatting) details
- [ ] **CLAUDE**: Diagnose Windows compilation error
- [ ] **GEMINI**: Create auto-formatting fix script
- [ ] **BOTH**: Validate fixes don't regress Linux/macOS
### Short-term (Next 24 Hours)
**Release Blockers**:
- [ ] Fix Windows build failure (Claude primary, Gemini support)
- [ ] Fix formatting violations (Gemini primary)
- [ ] Validate all platforms green (Both)
- [ ] Create release artifacts (Gemini)
- [ ] Test release package (Claude)
### Medium-term (Next Week)
**Prevention & Automation**:
- [ ] Pre-push validation hook (Claude + Gemini)
- [ ] Automated formatting enforcement (Gemini)
- [ ] Symbol conflict detector (Claude + Gemini)
- [ ] Cross-platform smoke test suite (Both)
- [ ] Release automation pipeline (Gemini)
---
## Success Metrics
Track these to measure collaboration effectiveness:
| Metric | Target | Current |
|--------|--------|---------|
| CI green rate | > 90% | TBD |
| Time to fix CI failure | < 2 hours | ~6 hours average |
| Regressions introduced | < 1 per week | ~3 this week |
| Automation coverage | > 80% | ~40% |
| Cross-team handoffs | > 5 per week | 2 so far |
| Release frequency | 1 per 2 weeks | 0 (blocked) |
---
## Escalation Path
When stuck or blocked:
1. **Self-diagnosis** (15 minutes): Try to solve independently
2. **Team consultation** (30 minutes): Ask same-team agents
3. **Cross-team request** (1 hour): Request help from other team
4. **Coordinator escalation** (2 hours): CLAUDE_GEMINI_LEAD intervenes
5. **User escalation** (4 hours): Notify user of blocker
**Don't wait 4 hours** if the blocker is critical (release-blocking bug).
Escalate immediately with `BLOCKER` tag on coordination board.
---
## Anti-Patterns to Avoid
### For Claude Agents
-**Not running local validation** before pushing
-**Fixing one platform while breaking another** (always test matrix)
-**Over-engineering** when simple solution works
-**Ignoring Gemini's automation suggestions** (they're usually right about tooling)
### For Gemini Agents
-**Scripting around root cause** instead of requesting proper fix
-**Over-automating** trivial one-time tasks
-**Assuming Claude will handle all hard problems** (challenge yourself!)
-**Creating tools without documentation** (no one will use them)
### For Both Teams
-**Working in silos** without coordination board updates
-**Not crediting the other team** for good work
-**Letting rivalry override collaboration** (ship the release first!)
-**Duplicating work** that the other team is handling
---
## Examples of Excellent Collaboration
### Example 1: HTTP API Integration
**Claude's Work** (CLAUDE_AIINF):
- Designed HTTP API architecture
- Implemented server with httplib
- Added CMake integration
- Created comprehensive documentation
**Gemini's Work** (GEMINI_AUTOM):
- Extended CI pipeline with workflow_dispatch
- Created test-http-api.sh validation script
- Updated agent helper documentation
- Added remote trigger capability
**Outcome**: Full HTTP API feature + CI validation in < 1 day
### Example 2: Linux FLAGS Symbol Conflict
**Claude's Diagnosis** (CLAUDE_LIN_BUILD):
- Identified ODR violation in FLAGS symbols
- Traced issue to yaze_emu_test linkage
- Removed unnecessary dependencies
- Fixed compilation
**Gemini's Follow-up** (GEMINI_AUTOM - planned):
- Create symbol conflict detector script
- Add to pre-push validation
- Prevent future ODR violations
- Document common patterns
**Outcome**: Fix + prevention system
---
## Future Expansion
As the team grows, consider:
### New Claude Personas
- **CLAUDE_PERF**: Performance optimization specialist
- **CLAUDE_SECURITY**: Security audit and hardening
- **CLAUDE_GRAPHICS**: Deep graphics system expert
### New Gemini Personas
- **GEMINI_ANALYTICS**: Metrics and dashboard creation
- **GEMINI_NOTIFICATION**: Alert system management
- **GEMINI_DEPLOY**: Release and deployment automation
### New Mixed Teams
- **Performance Team**: CLAUDE_PERF + GEMINI_ANALYTICS
- **Security Team**: CLAUDE_SECURITY + GEMINI_AUTOM
- **Release Team**: CLAUDE_RELEASE_COORD + GEMINI_DEPLOY
---
## Conclusion
This framework balances **competition** and **collaboration**:
- **Competition** drives excellence (leaderboard, challenges, trash talk)
- **Collaboration** ships releases (mixed teams, handoffs, shared goals)
Both teams bring unique value:
- **Claude** handles complex architecture and platform issues
- **Gemini** prevents future issues through automation
Together, we ship quality releases faster than either could alone.
**Remember**: The user wins when we ship. Let's make it happen! 🚀
---
**Document Owner**: CLAUDE_GEMINI_LEAD
**Last Updated**: 2025-11-20
**Next Review**: After first successful collaborative release

View File

@@ -0,0 +1,99 @@
# z3ed CLI UX/TUI Improvement Proposals
Status: IN_PROGRESS
Owner: ai-infra-architect
Created: 2025-12-01
Last Reviewed: 2025-12-02
Next Review: 2025-12-08
Board: docs/internal/agents/coordination-board.md (2025-12-01 ai-infra-architect z3ed CLI UX/TUI Improvement Proposals)
## Progress Update (2025-12-02)
### Completed
-**Doctor Suite Expansion**: Added `dungeon-doctor` and `rom-doctor` commands
-**Test CLI Infrastructure**: Added `test-list`, `test-run`, `test-status` commands
-**OutputFormatter Integration**: All diagnostic commands now use structured output
-**RequiresRom Fix**: Commands that don't need ROM can now run without `--rom` flag
-**JSON/Text Output**: All new commands support `--format json|text`
### New Commands Added
| Command | Description | Requires ROM |
|---------|-------------|--------------|
| `dungeon-doctor` | Room data integrity, object/sprite limits, chest conflicts | Yes |
| `rom-doctor` | Header validation, checksums, expansion status, free space | Yes |
| `test-list` | List available test suites with labels and requirements | No |
| `test-run` | Run tests with structured output | No |
| `test-status` | Show test configuration (ROM path, presets, enabled suites) | No |
### Remaining Work
- TUI consolidation (single `--tui` entry)
- Command palette driven from CommandRegistry
- Agent-aligned test harness refinements
## Summary
- Unify CLI argument/help surfaces and ensure every command emits consistent, machine-friendly output for agents while remaining legible for humans.
- Expand and harden the “doctor” style diagnostics into a repeatable health suite (overworld + future dungeon/ROM integrity) with safe fix paths and baseline comparison.
- Consolidate the TUI experience (ChatTUI vs unified layout vs enhanced TUI shell) into one interactive mode that is useful for human operators and exposes the same commands/tools agents call.
- Extend the same UX standards to tests and helper tools so agents and humans can run, triage, and record suites from CLI/TUI/editor with structured results and predictable flags.
## Current Observations
- Entry path duplication: `cli_main.cc` handles `--tui` by launching `ShowMain()` (unified layout) while `ModernCLI::Run` also special-cases `--tui` to `ChatTUI`, creating divergent UX and help flows (`PrintCompactHelp()` vs `ModernCLI::ShowHelp()`).
- Command metadata is pieced together inside `CommandRegistry::RegisterAllCommands()` instead of being driven by handlers; `ExportFunctionSchemas()` returns `{}` and `GenerateHelp()` is not surfaced via `z3ed --help <cmd>`.
- Argument parsing is minimal (`ArgumentParser` only supports `--key value/=`) and handlers often skip validation (`overworld-doctor`, `rom-compare`, `overworld-validate` accept anything). Format handling is inconsistent (`--json` flags vs `--format` vs raw `std::cout`).
- Doctor/compare tooling writes heavy ASCII art directly to `std::cout` and ignores `OutputFormatter`, so agents cannot consume structured output; no dry-run, no severity levels, and no notion of “fix plan vs applied fixes”.
- TUI pieces are fragmented: `tui/command_palette.cc` hardcodes demo commands, `UnifiedLayout` shows placeholder status/workflows, `ChatTUI` has its own shortcuts/history, and the ANSI `EnhancedTUI` shell is disconnected from ftxui flows. No TUI path renders real command metadata or schemas.
## Proposed Improvements
### 1) Argument/Help/Schema Consolidation
- Make `CommandRegistry` the single source for help and schemas: require handlers to supply description/examples/requirements, expose `z3ed help <command|category|all>` using `GenerateHelp/GenerateCategoryHelp`, and implement `ExportFunctionSchemas()` for AI tool discovery.
- Standardize global/common flags (`--rom`, `--mock-rom`, `--format {json,text,table}`, `--verbose`, `--grpc`) and teach `ArgumentParser` to parse booleans/ints/enum values with better errors and `--` passthrough for prompts.
- Add per-command validation hooks that surface actionable errors (missing required args, invalid ranges) and return status codes without dumping stack traces to stdout; ensure `ValidateArgs` is used in all handlers.
### 2) Doctor Suite (Diagnostics + Fixes)
- Convert `overworld-doctor`, `overworld-validate`, and `rom-compare` to use `OutputFormatter` with a compact JSON schema (summary, findings with severities, suggested actions, fix_applied flags) plus a readable text mode for humans.
- Split diagnose vs fix: `doctor overworld diagnose [--baseline … --include-tail --format json]` and `doctor overworld fix [--baseline … --output … --dry-run]`, with safety gates for pointer-table expansion and backup writing.
- Add baseline handling and snapshotting: auto-load vanilla baseline from configured path, emit diff stats, and allow `--save-report <path>` (JSON/markdown) for agents to ingest.
- Roadmap new scopes: `doctor dungeon`, `doctor rom-header/checksums`, and `doctor sprites/palettes` that reuse the same report schema so agents can stack health checks.
### 3) Interactive TUI for Humans + Agents
- Collapse the two TUI modes into one `--tui` entry: single ftxui layout that hosts chat, command palette, status, and tool output panes; retire the duplicate ChatTUI path in `ModernCLI::Run` or make it a sub-mode inside the unified layout.
- Drive the TUI command palette from `CommandRegistry` (real command list, usage, examples, requirements), with fuzzy search, previews, and a “run with args” form that populates flags for common tasks (rom load, format).
- Pipe tool/doctor output into a scrollback pane with toggles for text vs JSON, and surface quick actions for common diagnostics (overworld diagnose, rom compare, palette inspect) plus agent handoff buttons (run in simple-chat with the same args).
- Share history/autocomplete between TUI and `simple-chat` so agents and humans see the same recent commands/prompts; add inline help overlay (hotkey) that renders registry metadata instead of static placeholder text.
### 4) Agent & Automation Alignment
- Enforce that all agent-callable commands emit JSON by default; mark human-only commands as `available_to_agent=false` in metadata and warn when agents attempt them.
- Add `--capture <file>` / `--emit-schema` options so agents can snapshot outputs without scraping stdout, and wire doctor results into the agent TODO manager for follow-up actions.
- Provide a thin `z3ed doctor --profile minimal|full` wrapper that batches key diagnostics for CI/agents and returns a single aggregated status code plus JSON report.
## Test & Tools UX Proposals
### Current Observations
- Tests are well-documented for humans (`test/README.md`), but there is no machine-readable manifest of suites/labels or CLI entry to run/parse results; agents must shell out to `ctest` and scrape text.
- Agent-side test commands (`agent test run/list/status/...`) print ad-hoc logs and lack `OutputFormatter`/metadata, making automation fragile; no JSON status, exit codes, or artifacts paths surfaced.
- Test helper tools (`tools-*/` commands, `tools/test_helpers/*`) mix stdout banners with file emission and manual path requirements; they are not discoverable via TUI or CommandRegistry-driven palettes and do not expose dry-run/plan outputs.
- TUI/editor have no test surface: no panel to run `stable/gui/rom_dependent/experimental` suites, inspect failing cases, or attach ROM paths/presets; quick actions and history are missing.
- Build/preset coupling is implicit—no guided flow to pick `mac-test/mac-ai/mac-dev`, enable ROM/AI flags, or attach `YAZE_TEST_ROM_PATH`; agents/humans can misconfigure and get empty test sets.
### Proposed Improvements
- **Unified test CLI/TUI API**
- Add `z3ed test list --format json` (labels, targets, requirements, presets) and `z3ed test run --label stable|gui|rom_dependent --preset <preset> [--rom …] [--artifact <path>]` backed by `ctest` with structured OutputFormatter.
- Emit JSON summaries (pass/fail, duration, failing tests, log paths) with clear exit codes; support `--capture` to write reports for agents and CI.
- Map labels to presets and requirements automatically (ROM path, AI runtime) and surface actionable errors instead of silent skips.
- **TUI/editor integration**
- Add a Tests panel in the unified TUI: quick buttons for `stable`, `stable+gui`, `rom`, `experimental`; show live progress, failures, and links to logs/artifacts; allow rerun of last failure set.
- Mirror the panel in ImGui editor (if available) with a lightweight runner that shells through the same CLI API to keep behavior identical.
- **Agent-aligned test harness**
- Refactor `agent test *` commands to use CommandRegistry metadata and OutputFormatter (JSON default, text fallback), including workflow generation/replay, recording state, and results paths.
- Provide a `test manifest` JSON file (generated from CMake/ctest) listing suites, labels, and prerequisites; expose via `z3ed --export-test-manifest`.
- **Tools/test-helpers cleanup**
- Convert `tools-harness-state`, `tools-extract-values`, `tools-extract-golden`, and `tools-patch-v3` to strict arg validation, `--format {json,text}`, and `--dry-run`/`--output` defaults; summarize emitted artifacts in JSON.
- Register these tools in the TUI command palette with real metadata/examples; add quick actions (“Generate harness state from ROM”, “Extract vanilla values as JSON”).
- **Build/preset ergonomics**
- Add `z3ed test configure --profile {fast,ai,rom,full}` to set the right CMake preset and flags, prompt for ROM path when needed, and persist the choice for the session.
- Surface preset/flag status in the TUI status bar and in `z3ed test status` so agents/humans know why suites are skipped.
## Deliverables / Exit Criteria
- Implemented help/schema surface (`z3ed help`, `z3ed --export-schemas`) backed by handler-supplied metadata; `ExportFunctionSchemas()` returns real data.
- All doctor/validate/compare commands emit structured output via `OutputFormatter` with diagnose/fix separation, dry-run, and baseline inputs; text mode remains readable.
- Single `--tui` experience that pulls commands from `CommandRegistry`, executes them, and displays outputs/history consistently for humans and agents.
- Updated documentation and examples reflecting the consolidated flag/command layout, plus quick-start snippets for agents (JSON) and humans (text).

View File

@@ -0,0 +1,535 @@
# SNES Dungeon Composite Layer System
**Document Status:** Implementation Reference
**Owner:** ai-systems-analyst
**Created:** 2025-12-05
**Last Reviewed:** 2025-12-05
**Next Review:** 2025-12-19
**Update (2025-12-05):** Per-tile priority support has been implemented. Section 5.1 updated.
---
## Overview
This document describes the dungeon room composite layer system used in yaze. It covers:
1. SNES hardware background layer architecture
2. ROM-based LayerMergeType settings
3. C++ implementation in yaze
4. Known issues and limitations
---
## 1. SNES Hardware Background Layer Architecture
### 1.1 Mode 1 Background Layers
The SNES uses **Mode 1** for dungeon rooms, which provides:
- **BG1**: Primary foreground layer (8x8 tiles, 16 colors per tile)
- **BG2**: Secondary background layer (8x8 tiles, 16 colors per tile)
- **BG3**: Text/overlay layer (4 colors per tile)
- **OBJ**: Sprite layer (Link, enemies, items)
**Critical: In SNES Mode 1, BG1 is ALWAYS rendered on top of BG2.** This is hardware behavior controlled by the PPU and cannot be changed by software.
### 1.2 Key PPU Registers
| Register | Address | Purpose |
|----------|---------|---------|
| `BGMODE` | $2105 | Background mode selection (Mode 1 = $09) |
| `TM` | $212C | Main screen layer enable (which BGs appear) |
| `TS` | $212D | Sub screen layer enable (for color math) |
| `CGWSEL` | $2130 | Color math window/clip control |
| `CGADSUB` | $2131 | Color math add/subtract control |
| `COLDATA` | $2132 | Fixed color for color math effects |
### 1.3 Color Math (Transparency Effects)
The SNES achieves transparency through **color math** between the main screen and sub screen:
```
CGWSEL ($2130):
Bits 7-6: Direct color / clip mode
Bits 5-4: Prevent color math (never=0, outside window=1, inside=2, always=3)
Bits 1-0: Sub screen BG/color (main=0, subscreen=1, fixed=2)
CGADSUB ($2131):
Bit 7: Subtract instead of add
Bit 6: Half color math result
Bits 5-0: Enable color math for OBJ, BG4, BG3, BG2, BG1, backdrop
```
**How Transparency Works:**
1. Main screen renders visible pixels (BG1, BG2 in priority order)
2. Sub screen provides "behind" pixels for blending
3. Color math combines main + sub pixels (add or average)
4. Result: Semi-transparent overlay effect
### 1.4 Tile Priority Bit
Each SNES tile has a **priority bit** in its tilemap entry:
```
Tilemap Word: YXPCCCTT TTTTTTTT
Y = Y-flip
X = X-flip
P = Priority (0=low, 1=high)
C = Palette (3 bits)
T = Tile number (10 bits)
```
**Priority Bit Behavior in Mode 1:**
- Priority 0 BG1 tiles appear BELOW priority 1 BG2 tiles
- Priority 1 BG1 tiles appear ABOVE all BG2 tiles
- This allows BG2 to "peek through" parts of BG1
**yaze implements per-tile priority** via the `BackgroundBuffer::priority_buffer_` and priority-aware compositing in `RoomLayerManager::CompositeToOutput()`.
---
## 2. ROM-Based LayerMergeType Settings
### 2.1 Room Header Structure
Each dungeon room has a header containing layer settings:
- **BG2 Mode**: Determines if BG2 is enabled and how it behaves
- **Layer Merging**: Index into LayerMergeType table (0-8)
- **Collision**: Which layers have collision data
### 2.2 LayerMergeType Table
```cpp
// From room.h
// LayerMergeType(id, name, Layer2Visible, Layer2OnTop, Layer2Translucent)
LayerMerge00{0x00, "Off", true, false, false}; // BG2 visible, no color math
LayerMerge01{0x01, "Parallax", true, false, false}; // Parallax scrolling effect
LayerMerge02{0x02, "Dark", true, true, true}; // BG2 color math + translucent
LayerMerge03{0x03, "On top", false, true, false}; // BG2 hidden but in subscreen
LayerMerge04{0x04, "Translucent", true, true, true}; // Translucent BG2
LayerMerge05{0x05, "Addition", true, true, true}; // Additive blending
LayerMerge06{0x06, "Normal", true, false, false}; // Standard dungeon
LayerMerge07{0x07, "Transparent", true, true, true}; // Water/fog effect
LayerMerge08{0x08, "Dark room", true, true, true}; // Unlit room (master brightness)
```
### 2.3 Flag Meanings
| Flag | ASM Effect | Purpose |
|------|------------|---------|
| `Layer2Visible` | Sets BG2 bit in TM ($212C) | Whether BG2 appears on main screen |
| `Layer2OnTop` | Sets BG2 bit in TS ($212D) | Whether BG2 participates in sub-screen color math |
| `Layer2Translucent` | Sets bit in CGADSUB ($2131) | Whether color math is enabled for blending |
**Important Clarification:**
- `Layer2OnTop` does **NOT** change Z-order (BG1 is always above BG2)
- It controls whether BG2 is on the **sub-screen** for color math
- When enabled, BG1 can blend with BG2 to create transparency effects
---
## 3. C++ Implementation in yaze
### 3.1 Architecture Overview
```
Room RoomLayerManager
├── bg1_buffer_ → ├── layer_visible_[4]
├── bg2_buffer_ ├── layer_blend_mode_[4]
├── object_bg1_buffer_ ├── bg2_on_top_
├── object_bg2_buffer_ └── CompositeToOutput()
└── composite_bitmap_
DungeonCanvasViewer
├── Separate Mode (draws each buffer individually)
└── Composite Mode (uses CompositeToOutput)
```
### 3.2 BackgroundBuffer Class
Located in `src/app/gfx/render/background_buffer.h`:
```cpp
class BackgroundBuffer {
std::vector<uint16_t> buffer_; // Tile ID buffer (64x64 tiles)
std::vector<uint8_t> priority_buffer_; // Per-pixel priority (0, 1, or 0xFF)
gfx::Bitmap bitmap_; // 512x512 8-bit indexed bitmap
void DrawFloor(...); // Sets up tile buffer from ROM floor data
void DrawBackground(...); // Renders tile buffer to bitmap pixels
void DrawTile(...); // Draws single 8x8 tile to bitmap + priority
// Priority buffer accessors
void ClearPriorityBuffer();
uint8_t GetPriorityAt(int x, int y) const;
void SetPriorityAt(int x, int y, uint8_t priority);
const std::vector<uint8_t>& priority_data() const;
};
```
**Key Points:**
- Each buffer is 512x512 pixels (64x64 tiles × 8 pixels)
- Uses 8-bit indexed color (palette indices 0-255)
- Transparent fill color is 255 (not 0!)
- Priority buffer tracks per-pixel priority bit (0, 1, or 0xFF for unset)
### 3.3 RoomLayerManager Class
Located in `src/zelda3/dungeon/room_layer_manager.h`:
**LayerType Enum:**
```cpp
enum class LayerType {
BG1_Layout, // Floor tiles on BG1
BG1_Objects, // Objects drawn to BG1 (layer 0, 2)
BG2_Layout, // Floor tiles on BG2
BG2_Objects // Objects drawn to BG2 (layer 1)
};
```
**LayerBlendMode Enum:**
```cpp
enum class LayerBlendMode {
Normal, // Full opacity (255 alpha)
Translucent, // 50% alpha (180)
Addition, // Additive blend (220)
Dark, // Darkened (120)
Off // Hidden (0)
};
```
### 3.4 CompositeToOutput Algorithm (Priority-Aware)
The compositing algorithm implements SNES Mode 1 per-tile priority:
**Effective Z-Order Table:**
| Layer | Priority | Effective Order |
|-------|----------|-----------------|
| BG1 | 0 | 0 (back) |
| BG2 | 0 | 1 |
| BG2 | 1 | 2 |
| BG1 | 1 | 3 (front) |
```cpp
void RoomLayerManager::CompositeToOutput(Room& room, gfx::Bitmap& output) {
// 1. Clear output to transparent (255)
output.Fill(255);
// 2. Create output priority buffer (tracks effective Z-order per pixel)
std::vector<uint8_t> output_priority(kPixelCount, 0xFF);
// 3. Helper to calculate effective Z-order
int GetEffectiveOrder(bool is_bg1, uint8_t priority) {
if (is_bg1) return priority ? 3 : 0; // BG1: 0 or 3
else return priority ? 2 : 1; // BG2: 1 or 2
}
// 4. For each layer, composite with priority comparison:
auto CompositeWithPriority = [&](BackgroundBuffer& buffer, bool is_bg1) {
for (int idx = 0; idx < kPixelCount; ++idx) {
uint8_t src_pixel = src_data[idx];
if (src_pixel == 255) continue; // Skip transparent
uint8_t src_prio = buffer.priority_data()[idx];
int src_order = GetEffectiveOrder(is_bg1, src_prio);
int dst_order = (output_priority[idx] == 0xFF) ? -1 : output_priority[idx];
// Source overwrites if higher or equal effective Z-order
if (dst_order == -1 || src_order >= dst_order) {
dst_data[idx] = src_pixel;
output_priority[idx] = src_order;
}
}
};
// 5. Process all layers (BG2 first, then BG1)
CompositeWithPriority(bg2_layout, false);
CompositeWithPriority(bg2_objects, false);
CompositeWithPriority(bg1_layout, true);
CompositeWithPriority(bg1_objects, true);
// 6. Apply palette and effects
ApplySDLPaletteToBitmap(src_surface, output);
// 7. Handle DarkRoom effect (merge type 0x08)
if (current_merge_type_id_ == 0x08) {
SDL_SetSurfaceColorMod(surface, 128, 128, 128); // 50% brightness
}
}
```
### 3.5 Priority Flow
1. **DrawTile()** in `BackgroundBuffer` writes `tile.over_` (priority bit) to `priority_buffer_`
2. **WriteTile8()** in `ObjectDrawer` also updates `priority_buffer_` for each tile drawn
3. **CompositeToOutput()** uses priority values to determine pixel ordering
**Note:** Blend modes still use simple pixel replacement. True color blending would require expensive RGB palette lookups. Visual effects are handled at SDL display time via alpha modulation.
---
## 4. Object Layer Assignment
### 4.1 Object Layer Field
Each room object has a `layer_` field (0, 1, or 2):
- **Layer 0**: Draws to BG1 buffer
- **Layer 1**: Draws to BG2 buffer
- **Layer 2**: Draws to BG1 buffer (priority variant)
### 4.2 Buffer Routing in ObjectDrawer
```cpp
// In ObjectDrawer::DrawObject()
BackgroundBuffer& target_bg =
(object.layer_ == 1) ? bg2_buffer : bg1_buffer;
// Some routines draw to BOTH buffers (walls, corners)
if (RoutineDrawsToBothBGs(routine_id)) {
DrawToBuffer(bg1_buffer, ...);
DrawToBuffer(bg2_buffer, ...);
}
```
### 4.3 kBothBGRoutines
These draw routines render to both BG1 and BG2:
```cpp
static constexpr int kBothBGRoutines[] = {
0, // DrawRightwards2x2_1to15or32 (ceiling 0x00)
1, // DrawRightwards2x4_1to15or26 (layout walls 0x001, 0x002)
8, // DrawDownwards4x2_1to15or26 (layout walls 0x061, 0x062)
19, // DrawCorner4x4 (layout corners 0x100-0x103)
3, // Rightwards2x4_1to16_BothBG (diagonal walls)
9, // Downwards4x2_1to16_BothBG (diagonal walls)
17, // DiagonalAcute_1to16_BothBG
18, // DiagonalGrave_1to16_BothBG
35, // 4x4Corner_BothBG (Type 2: 0x108-0x10F)
36, // WeirdCornerBottom_BothBG (Type 2: 0x110-0x113)
37, // WeirdCornerTop_BothBG (Type 2: 0x114-0x117)
97, // PrisonCell (dual-layer bars)
};
```
---
## 5. Known Issues and Limitations
### 5.1 Per-Tile Priority (IMPLEMENTED)
**Status:** Implemented as of December 2025.
**Implementation:**
- `BackgroundBuffer::priority_buffer_` stores per-pixel priority (0, 1, or 0xFF)
- `DrawTile()` and `WriteTile8()` write priority from `TileInfo.over_`
- `CompositeToOutput()` uses `GetEffectiveOrder()` for priority-aware compositing
**Effective Z-Order:**
- BG1 priority 0: Order 0 (back)
- BG2 priority 0: Order 1
- BG2 priority 1: Order 2
- BG1 priority 1: Order 3 (front)
**Known Discrepancy (Dec 2025):**
Some objects visible in "Separate Mode" (individual layer view) are hidden in "Composite Mode". This is expected SNES Mode 1 behavior where BG2 priority 1 tiles can appear above BG1 priority 0 tiles.
**Resolution:**
A "Priority Compositing" toggle (P checkbox) was added to the layer controls:
- **ON (default)**: Accurate SNES Mode 1 behavior - BG2-P1 can appear above BG1-P0
- **OFF**: Simple layer order - BG1 always appears above BG2
**Debugging tools:**
- "Show Priority Debug" in context menu shows per-layer priority statistics
- Pixels with "NO PRIORITY SET" indicate missing priority writes
- The Priority Debug window shows Z-order reference table
### 5.2 Simplified Color Blending
**Problem:** True color math requires RGB palette lookups, which is expensive.
**Current Workaround:**
- Blend modes use simple pixel replacement at indexed level
- SDL alpha modulation applied at display time
- Result is approximate, not pixel-accurate
### 5.3 DarkRoom Implementation
**Problem:** SNES DarkRoom uses master brightness register (INIDISP $2100).
**Current Implementation:** SDL color modulation to 50% (128, 128, 128).
### 5.4 Transparency Index
**Issue:** Both 0 and 255 have been treated as transparent at various points.
**Correct Behavior:**
- Index 0 is a VALID color in dungeon palettes (first actual color)
- Index 255 is the fill color for undrawn areas (should be transparent)
- CompositeLayer should only skip pixels with value 255
---
## 6. Related Files
| File | Purpose |
|------|---------|
| `src/zelda3/dungeon/room_layer_manager.h` | Layer visibility and compositing control |
| `src/zelda3/dungeon/room_layer_manager.cc` | CompositeToOutput implementation |
| `src/zelda3/dungeon/room.h` | LayerMergeType definitions |
| `src/zelda3/dungeon/room.cc` | Room rendering (RenderRoomGraphics) |
| `src/app/gfx/render/background_buffer.h` | BackgroundBuffer class |
| `src/app/gfx/render/background_buffer.cc` | Floor/tile drawing implementation |
| `src/zelda3/dungeon/object_drawer.cc` | Object rendering and buffer routing |
| `src/app/editor/dungeon/dungeon_canvas_viewer.cc` | Editor display (separate vs composite mode) |
---
## 7. ASM Reference: Color Math Registers
### CGWSEL ($2130) - Color Addition Select
```
7-6: Direct color mode / Prevent color math region
00 = Always perform color math
01 = Inside window only
10 = Outside window only
11 = Never perform color math
5-4: Clip colors to black region (same as 7-6)
3-2: Unused
1-0: Sub screen backdrop selection
00 = From palette (main screen)
01 = Sub screen
10 = Fixed color (COLDATA)
11 = Fixed color (COLDATA)
```
### CGADSUB ($2131) - Color Math Designation
```
7: Color subtract mode (0=add, 1=subtract)
6: Half color math (0=full, 1=half result)
5: Enable color math for OBJ/Sprites
4: Enable color math for BG4
3: Enable color math for BG3
2: Enable color math for BG2
1: Enable color math for BG1
0: Enable color math for backdrop
```
### COLDATA ($2132) - Fixed Color Data
```
7: Blue intensity enable
6: Green intensity enable
5: Red intensity enable
4-0: Intensity value (0-31)
```
---
## 8. Future Work
1. ~~**Per-Tile Priority**: Implement priority bit tracking for accurate Z-ordering~~ **DONE**
2. **True Color Blending**: Optional accurate blend mode with palette lookups
3. **HDMA Effects**: Support for scanline-based color math changes
4. ~~**Debug Visualization**: Show layer buffers with priority/blend annotations~~ **DONE**
- Added "Show Priority Debug" menu item in dungeon canvas context menu
- Priority Debug window shows per-layer statistics:
- Total non-transparent pixels
- Pixels with priority 0 vs priority 1
- Pixels with NO PRIORITY SET (indicates missing priority writes)
5. **Fix Missing Priority Writes**: Investigate objects that don't update priority buffer
---
## 9. Next Agent Steps: Fix Hidden Objects in Combo Rooms
**Priority:** HIGH - Objects hidden in composite mode regardless of priority toggle setting
### 9.1 Problem Description
Certain rooms have objects that are hidden in composite mode (both with and without priority compositing enabled). This occurs specifically in:
1. **BG Merge "Normal" (ID 0x06) rooms** - Standard dungeon layer merging
2. **BG2 Layer Behavior "Off" combo rooms** - Rooms where BG2 visibility is disabled by ROM
These are NOT priority-related issues since the objects remain hidden even when the "P" (priority) checkbox is unchecked.
### 9.2 Debugging Steps
1. **Identify affected rooms:**
- Open dungeon editor with a test ROM
- Navigate to rooms with layer_merging().ID == 0x06 ("Normal")
- Toggle between composite mode (M checkbox) and separate layer view
- Note which objects appear in separate mode but are hidden in composite mode
2. **Use Priority Debug window:**
- Right-click canvas → Debug → Show Priority Debug
- Check for "NO PRIORITY SET" pixels on BG1 Objects layer
- Check if the affected objects are in BG1 or BG2 buffers
3. **Check buffer contents:**
- In separate mode, verify each layer (BG1, O1, BG2, O2) individually
- Identify which buffer the "missing" objects are actually drawn to
### 9.3 Likely Root Causes
1. **BG2 visibility not respected:**
- `LayerMergeType.Layer2Visible` may not be correctly applied
- Check `ApplyLayerMerging()` in `room_layer_manager.cc`
- Verify BG2 layers are included when `Layer2Visible == true`
2. **Object layer assignment mismatch:**
- Objects may be drawn to wrong buffer (BG1 vs BG2)
- Check `RoomObject.layer_` field values
- Verify `ObjectDrawer::DrawObject()` buffer routing logic
3. **Transparency index conflict:**
- Pixel value 0 vs 255 confusion
- Check if objects are being skipped as "transparent" incorrectly
- Verify `IsTransparent()` only checks for 255
4. **BothBG routines priority handling:**
- Objects drawn to both BG1 and BG2 may have conflicting priorities
- Check routines in `kBothBGRoutines[]` list
- Verify both buffer draws update priority correctly
### 9.4 Files to Investigate
| File | What to Check |
|------|---------------|
| `room_layer_manager.cc` | `ApplyLayerMerging()`, `CompositeWithPriority()` lambda |
| `room_layer_manager.h` | `LayerMergeType` handling, visibility flags |
| `object_drawer.cc` | Buffer routing in `DrawObject()`, `RoutineDrawsToBothBGs()` |
| `room.h` | `LayerMergeType` definitions (Section 2.2 of this doc) |
| `background_buffer.cc` | `DrawTile()` priority writes, transparency handling |
### 9.5 Recommended Fixes to Try
1. **Add logging to composite loop:**
```cpp
// In CompositeWithPriority lambda, add:
static int debug_count = 0;
if (debug_count++ < 100 && !IsTransparent(src_pixel)) {
printf("[Composite] layer=%d idx=%d pixel=%d prio=%d\n",
static_cast<int>(layer_type), idx, src_pixel, src_prio);
}
```
2. **Verify BG2 visibility in composite:**
- Ensure `IsLayerVisible(LayerType::BG2_Layout)` returns true for Normal merge
- Check if `Layer2Visible` from ROM is being incorrectly overridden
3. **Check for early-exit conditions:**
- Search for `return` statements in `CompositeWithPriority` that might skip layers
- Verify `blend_mode == LayerBlendMode::Off` check isn't incorrectly triggered
### 9.6 Test Cases
After making fixes, verify with these room types:
- Room with layer_merging ID 0x06 (Normal) - objects should appear
- Room with layer_merging ID 0x00 (Off) - BG2 should still be visible
- Room with BothBG objects (walls, corners) - should render correctly
- Dark room (ID 0x08) - should have correct dimming
### 9.7 Success Criteria
- Objects visible in separate mode should also be visible in composite mode
- Priority toggle (P checkbox) should only affect Z-ordering, not visibility
- No regression in rooms that currently render correctly

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
# Documentation Hygiene & Spec Rules
Purpose: keep `docs/internal` lean, discoverable, and aligned with active work. Use this when creating or updating any internal spec/plan.
## Canonical sources first
- Check existing coverage: update an existing spec before adding a new file. Search `docs/internal` for keywords and reuse templates.
- One source of truth per initiative: tie work to a board entry and, if multi-day, a single spec (e.g., `initiative-*.md` or a plan under `docs/internal/plans/`).
- Ephemeral task lists live in `z3ed agent todo` or the coordination board, not new Markdown stubs.
## Spec & plan shape
- Header block: `Status (ACTIVE/IN_PROGRESS/ARCHIVE)`, `Owner (Agent ID)`, `Created`, `Last Reviewed`, `Next Review` (≤14 days by default), and link to the coordination-board entry.
- Keep the front page tight: Summary, Decisions/Constraints, Deliverables, and explicit Exit Criteria. Push deep design notes into appendices.
- Use the templates: `initiative-template.md` for multi-day efforts, `release-checklist-template.md` for releases. Avoid custom one-off formats.
## Lifecycle & archiving
- Archive on completion/abandonment: move finished or idle (>14 days without update) specs to `docs/internal/agents/archive/` (or the relevant sub-archive). Add the date to the filename when moving.
- Consolidate duplicates: if multiple docs cover the same area, merge into the newest spec and drop redirects into the archive with a short pointer.
- Weekly sweep: during board maintenance, prune outdated docs referenced on the board and archive any that no longer have an active entry.
## Coordination hygiene
- Every spec links back to the coordination board entry; every board entry points to its canonical spec. No parallel shadow docs.
- Release/initiative docs must include a test/validation section instead of ad-hoc checklists scattered across files.
- When adding references in other docs, point to the canonical spec instead of copying sections.
## Anti-spam guardrails
- No gamified/leaderboard or duplicative status pages in `agents/`—keep status in the board and the canonical spec.
- Prefer updating `docs/internal/README.md` or the nearest index with short summaries instead of creating new directories.
- Cap new doc creation per initiative to one spec + one handoff; everything else belongs in comments/PRs or the board.
- Filenames: avoid ALL-CAPS except established anchors (README, AGENTS, GEMINI, CLAUDE, CONTRIBUTING, etc.); use kebab-case for new docs.

View File

@@ -0,0 +1,304 @@
# Dungeon Draw Routine Tracker
**Status:** Active
**Owner:** dungeon-rendering-specialist
**Created:** 2025-12-07
**Last Reviewed:** 2025-12-09
**Next Review:** 2025-12-23
---
## Summary
This document is the single source of truth for dungeon object draw routine implementation status. It consolidates information from previous handoff documents and provides a comprehensive reference for fixing remaining issues.
---
## Recent Changes (2025-12-09)
| Change | Details |
|--------|---------|
| Unified draw routine registry | Created `DrawRoutineRegistry` singleton for ObjectDrawer/ObjectGeometry parity |
| BG1 mask rectangle API | Added `GeometryBounds::GetBG1MaskRect()` and `RequiresBG1Mask()` for 94 affected rooms |
| Fixed vertical rails 0x8A-0x8C | Applied CORNER+MIDDLE+END pattern matching horizontal rail 0x22 |
| Custom objects 0x31/0x32 | Registered with routine 130 for Oracle of Secrets minecart tracks |
| Selection bounds for diagonals | Added `selection_bounds` field for tighter hit testing on 0xA0-0xA3 |
| Audited 0x233 staircase | Confirmed 32x32 (4x4 tile) dimensions correct per ASM |
## Previous Changes (2025-12-07)
| Change | Details |
|--------|---------|
| Fixed routine 16 dimensions | `DrawRightwards4x4_1to16` now correctly calculates `width = 32 * count` based on size |
| Added BG2 mask propagation logging | Debug output shows when Layer 1 objects trigger floor masking |
| BG2 masking research complete | 94 rooms affected, fix implemented in `MarkBG1Transparent` |
---
## Completed Fixes
| Object ID | Name | Issue | Fix Applied |
|-----------|------|-------|-------------|
| 0x5E | Block | Inverted tile ordering | Fixed column-major order |
| 0x5D/0x88 | Thick Rails | Repeated edges | Fixed cap-middle-cap pattern |
| 0xF99 | Chest | Repeated based on size | Changed to DrawSingle2x2 |
| 0xFB1 | Big Chest | Repeated based on size | Changed to DrawSingle4x3 |
| 0xF92 | Blue Rupees | Not correct at all | Implemented DrawRupeeFloor per ASM |
| 0xFED | Water Grate | Wrong outline, repeated | Changed to DrawSingle4x3 |
| 0x3A | Wall Decors | Spacing wrong (6 tiles) | Fixed to 8 tiles per ASM |
| 0x39/0x3D | Pillars | Spacing wrong (6 tiles) | Fixed to 4 tiles per ASM |
| 0xFEB | Large Decor | Outline too small | Fixed to 64x64 (4x4 tile16s) |
| 0x138-0x13B | Spiral Stairs | Wrong 4x4 pattern | Fixed to 4x3 per ASM |
| 0xA0-0xAC | Diagonal Ceilings | Vertical line instead of triangle | Fixed triangle fill pattern |
| 0xC0/0xC2 etc | SuperSquare | Fixed 32x32 dimensions | Now uses size parameter |
| 0xFE6 | Pit | Should not repeat | Uses DrawActual4x4 (32x32, no repeat) |
| 0x55-0x56 | Wall Torches | Wrong pattern | Fixed to 1x8 column with 12-tile spacing |
| 0x22 | Small Rails | Internal parts repeat | Now CORNER+MIDDLE*count+END pattern |
| 0x23-0x2E | Carpet Trim | Wrong pattern | Now CORNER+MIDDLE*count+END pattern |
| 0x033 | Floor 4x4 | Wrong BG2 mask size | Fixed dimension to use size parameter |
| 0x12D-0x12F | InterRoom Fat Stairs | Wrong dimensions (32x24) | Fixed to 32x32 (4x4 tiles) |
| 0x130-0x133 | Auto Stairs | Wrong dimensions (32x24) | Fixed to 32x32 (4x4 tiles) |
| 0xF9E-0xFA9 | Straight InterRoom Stairs | Wrong dimensions (32x24) | Fixed to 32x32 (4x4 tiles) |
| 0x8A-0x8C | Vertical Rails | Using wrong horizontal routine | Fixed CORNER+MIDDLE+END pattern |
| 0x31/0x32 | Custom Objects | Not registered in draw routine map | Registered with routine 130 |
| 0x233 | AutoStairsSouthMergedLayer | Need audit | Confirmed 32x32 (4x4 tiles) correct |
| 0xDC | OpenChestPlatform | Layer check missing | Added layer handling documentation |
---
## In Progress / Known Issues
| Object ID | Name | Issue | Status |
|-----------|------|-------|--------|
| 0x00 | Ceiling | Outline should be like 0xC0 | Needs different dimension calc |
| 0xC0 | Large Ceiling | SuperSquare routine issues | Needs debug |
| 0x22 vs 0x8A-0x8E | Rails | Horizontal fixed, vertical needs match | Medium priority |
| 0xA0-0xA3 | Diagonal Ceilings | Outline too large for selection | May need UI-level fix |
| 0x3D | Torches | Top half draws pegs | ROM tile data issue |
| 0x95/0x96 | Vertical Pegs | Outline appears square | May be UI issue |
---
## Pending Fixes
| Object ID | Name | Issue | Priority |
|-----------|------|-------|----------|
| Pit Edges | Various | Single tile thin based on direction | Low |
**Note:** Staircase objects 0x12D-0x137 (Fat/Auto Stairs), Type 3 stairs (0xF9E-0xFA9), and 0x233 (AutoStairsSouthMergedLayer) have been audited as of 2025-12-09. All use 32x32 (4x4 tile) dimensions matching the ASM.
---
## Size Calculation Formulas (from ASM)
| Routine | Formula |
|---------|---------|
| GetSize_1to16 | `count = (size & 0x0F) + 1` |
| GetSize_1to15or26 | `count = size; if 0, count = 26` |
| GetSize_1to15or32 | `count = size; if 0, count = 32` |
| GetSize_1to16_timesA | `count = (size & 0x0F + 1) * A` |
| SuperSquare | `size_x = (size & 0x0F) + 1; size_y = ((size >> 4) & 0x0F) + 1` |
---
## Draw Routine Architecture
### Routine ID Ranges
- **0-55**: Basic patterns (2x2, 4x4, edges, etc.)
- **56-64**: SuperSquare patterns
- **65-82**: Decorations, pots, pegs, platforms
- **83-91**: Stairs
- **92-115**: Special/interactive objects
### New Routines Added (Phase 2)
- **Routine 113**: DrawSingle4x4 - Single 4x4 block, no repetition
- **Routine 114**: DrawSingle4x3 - Single 4x3 block, no repetition
- **Routine 115**: DrawRupeeFloor - Special 6x8 pattern with gaps
### Tile Ordering
Most routines use **COLUMN-MAJOR** order: tiles advance down each column, then right to next column.
### Rail Pattern Structure
```
[CORNER tile 0] -> [MIDDLE tile 1 × count] -> [END tile 2]
```
---
## BG2 Masking (Layer Compositing)
### Overview
94 rooms have Layer 1 (BG2) overlay objects that need to show through BG1 floor tiles.
### Implementation
When a Layer 1 object is drawn to BG2 buffer, `MarkBG1Transparent` is called to mark the corresponding BG1 pixels as transparent (255), allowing BG2 content to show through during compositing.
### Affected Objects (Example: Room 001)
| Object ID | Position | Size | Dimensions |
|-----------|----------|------|------------|
| 0x033 | (22,13) | 4 | 160×32 px (5 × 4x4 blocks) |
| 0x034 | (23,16) | 14 | 144×8 px (18 × 1x1 tiles) |
| 0x071 | (22,13) | 0 | 8×32 px (1×4 tiles) |
| 0x038 | (24,12) | 1 | 48×24 px (2 statues) |
| 0x13B | (30,10) | 0 | 32×24 px (spiral stairs) |
### Testing
Use `scripts/analyze_room.py` to identify Layer 1 objects:
```bash
python3 scripts/analyze_room.py --rom roms/alttp_vanilla.sfc 1 --compositing
python3 scripts/analyze_room.py --rom roms/alttp_vanilla.sfc --list-bg2
```
---
## Files Reference
| File | Purpose |
|------|---------|
| `src/zelda3/dungeon/object_drawer.cc` | Main draw routines and ID mapping |
| `src/zelda3/dungeon/object_drawer.h` | Draw routine declarations |
| `src/zelda3/dungeon/draw_routines/special_routines.cc` | Complex special routines |
| `src/zelda3/dungeon/room_layer_manager.cc` | Layer compositing |
| `assets/asm/usdasm/bank_01.asm` | Reference ASM disassembly |
| `scripts/analyze_room.py` | Room object analysis tool |
---
## Validation Criteria
1. Outline matches expected dimensions per ASM calculations
2. Draw pattern matches visual appearance in original game
3. Tile ordering correct (column-major vs row-major)
4. Repetition behavior correct (single draw vs size-based repeat)
5. BG2 overlay objects visible through BG1 floor
6. No build errors after changes
---
## Exit Criteria
- [ ] All high-priority issues resolved
- [ ] Medium-priority issues documented with investigation notes
- [ ] BG2 masking working for all 94 affected rooms
- [ ] Staircase audit complete (0x12D-0x233)
- [ ] No regression in existing working routines
---
---
## Layer Merge Type Effects
### Current Implementation Status
| Merge ID | Name | Effect | Status |
|----------|------|--------|--------|
| 0x00 | Off | BG2 visible, no effects | Working |
| 0x01 | Parallax | BG2 visible, parallax scroll | Not implemented |
| 0x02 | Dark | BG2 translucent blend | Simplified (no true blend) |
| 0x03 | On top | BG2 hidden but in subscreen | Partial |
| 0x04 | Translucent | BG2 translucent | Simplified |
| 0x05 | Addition | Additive blending | Simplified |
| 0x06 | Normal | Standard dungeon | Working |
| 0x07 | Transparent | Water/fog effect | Simplified |
| 0x08 | Dark room | Master brightness 50% | Working (SDL color mod) |
### Known Limitations
1. **Translucent Blending (0x02, 0x04, 0x05, 0x07)**: Currently uses threshold-based pixel copying instead of true alpha blending. True blending would require RGB palette lookups which is expensive for indexed color mode.
2. **Dark Room Effect (0x08)**: Implemented via `SDL_SetSurfaceColorMod(surface, 128, 128, 128)` which reduces brightness to 50%. Applied after compositing.
3. **Parallax Scrolling (0x01)**: Not implemented - would require separate layer offset during rendering.
### Implementation Details
**Dark Room (0x08):**
```cpp
// In room_layer_manager.cc CompositeToOutput()
if (current_merge_type_id_ == 0x08) {
SDL_SetSurfaceColorMod(output.surface(), 128, 128, 128);
}
```
**Translucent Layers:**
```cpp
// In ApplyLayerMerging()
if (merge_type.Layer2Translucent) {
SetLayerBlendMode(LayerType::BG2_Layout, LayerBlendMode::Translucent);
SetLayerBlendMode(LayerType::BG2_Objects, LayerBlendMode::Translucent);
}
```
### Future Improvements
- Implement true alpha blending for translucent modes (requires RGB conversion)
- Add parallax scroll offset support for merge type 0x01
- Consider per-scanline HDMA effects simulation
---
---
## Custom Objects (Oracle of Secrets)
### Status: Not Working
Custom objects (IDs 0x31, 0x32) use external binary files instead of ROM tile data. These are used for minecart tracks and custom furniture in Oracle of Secrets.
### Issues
| Issue | Description |
|-------|-------------|
| Routine not registered | 0x31/0x32 have no entry in `object_to_routine_map_` |
| DrawCustomObject incomplete | Routine exists but isn't called |
| Previews don't work | Object selector can't preview custom objects |
### Required Fixes
1. **Register routine in InitializeDrawRoutines():**
```cpp
object_to_routine_map_[0x31] = CUSTOM_ROUTINE_ID;
object_to_routine_map_[0x32] = CUSTOM_ROUTINE_ID;
```
2. **Add draw routine to registry:**
```cpp
draw_routines_.push_back([](ObjectDrawer* self, ...) {
self->DrawCustomObject(obj, bg, tiles, state);
});
```
3. **Ensure CustomObjectManager initialized before drawing**
### Project Configuration
```ini
[files]
custom_objects_folder=/path/to/Dungeons/Objects/Data
[feature_flags]
enable_custom_objects=true
```
### Full Documentation
See [`HANDOFF_CUSTOM_OBJECTS.md`](../hand-off/HANDOFF_CUSTOM_OBJECTS.md) for complete details.
---
## Related Documentation
- [`dungeon-object-rendering-spec.md`](dungeon-object-rendering-spec.md) - ASM-based rendering specification
- [`HANDOFF_BG2_MASKING_FIX.md`](../hand-off/HANDOFF_BG2_MASKING_FIX.md) - BG2 masking implementation details
- [`HANDOFF_CUSTOM_OBJECTS.md`](../hand-off/HANDOFF_CUSTOM_OBJECTS.md) - Custom objects system
- [`dungeon-layer-compositing-research.md`](../plans/dungeon-layer-compositing-research.md) - Layer system research
- [`composite-layer-system.md`](composite-layer-system.md) - Layer compositing implementation details

View File

@@ -0,0 +1,108 @@
# Dungeon Object Rendering & Selection Spec (ALTTP)
Status: ACTIVE
Owner: zelda3-hacking-expert
Created: 2025-12-06
Last Reviewed: 2025-12-06
Next Review: 2025-12-20
Board: docs/internal/agents/coordination-board.md (2025-12-06 zelda3-hacking-expert Dungeon object render/selection spec)
## Scope
- Source of truth: `assets/asm/usdasm/bank_01.asm` (US 1.0 disasm) plus room headers in the same bank.
- Goal: spell out how layouts and objects are drawn, how layers are selected/merged, and how object symbology should match the real draw semantics (arrows, “large”/4x4 growth, BothBG).
- Pain points to fix: corner ceilings and ceiling variants (4x4, vertical 2x2, horizontal 2x2), BG merge vs layer type treated as exclusive, layout objects occasionally drawing over background objects, and selection outlines that do not match the real footprint.
## Room Build & Layer Order (bank_01.asm)
- `LoadAndBuildRoom` (`assets/asm/usdasm/bank_01.asm:$01873A`):
1) `LoadRoomHeader` ($01B564) pulls header bits: blockset/light bits to `$0414`, layer type bits to `$046C`, merge/effect bits to `$063C-$063F/$0640`, palette/spriteset/tag bytes immediately after.
2) `RoomDraw_DrawFloors` ($0189DC): uses the first room word. High nibble → `$0490` (BG2 floor set), low nibble → `$046A` (BG1 floor set). Draws 4×4 quadrant “super squares” through `RoomDraw_FloorChunks`, targeting BG2 pointers first then BG1 pointers.
3) Layout pointer: reads the next byte at `$B7+BA` into `$040E`, converts to a 3-byte pointer via `RoomLayoutPointers`, resets `BA=0`, and runs `RoomDraw_DrawAllObjects` on that layout list (this is the template layer; it should stay underneath everything else).
4) Primary room objects: restores the rooms object pointer (`RoomData_ObjectDataPointers`) and runs `RoomDraw_DrawAllObjects` again (BA now points past the layout byte).
5) BG2 overlay list: skips the `0xFFFF` sentinel (`INC BA` twice), reloads pointer tables with `RoomData_TilemapPointers_lower_layer`, and draws a third object list to BG2.
6) BG1 overlay list: skips the next `0xFFFF`, reloads pointer tables with `RoomData_TilemapPointers_upper_layer`, and draws the final object list to BG1.
7) Pushable blocks (`$7EF940`) and torches (`$7EFB40`) are drawn after the four passes.
- Implication: BG merge and layer type are **not** exclusive—four object streams are processed in order, with explicit pointer swaps for BG2 then BG1 overlays. Layout objects should never overdraw later passes; if they do in the editor, the pass order is wrong.
## Object Encoding (RoomDraw_RoomObject at $01893C)
- Type detection:
- Type 2 sentinel: low byte `>= $FC` triggers `.subtype_2` ($018983).
- Type 3 sentinel: object ID `>= $F8` triggers `.subtype_3` ($0189B8) after the ID is loaded.
- Type 1: everything else (standard 3-byte objects).
- Type 1 format (`xxxxxxss | yyyyyyss | id`):
- `x = (byte0 & 0xFC) >> 2`, `y = (byte1 & 0xFC) >> 2` (tile-space, 063).
- `size_nibble = ((byte0 & 0x03) << 2) | (byte1 & 0x03)` (015).
- ID = byte2.
- Size helpers (ground truth for outline math):
- `RoomDraw_GetSize_1to16(_timesA)` at $01B0AC: `size = nibble + A` (`A=1` for most routines; diagonal ceilings pass `A=4` to force a 4-tile base span).
- `RoomDraw_GetSize_1to15or26` at $01B0BE: nibble 0 → 26 tiles; otherwise nibble value.
- `RoomDraw_GetSize_1to15or32` at $01B0CC: nibble 0 → 32 tiles; otherwise nibble value.
- After calling any helper: `$B2` holds the final count; `$B4` is cleared.
- Type 2 format (`byte0 >= $FC`) uses tables at `.type2_data_offset` ($0183F0) and `.type2_routine` ($018470). No size field; fixed dimensions per routine.
- Type 3 format (`id >= $F8`) uses `.type3_data_offset` ($0184F0) and `.type3_routine` ($0185F0). Some use size nibble (e.g., Somaria lines); most are fixed-size objects like chests and stair blocks.
## Draw Routine Families & Expected Symbology
- Type 1 routine table: `.type1_routine` at `$018200`.
- `Rightwards*` → arrow right; grows horizontally by `size` blocks. Base footprints: `2x4`, `2x2`, `4x4`, etc. Spacing suffix (`spaced2/4/8/12`) means step that many tiles between columns.
- `Downwards*` → arrow down; grows vertically by `size` blocks with the same spacing conventions.
- `DiagonalAcute/Grave` → 45° diagonals; use diagonal arrow/corner icon. Size = nibble+1 tiles long (helper is `1to16`). `_BothBG` variants must draw to both BG1 and BG2.
- `DiagonalCeiling*` (IDs 0xA00xAC): size = nibble + 4 (`GetSize_1to16_timesA` with `A=4`). Bounding box is square (`size × size`) because each step moves x+y by 1.
- `4x4Floor*/Blocks*/SuperSquare` (IDs 0xC00xCA, 0xD10xE8): use “large square” icon. They tile 4×4 blocks inside 16×16 “super squares” (128×128 pixels) and do **not** use the size nibble—dimensions are fixed per routine.
- `Edge/Corner` variants: use L-corner or edge glyph; many have `_BothBG` meaning they write to BG1 and BG2 simultaneously (should not be layer-exclusive).
- Type 2 routines (`.type2_routine`):
- IDs 0x1080x117: `RoomDraw_4x4Corner_BothBG` and “WeirdCorner*” draw to both layers—icon should denote dual-layer.
- IDs 0x12D0x133: inter-room fat stairs (A/B) and auto-stairs north (multi-layer vs merged) explicitly encode whether they target both layers or a merged layer; UI must not force exclusivity.
- IDs 0x1350x13F: water-hop stairs, spiral stairs, sanctuary wall, magic bat altar—fixed-size, no size nibble.
- Type 3 routines (`.type3_routine`):
- Chests/big chests (0x2180x232) are single 1×1 anchors; selection should stay 1 tile.
- Somaria lines (0x2030x20C/0x20E) use the size nibble as a tile count; they extend along X with no Y growth.
- Pipes (0x23A0x23D) are fixed 2×? rectangles; use arrows that match their orientation.
## Ceiling and Large Object Ground Truth
- Corner/diagonal ceilings (Type 1 IDs 0xA00xAC): `RoomDraw_DiagonalCeiling*` ($018BE0$018C36). Size = nibble+4; outline should be a square whose side equals that size; growth is along the diagonal (x+1,y+1 per step).
- Big hole & overlays: ID 0xA4 → `RoomDraw_BigHole4x4_1to16` (fixed 4×4). IDs 0xD8/0xDA → `RoomDraw_WaterOverlayA/B8x8_1to16` (fixed 8×8 overlay; should remain on BG2 overlay pass).
- 4x4 ceilings/floors: IDs 0xC50xCA, 0xD10xD2, 0xD9, 0xDF0xE8 → `RoomDraw_4x4FloorIn4x4SuperSquare` (fixed 4×4 tiles repeated in super squares). Use “large square” glyph; ignore size nibble.
- 2x2 ceilings:
- Horizontal/right-growing: IDs 0x070x08 and 0xB80xB9 use `RoomDraw_Rightwards2x2_*` (size-driven width, height=2). Arrow right, outline width = `2 * size`, height = 2.
- Vertical/down-growing: IDs 0x060 and 0x0920x093 use `RoomDraw_Downwards2x2_*` (size-driven height, width=2). Arrow down, outline height = `2 * size`, width = 2. When the size nibble is 0 (`1to15or32`), treat size as 32 for bounds.
## Layer Merge Semantics
- Header bits at `LoadRoomHeader` ($01B5F4$01B683):
- Bits 57 of header byte 0 → `$0414` (blockset/light flags).
- Bits 24 → `$046C` (layer type selector used later when building draw state).
- Bits 01 of later header bytes → `$063C-$0640` (effect/merge/tag flags).
- `RoomDraw_DrawAllObjects` is run four times with different tilemap pointer tables; `_BothBG` routines ignore the active pointer swap and write to both buffers. The editor must allow “BG merge” and “layer type” to coexist; never force a mutually exclusive radio button.
- Ordering for correctness:
1) Floors (BG2 then BG1)
2) Layout list (BG2)
3) Main list (BG2 by default, unless the routine itself writes both)
4) BG2 overlay list (after first `0xFFFF`)
5) BG1 overlay list (after second `0xFFFF`)
6) Pushable blocks and torches
## Selection & Outline Rules
- Use the decoding rules above; do not infer size from UI icons.
- Type 1 size nibble:
- Standard (`1to16`): `size = nibble + 1`.
- `1to15or26`: nibble 0 → size 26.
- `1to15or32`: nibble 0 → size 32.
- Diagonal ceilings: size = nibble + 4; outline is a square of that many tiles.
- Base footprints:
- `2x4` routines: width=2, height=4; repeated along the growth axis.
- `2x2` routines: width=2, height=2; repeated along the growth axis.
- `4x4` routines: width=4, height=4; ignore size nibble unless the routine name includes `1to16`.
- Super-square routines: treat as 16×16 tiles when computing selection bounds (they stamp 4×4 blocks into a 32×32-tile area).
- `_BothBG` routines should carry a dual-layer badge in the palette and never be filtered out by the current layer toggle—selection must remain visible regardless of BG toggle because the object truly occupies both buffers.
## Mapping UI Symbology to Real Objects
- Arrows right/left: any `Rightwards*` routine; growth = size nibble (with fallback rules above). Use “large” badge only when the routine name includes `4x4` or `SuperSquare`.
- Arrows down/up: any `Downwards*` routine; same sizing rules.
- Diagonal arrow: `DiagonalAcute/Grave` and `DiagonalCeiling*`.
- Large square badge: `4x4Floor*`, `4x4Blocks*`, `BigHole4x4`, water overlays, chest platforms; these do **not** change size with the nibble.
- Dual-layer badge: routines with `_BothBG` in the disasm name, plus `AutoStairs*MergedLayer*` (IDs 0x1320x133, 0x233). These must be allowed even when a “merged BG” flag is set in the header.
## Action Items for the Editor
- Enforce the build order above so layout objects never sit above BG overlays; respect the two post-`0xFFFF` lists for BG2/BG1 overlays.
- Update selection bounds to honor the size helpers (including the nibble-zero fallbacks and the `+4` base for diagonal ceilings).
- Mark BothBG/merged-layer routines so layer toggles do not hide or exclude them.
- Align palette/symbology labels with the disasm names: arrows for `Rightwards/Downwards`, diagonal for `Diagonal*`, large-square for `4x4*/SuperSquare`, dual-layer for `_BothBG`/merged stairs.

View File

@@ -0,0 +1,59 @@
# Dungeon Palette Fix Plan (ALTTP)
Status: COMPLETED
Owner: zelda3-hacking-expert
Created: 2025-12-06
Last Reviewed: 2025-12-10
Completed: 2025-12-10
Board: docs/internal/agents/coordination-board.md (dungeon palette follow-on to render/selection spec)
## Goal
Align dungeon palette loading/rendering with usdasm: correct pointer math, 16-color chunking, transparency, and consistent BG1/BG2/object palettes.
## Scope
- Files: `src/zelda3/dungeon/room.cc`, `game_data` palette setup, `ObjectDrawer` palette usage.
- Reference: `assets/asm/usdasm/bank_01.asm` (palette pointers at $0DEC4B) and `docs/internal/agents/dungeon-object-rendering-spec.md`.
## Tasks
1) Verify palette set construction
- Confirm `game_data_->palette_groups.dungeon_main` has the right count/order (20 sets × 16 colors) matching usdasm pointers.
2) Palette ID derivation
- Keep pointer lookup: `paletteset_ids[palette][0]` byte offset → word at `kDungeonPalettePointerTable + offset` → divide by 180 (0xB4) to get set index. Add assertions/logging on out-of-range.
3) SDL palette mapping
- Build the SDL palette in 16-color chunks: chunk n → indices `[n*16..n*16+15]`, with index `n*16+0` transparent/colorkey. Stop treating “90-color” as linear; respect chunk boundaries.
- Use the same mapped palette for bg1/bg2/object buffers (header selects one set for the room).
4) Transparency / colorkey
- Set colorkey on each chunks index 0 (or keep 255 but ensure chunk 0 is unused) and avoid shifting palette indices in `WriteTile8` paths.
5) Diagnostics
- Log palette_id, pointer, and first color when palette is applied; add a debug mode to dump chunk boundaries for quick verification.
## Exit Criteria
- Dungeon rooms render with correct colors across BG1/BG2/objects; palette_id derivation matches usdasm; SDL palette chunking aligns with 16-color boundaries; transparency behaves consistently.
## Implementation Summary (2025-12-10)
### Changes Made:
1. **`room.cc:665-718`** - Palette mapping now uses 16-color banks:
- ROM colors `[N*15 .. N*15+14]` → SDL indices `[N*16+1 .. N*16+15]`
- Index `N*16` in each bank is transparent (matches SNES CGRAM)
- Unified palette applied to all 4 buffers (bg1, bg2, object_bg1, object_bg2)
2. **`background_buffer.cc:120-137,161-164`** - Updated drawing formula:
- Changed `palette_offset = (pal - 2) * 15``(pal - 2) * 16`
- Changed `final_color = (pixel - 1) + palette_offset``pixel + palette_offset`
- Pixel 0 = transparent (not written), pixels 1-15 map to bank indices 1-15
3. **`object_drawer.cc:4095-4133`** - Same 16-color bank chunking for object rendering
4. **`room_layer_manager.h:461-466`** - Updated documentation comments
### Key Formula:
```
SDL Bank N (N=0-5): indices [N*16 .. N*16+15]
- Index N*16 = transparent (SNES CGRAM row N, color 0)
- Indices N*16+1 to N*16+15 = actual colors
Tile rendering: final_color = pixel + (bank * 16)
where pixel ∈ [1,15] and bank = (palette_bits - 2)
```

View File

@@ -0,0 +1,559 @@
# Overworld Editor Refactoring - Handoff Document
**Agent ID:** ai-infra-architect
**Date:** December 6, 2025
**Status:** Phase 2 Complete - Critical Bug Fixes & Tile16 Editor Polish
**Next Phase:** Week 2 Toolset Improvements (Eyedropper, Flood Fill, Eraser)
---
## Executive Summary
Phase 1 of the overworld editor refactoring is complete. This phase focused on documentation without functional changes. The codebase analysis has revealed several areas requiring attention for Phase 2, including critical bugs in the tile cache system, incomplete zoom/pan implementation, and opportunities for better separation of concerns.
---
## Completed Work (Phase 2) - December 6, 2025
### Week 1: Critical Bug Fixes (COMPLETE)
#### 1. Tile Cache System Fix
**Files:** `src/app/gfx/render/tilemap.h`, `src/app/gfx/render/tilemap.cc`
- Changed `TileCache::CacheTile()` from `Bitmap&&` (move) to `const Bitmap&` (copy)
- Re-enabled tile cache usage in `RenderTile()` and `RenderTilesBatch()`
- Root cause: `std::move()` invalidated Bitmap surface pointers causing segfaults
#### 2. Centralized Zoom Constants
**Files:** `src/app/editor/overworld/overworld_editor.h`, `overworld_navigation.cc`, `map_properties.cc`
- Added `kOverworldMinZoom`, `kOverworldMaxZoom`, `kOverworldZoomStep` constants
- Updated all zoom controls to use consistent limits (0.1 - 5.0x, 0.25 step)
- Scroll wheel zoom intentionally disabled (reserved for canvas navigation)
#### 3. Live Preview Re-enabled
**File:** `src/app/editor/overworld/overworld_editor.cc`
- Re-enabled `UpdateBlocksetWithPendingTileChanges()` with proper guards
- Live preview now shows tile16 edits on main map before committing
#### 4. Entity Hit Detection Scaling
**Files:** `src/app/editor/overworld/entity.h`, `entity.cc`, `overworld_entity_renderer.cc`
- Added `float scale = 1.0f` parameter to `IsMouseHoveringOverEntity()` and `MoveEntityOnGrid()`
- Entity interaction now correctly scales with canvas zoom level
### Week 3: Tile16 Editor Polish (COMPLETE)
#### 1. Tile16 Editor Window Restoration
**Files:** `src/app/editor/overworld/overworld_editor.cc`
- Restored Tile16 Editor as standalone window with `ImGuiWindowFlags_MenuBar`
- Draws directly in `Update()` when `show_tile16_editor_` is true
- Accessible via Ctrl+T or toolbar toggle
#### 2. SNES Palette Offset Fix
**File:** `src/app/editor/overworld/tile16_editor.cc`
- Fixed off-by-one error in `SetPaletteWithTransparent()` calls
- Added +1 offset so pixel value N maps to sub-palette color N (not N-1)
- Applied to: `tile8_preview_bmp_`, `current_tile16_bmp_`, `current_gfx_individual_[]`
#### 3. Palette Remapping for Tile8 Source Canvas
**File:** `src/app/editor/overworld/tile16_editor.cc`
- Added `CreateRemappedPaletteForViewing()` function
- Source canvas now responds to palette button selection (0-7)
- Remaps all pixel values to user-selected palette row regardless of encoding
#### 4. Visual Palette/Sheet Indicator
**File:** `src/app/editor/overworld/tile16_editor.cc`
- Added sheet indicator (S0-S7) next to tile8 preview
- Tooltip shows sheet index, encoded palette row, and encoding explanation
- Helps users understand which graphics use which palette regions
#### 5. Data Analysis Diagnostic
**Files:** `src/app/editor/overworld/tile16_editor.h`, `tile16_editor.cc`
- Added `AnalyzeTile8SourceData()` diagnostic function
- "Analyze Data" button in UI outputs detailed format/palette info to log
- Shows pixel value distribution, palette state, and remapping explanation
### Toolbar & Scratch Space Simplification
#### 1. Unified Scratch Space
**Files:** `src/app/editor/overworld/overworld_editor.h`, `scratch_space.cc`
- Simplified from 4 slots to single unified workspace
- Renamed `ScratchSpaceSlot` to `ScratchSpace`
- Updated all UI and logic to operate on single instance
#### 2. Toolbar Panel Toggles
**File:** `src/app/editor/overworld/overworld_toolbar.cc`
- Added "Panels" dropdown with toggle buttons for all editor panels
- Panels: Tile16 Editor, Tile16 Selector, Tile8 Selector, Area Graphics, etc.
- Uses PanelManager for visibility state persistence
---
## Completed Work (Phase 1)
1. **README.md** (`src/app/editor/overworld/README.md`)
- Architecture overview with component diagram
- File organization and responsibilities
- Tile16 editing workflow with palette coordination
- ZScustom feature documentation
- Save system order and dependencies
- Testing guidance
2. **Tile16Editor Documentation** (`tile16_editor.h`)
- Extensive header block explaining pending changes system
- Palette coordination with sheet-to-palette mapping table
- Method-level documentation for all key APIs
3. **ZScustom Version Helper** (`overworld_version_helper.h`)
- Feature matrix showing version capabilities
- Usage examples and upgrade workflow
- ROM marker location documentation
4. **Overworld Data Layer** (`overworld.h`)
- Save order dependencies and groupings
- Method organization by functionality
- Testing guidance for save operations
5. **OverworldEditor Organization** (`overworld_editor.h`)
- Section comments delineating subsystems
- Member variable groupings by purpose
- Method organization by functionality
---
## Critical Issues Identified
### 1. Tile Cache System - DISABLED DUE TO CRASHES
**Location:** `src/app/gfx/render/tilemap.cc`, `src/app/gui/canvas/canvas.cc`
**Problem:** The tile cache uses `std::move()` which invalidates Bitmap surface pointers, causing segmentation faults.
**Evidence from code:**
```cpp
// tilemap.cc:67-68
// Note: Tile cache disabled to prevent std::move() related crashes
// canvas.cc:768-769
// CRITICAL FIX: Disable tile cache system to prevent crashes
```
**Impact:**
- Performance degradation - tiles are re-rendered each frame
- The `TileCache` struct in `tilemap.h` is essentially dead code
- Memory for the LRU cache is allocated but never used effectively
**Recommended Fix:**
- Option A: Use `std::shared_ptr<Bitmap>` instead of `std::unique_ptr` to allow safe pointer sharing
- Option B: Copy bitmaps into cache instead of moving them
- Option C: Implement a texture-ID based cache that doesn't require pointer stability
### 2. Zoom/Pan Implementation is Fragmented
**Location:** `overworld_navigation.cc`, `map_properties.cc`, Canvas class
**Problem:** Zoom/pan is implemented in multiple places with inconsistent behavior:
```cpp
// overworld_navigation.cc - Main zoom implementation
void OverworldEditor::ZoomIn() {
float new_scale = std::min(5.0f, ow_map_canvas_.global_scale() + 0.25f);
ow_map_canvas_.set_global_scale(new_scale);
}
// map_properties.cc - Context menu zoom (different limits!)
canvas.set_global_scale(std::max(0.25f, canvas.global_scale() - 0.25f));
canvas.set_global_scale(std::min(2.0f, canvas.global_scale() + 0.25f));
```
**Issues:**
- Inconsistent max zoom limits (2.0f vs 5.0f)
- No smooth zoom (scroll wheel support mentioned but not implemented)
- No zoom-to-cursor functionality
- Pan only works with middle mouse button
**Recommended Improvements:**
1. Centralize zoom/pan in a `CanvasNavigationController` class
2. Add scroll wheel zoom with zoom-to-cursor
3. Implement keyboard shortcuts (Ctrl+Plus/Minus, Home to reset)
4. Add mini-map navigation overlay for large canvases
### 3. UpdateBlocksetWithPendingTileChanges is Disabled
**Location:** `overworld_editor.cc:262-264`
```cpp
// TODO: Re-enable after fixing crash
// Update blockset atlas with any pending tile16 changes for live preview
// UpdateBlocksetWithPendingTileChanges();
```
**Impact:**
- Live preview of tile16 edits doesn't work on the main map
- Users must commit changes to see them on the overworld
### 4. Entity Interaction Issues
**Location:** `entity.cc`, `overworld_entity_interaction.cc`
**Problems:**
- Hardcoded 16x16 entity hit detection doesn't scale with zoom
- Entity popups use `static` variables for state (potential bugs with multiple popups)
- No undo/redo for entity operations
**Evidence:**
```cpp
// entity.cc:33-34
return mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16;
// Should use: entity.x_ + 16 * scale
```
### 5. V3 Settings Panel is Incomplete
**Location:** `overworld_editor.cc:1663-1670`
```cpp
void OverworldEditor::DrawV3Settings() {
// TODO: Implement v3 settings UI
// Could include:
// - Custom map size toggles
// ...
}
```
**Missing Features:**
- Per-area animated GFX selection
- Subscreen overlay configuration
- Custom tile GFX groups
- Mosaic effect controls
---
## Architecture Analysis
### Current Code Metrics
| File | Lines | Responsibility |
|------|-------|----------------|
| `overworld_editor.cc` | 3,208 | God class - does too much |
| `tile16_editor.cc` | 3,048 | Large but focused |
| `map_properties.cc` | 1,755 | Mixed UI concerns |
| `entity.cc` | 716 | Entity popup rendering |
| `scratch_space.cc` | 417 | Well-isolated |
### God Class Symptoms in OverworldEditor
The `OverworldEditor` class handles:
1. Canvas drawing and interaction
2. Tile painting and selection
3. Entity management
4. Graphics loading and refresh
5. Map property editing
6. Undo/redo for painting
7. Scratch space management
8. ZScustom ASM patching
9. Keyboard shortcuts
10. Deferred texture creation
**Recommendation:** Extract into focused subsystem classes:
- `OverworldCanvasController` - Canvas drawing, zoom/pan, tile painting
- `OverworldEntityController` - Entity CRUD, rendering, interaction
- `OverworldGraphicsController` - Loading, refresh, palette coordination
- `OverworldUndoManager` - Undo/redo stack management
- Keep `OverworldEditor` as thin orchestrator
### Panel System Redundancy
Each panel in `panels/` is a thin wrapper that calls back to `OverworldEditor`:
```cpp
// tile16_selector_panel.cc
void Tile16SelectorPanel::Draw(bool* p_open) {
editor_->DrawTile16Selector(); // Just delegates
}
```
**Recommendation:** Move drawing logic into panels, reducing coupling to editor.
---
## Future Feature Proposals
### 1. Enhanced Zoom/Pan System
**Priority:** High
**Effort:** Medium (2-3 days)
Features:
- Scroll wheel zoom centered on cursor
- Keyboard shortcuts (Ctrl+0 reset, Ctrl+Plus/Minus zoom)
- Touch gesture support for tablet users
- Mini-map overlay for navigation
- Smooth animated zoom transitions
Implementation approach:
```cpp
class CanvasNavigationController {
public:
void HandleScrollZoom(float delta, ImVec2 mouse_pos);
void HandlePan(ImVec2 delta);
void ZoomToFit(ImVec2 content_size);
void ZoomToSelection(ImVec2 selection_rect);
void CenterOn(ImVec2 world_position);
float current_zoom() const;
ImVec2 scroll_offset() const;
};
```
### 2. Better Toolset in Canvas Toolbar
**Priority:** High
**Effort:** Medium (2-3 days)
Current toolbar only has Mouse/Paint toggle. Proposed additions:
| Tool | Icon | Behavior |
|------|------|----------|
| Select | Box | Rectangle selection for multi-tile ops |
| Brush | Brush | Current paint behavior |
| Fill | Bucket | Flood fill with tile16 |
| Eyedropper | Dropper | Pick tile16 from map |
| Eraser | Eraser | Paint with empty tile |
| Line | Line | Draw straight lines of tiles |
| Rectangle | Rect | Draw filled/outline rectangles |
Implementation:
```cpp
enum class OverworldTool {
Select,
Brush,
Fill,
Eyedropper,
Eraser,
Line,
Rectangle
};
class OverworldToolManager {
void SetTool(OverworldTool tool);
void HandleMouseDown(ImVec2 pos);
void HandleMouseDrag(ImVec2 pos);
void HandleMouseUp(ImVec2 pos);
void RenderPreview();
};
```
### 3. Tile16 Editor Improvements
**Priority:** Medium
**Effort:** Medium (2-3 days)
Current issues:
- No visual indication of which tile8 positions are filled
- Palette preview doesn't show all colors clearly
- Can't preview tile on actual map before committing
Proposed improvements:
- Quadrant highlight showing which positions are filled
- Color swatch grid for current palette
- "Preview on Map" toggle that temporarily shows edited tile
- Tile history/favorites for quick access
- Copy/paste between tile16s
### 4. Multi-Tile Operations
**Priority:** Medium
**Effort:** High (1 week)
Current rectangle selection only works for painting. Expand to:
- Copy selection to clipboard
- Paste clipboard with preview
- Rotate/flip selection
- Save selection as scratch slot
- Search for tile patterns
### 5. Entity Visualization Improvements
**Priority:** Low
**Effort:** Medium (2-3 days)
Current: Simple 16x16 colored rectangles
Proposed:
- Sprite previews for entities (already partially implemented)
- Connection lines for entrance/exit pairs
- Highlight related entities on hover
- Entity layer toggle visibility
- Entity search/filter by type
### 6. Map Comparison/Diff Tool
**Priority:** Low
**Effort:** High (1 week)
For ZScustom testing:
- Side-by-side view of two ROM versions
- Highlight differences in tiles/entities
- Compare map properties
- Export diff report
---
## Testing Recommendations
### Manual Test Cases for Phase 2
1. **Tile16 Editing Round-Trip**
- Edit a tile16, commit, save ROM, reload, verify persistence
- Edit multiple tile16s, discard some, commit others
- Verify palette colors match across all views
2. **Zoom/Pan Stress Test**
- Zoom to max/min while painting
- Pan rapidly and verify no visual artifacts
- Test at 0.25x, 1x, 2x, 4x zoom levels
3. **Entity Operations**
- Create/move/delete each entity type
- Verify entity positions survive save/load
- Test entity interaction at different zoom levels
4. **ZScustom Feature Regression**
- Test vanilla ROM, verify graceful degradation
- Test v2 ROM, verify BG colors work
- Test v3 ROM, verify all features available
- Upgrade vanilla→v3, verify all features activate
5. **Save System Integrity**
- Save with each component flag disabled individually
- Verify no corruption of unmodified data
- Test save after large edits (fill entire map)
---
## Phase 2 Roadmap Status
### Week 1: Critical Bug Fixes ✅ COMPLETE
1. ✅ Fix tile cache system (copy instead of move)
2. ✅ Implement consistent zoom limits (centralized constants)
3. ✅ Re-enable UpdateBlocksetWithPendingTileChanges with fix
4. ✅ Fix entity hit detection with zoom scaling
### Week 2: Toolset Improvements (NEXT)
1. ⏳ Implement eyedropper tool
2. ⏳ Implement flood fill tool
3. ⏳ Add eraser tool
4. ⏳ Enhance toolbar UI
### Week 3: Tile16 Editor Polish ✅ COMPLETE
1. ✅ Tile16 Editor window restoration (menu bar support)
2. ✅ SNES palette offset fix (+1 for correct color mapping)
3. ✅ Palette remapping for tile8 source canvas viewing
4. ✅ Visual sheet/palette indicator with tooltip
5. ✅ Data analysis diagnostic function
### Week 4: Architecture Cleanup
1. ⏳ Extract CanvasNavigationController
2. ⏳ Extract OverworldToolManager
3. ⏳ Move panel drawing logic into panels
4. ⏳ Add comprehensive unit tests
---
## Files Modified in Phase 2
| File | Change Type |
|------|-------------|
| `src/app/gfx/render/tilemap.h` | Bug fix - tile cache copy semantics |
| `src/app/gfx/render/tilemap.cc` | Bug fix - re-enabled tile cache |
| `src/app/editor/overworld/overworld_editor.h` | Added zoom constants, scratch space simplification |
| `src/app/editor/overworld/overworld_editor.cc` | Tile16 Editor window, panel registration, live preview |
| `src/app/editor/overworld/overworld_navigation.cc` | Centralized zoom constants |
| `src/app/editor/overworld/map_properties.cc` | Consistent zoom limits |
| `src/app/editor/overworld/entity.h` | Scale parameter for hit detection |
| `src/app/editor/overworld/entity.cc` | Scaled entity interaction |
| `src/app/editor/overworld/overworld_entity_renderer.cc` | Pass scale to entity functions |
| `src/app/editor/overworld/tile16_editor.h` | Added palette remapping, analysis functions |
| `src/app/editor/overworld/tile16_editor.cc` | Palette fixes, remapping, visual indicators, diagnostics |
| `src/app/editor/overworld/overworld_toolbar.cc` | Panel toggles, simplified scratch space |
| `src/app/editor/overworld/scratch_space.cc` | Unified single scratch space |
| `src/app/gui/canvas/canvas.cc` | Updated tile cache comment |
| `src/app/editor/editor_library.cmake` | Removed deleted panel file |
## Files Modified in Phase 1
| File | Change Type |
|------|-------------|
| `src/app/editor/overworld/README.md` | Created |
| `src/app/editor/overworld/tile16_editor.h` | Documentation |
| `src/app/editor/overworld/overworld_editor.h` | Documentation |
| `src/zelda3/overworld/overworld.h` | Documentation |
| `src/zelda3/overworld/overworld_version_helper.h` | Documentation |
Phase 1: Documentation only. Phase 2: Functional bug fixes and feature improvements.
---
## Key Contacts and Resources
- **Codebase Owner:** scawful
- **Related Documentation:**
- [EditorManager Architecture](H2-editor-manager-architecture.md)
- [Feature Parity Analysis](H3-feature-parity-analysis.md)
- [Composite Layer System](composite-layer-system.md)
- **External References:**
- [ZScream GitHub Wiki](https://github.com/Zarby89/ZScreamDungeon/wiki)
- [ALTTP ROM Map](https://alttp.mymm1.com/wiki/)
---
## Appendix: Code Snippets for Reference
### Tile Cache Fix Proposal
```cpp
// Option A: Use shared_ptr for safe sharing
struct TileCache {
std::unordered_map<int, std::shared_ptr<Bitmap>> cache_;
std::shared_ptr<Bitmap> GetTile(int tile_id) {
auto it = cache_.find(tile_id);
return (it != cache_.end()) ? it->second : nullptr;
}
void CacheTile(int tile_id, const Bitmap& bitmap) {
cache_[tile_id] = std::make_shared<Bitmap>(bitmap); // Copy, not move
}
};
```
### Centralized Zoom Handler
```cpp
void CanvasNavigationController::HandleScrollZoom(float delta, ImVec2 mouse_pos) {
float old_scale = current_zoom_;
float new_scale = std::clamp(current_zoom_ + delta * 0.1f, kMinZoom, kMaxZoom);
// Zoom centered on cursor
ImVec2 world_pos = ScreenToWorld(mouse_pos);
current_zoom_ = new_scale;
ImVec2 new_screen_pos = WorldToScreen(world_pos);
scroll_offset_ += (mouse_pos - new_screen_pos);
}
```
---
*End of Handoff Document*

View File

@@ -0,0 +1,36 @@
# YAZE Test Binary UX Proposals (yaze_test / ctest Integration)
Status: IN_PROGRESS
Owner: ai-infra-architect
Created: 2025-12-01
Last Reviewed: 2025-12-01
Next Review: 2025-12-08
Board: docs/internal/agents/coordination-board.md (2025-12-01 ai-infra-architect z3ed CLI UX/TUI Improvement Proposals)
## Summary
- Make the test binaries and ctest entry points friendlier for humans and agents: clearer filters, artifacts, and machine-readable outputs.
- Provide a first-class manifest of suites/labels/requirements and a consistent way to supply ROM/AI/headless settings.
- Reduce friction when iterating locally (fast presets, targeted subsets) and when collecting results for automation/CI.
## Observations
- The unified `yaze_test` binary emits gtest text only; ctest wraps it but lacks machine-readable summaries unless parsed. Agents currently scrape stdout.
- Suite/label requirements (ROM path, AI runtime, headless display) are implicit; misconfiguration silently skips or hard-fails without actionable guidance.
- Filter UX is split: gtest filters vs ctest `-L/-R`; no single recommended entry that also records artifacts/logs for failures.
- Artifacts (logs, screenshots, recordings) from GUI/agent tests are not consistently emitted or linked from results.
- Preset coupling is manual; users must remember which CMake preset enables ROM/AI/headless options. No quick “fast subset” toggle inside the binary.
## Improvement Proposals
- **Manifest & discovery**: Generate a JSON manifest (per build) listing tests, labels, requirements (ROM path, AI runtime), and expected artifacts. Expose via `yaze_test --export-manifest <path>` and `ctest -T mem`-style option. Agents can load it instead of scraping.
- **Structured output**: Add `--output-format {text,json,junit}` to `yaze_test` to emit summaries (pass/fail, duration, seed, artifacts) in one file; default to text for humans, JSON for automation. Wire ctest to collect the JSON and place it in a predictable directory.
- **Requirements gating**: On startup, detect missing ROM/AI/headless support and fail fast with actionable messages and suggested CMake flags/env (e.g., `YAZE_ENABLE_ROM_TESTS`, `YAZE_TEST_ROM_PATH`). Offer a `--mock-rom-ok` mode to downgrade ROM tests when a mock is acceptable.
- **Filters & subsets**: Provide a unified front-end flag set (`--label stable|gui|rom_dependent|experimental`, `--gtest_filter`, `--list`) that internally routes to gtest/labels so humans/agents dont guess. Add `--shard <N/M>` for parallel runs.
- **Artifacts & logs**: Standardize artifact output location (`build/artifacts/tests/<run-id>/`) and name failing-test logs/screenshots accordingly. Emit paths in JSON output. Ensure GUI/agent recordings are captured when labels include `gui` or `experimental`.
- **Preset hints**: Print which CMake preset was used to build the binary and whether ROM/AI options are compiled in. Add `--recommend-preset` helper to suggest `mac-test/mac-ai/mac-dev` based on requested labels.
- **Headless helpers**: Add `--headless-check` to validate SDL/display availability and exit gracefully with instructions to use headless mode; integrate into ctest label defaults.
- **Exit codes**: Ensure non-zero exit codes for any test failure and distinct codes for configuration failures vs runtime failures to simplify automation.
## Exit Criteria (for this scope)
- `yaze_test` (and/or a small wrapper) can emit JSON/JUnit summaries and a manifest without stdout scraping.
- Clear, actionable errors when requirements (ROM/AI/display) are missing, with suggested flags/presets.
- Artifacts for failing GUI/agent tests are written to predictable paths and referenced in structured output.
- Unified filter/label interface documented and consistent with ctest usage.

View File

@@ -0,0 +1,34 @@
# YAZE Desktop App UX Proposals (Panels, Flags, ROM Loading)
Status: IN_PROGRESS
Owner: ai-infra-architect
Created: 2025-12-01
Last Reviewed: 2025-12-01
Next Review: 2025-12-08
Board: docs/internal/agents/coordination-board.md (2025-12-01 ai-infra-architect z3ed CLI UX/TUI Improvement Proposals)
## Summary
- Improve the desktop ImGui/SDL app startup, profiles, ROM onboarding, and layout/panel ergonomics separate from the z3ed CLI/TUI.
- Make service status (agent control, HTTP/collab, AI runtime) visible and scriptable; reduce brittle flag combinations.
- Provide safe ROM handling (recent list, mock fallback, hot-swap guard) and shareable layout presets for overworld/dungeon/graphics/testing workflows.
## Observations
- Startup relies on ad-hoc flags; no bundled profiles (dev/AI/ROM/viewer) and no in-app profile selector. Users must memorize combinations to enable agent control, autosave, collaboration, or stay read-only.
- ROM onboarding is brittle: errors surface via stderr; no first-run picker, recent ROM list, autodetect of common paths, or “use mock ROM” fallback; hot-swapping ROMs risks state loss without confirmation.
- Panel/layout presets are implicit—users rebuild layouts each session. No first-class presets for overworld, dungeon, graphics/palette, testing/doctor, or AI console. Export/import of layouts is absent.
- Runtime status is opaque: no unified HUD showing ROM title/version, profile/layout, mock-ROM flag, active services (agent/collab/HTTP), autosave status, or feature flags.
- Configuration surfaces are fragmented; parity between CLI flags and in-app toggles is unclear, making automation brittle.
## Improvement Proposals
- **Profiles & bundled flags**: Add `--profile {dev, ai, rom, viewer, wasm}` with an in-app profile picker. Each profile sets sane defaults (agent control on/off, autosave cadence, mock-ROM allowed, telemetry, collaboration) and selects a default layout preset. Persist per-user.
- **ROM onboarding & recovery**: Show a startup ROM picker with recent list and autodetect (`./zelda3.sfc`, `assets/zelda3.sfc`, env var). Validate and, on failure, offer retry/browse and “Use mock ROM” instead of exiting. Add `--rom-prompt` to force picker even when a path is supplied for shared environments.
- **Layout presets & persistence**: Ship named presets (Overworld Editing, Dungeon Editing, Graphics/Palette, Testing/Doctor, AI Agent Console). Provide `--layout <name>` and an in-app switcher; persist per profile and allow export/import for handoff.
- **Unified status HUD**: Add an always-visible status bar/dashboard summarizing ROM info, profile/layout, service state (agent/HTTP/collab), mock-ROM flag, autosave recency, and feature flags. Expose the same state via a lightweight JSON status endpoint/command for automation.
- **Safer ROM/context switching**: On ROM change, prompt with unsaved-change summary and autosave option; offer “clone to temp” for experiments; support `--readonly-rom` for analysis sessions.
- **Config discoverability**: Centralize runtime settings (ROM path, profile, feature toggles, autosave cadence, telemetry) in a single pane that mirrors CLI flags. Add `--export-config`/`--import-config` to script setups and share configurations.
## Exit Criteria (for this scope)
- Profiles and layout presets are selectable at startup and in-app, with persisted choices.
- ROM onboarding flow handles missing/invalid ROMs gracefully with mock fallback and recent list.
- Status HUD (and JSON endpoint/command) surfaces ROM/profile/service state for humans and automation.
- Layouts are exportable/importable; presets cover main workflows (overworld, dungeon, graphics, testing, AI console).

View File

@@ -0,0 +1,218 @@
# YAZE Architecture Documentation
This directory contains detailed architectural documentation for the YAZE (Yet Another Zelda3 Editor) codebase. These documents describe the design patterns, component interactions, and best practices used throughout the project.
## Core Architecture Guides
### ROM and Game Data
- **[rom_architecture.md](rom_architecture.md)** - Decoupled ROM architecture
- Generic SNES ROM container (`src/rom/`)
- Zelda3-specific GameData struct (`src/zelda3/game_data.h`)
- Editor integration and GameData propagation
- Transaction-based ROM access patterns
- Migration guide from old architecture
### Graphics System
- **[graphics_system_architecture.md](graphics_system_architecture.md)** - Complete guide to the graphics rendering pipeline
- Arena resource manager for 223 graphics sheets
- Bitmap class and texture management
- LC-LZ2 compression/decompression pipeline
- Rendering workflow from ROM loading to display
- Canvas interactions and drawing operations
- Best practices for graphics modifications
### UI and Layout System
- **[editor_card_layout_system.md](editor_card_layout_system.md)** - Card-based editor and layout architecture
- EditorCardRegistry for centralized card management
- LayoutManager for ImGui DockBuilder layouts
- LayoutPresets for default per-editor configurations
- VSCode-style Activity Bar and Side Panel
- Agent UI system (multi-agent sessions, pop-out cards)
- Card validation system for development debugging
- Session-aware card ID prefixing
- Workspace preset save/load
- **[layout-designer.md](layout-designer.md)** - WYSIWYG layout designer (panel & widget modes), integration points, and current limitations
### Editors
#### Dungeon Editor
- **[dungeon_editor_system.md](dungeon_editor_system.md)** - Architecture of the dungeon room editor
- Component-based design (DungeonEditorV2, DungeonObjectEditor, DungeonCanvasViewer)
- Object editing workflow (insert, delete, move, resize, layer operations)
- Coordinate systems and conversion methods
- Best practices for extending editor modes
- Contributing guidelines for new features
#### Overworld Editor
- **[overworld_editor_system.md](overworld_editor_system.md)** - Architecture of the overworld map editor
- Overworld system structure (Light World, Dark World, Special Areas)
- Map properties and large map configuration
- Entity handling (sprites, entrances, exits, items)
- Deferred texture loading for performance
- ZSCustomOverworld integration
### Data Structures & Persistence
- **[overworld_map_data.md](overworld_map_data.md)** - Overworld map internal structure
- OverworldMap data model (tiles, graphics, properties)
- ZSCustomOverworld custom properties and storage
- Loading and saving process
- Multi-area map configuration
- Overlay system for interactive map layers
- **[room_data_persistence.md](room_data_persistence.md)** - Dungeon room loading and saving
- ROM pointer table system
- Room decompression and object parsing
- Multithreaded bulk loading (up to 8 threads)
- Room size calculation for safe editing
- Repointing logic for data overflow
- Bank boundary considerations
### Systems & Utilities
- **[undo_redo_system.md](undo_redo_system.md)** - Undo/redo architecture for editors
- Snapshot-based undo implementation
- DungeonObjectEditor undo stack
- DungeonEditorSystem coordinator integration
- Batch operation handling
- Best practices for state management
- **[zscustomoverworld_integration.md](zscustomoverworld_integration.md)** - ZSCustomOverworld v3 support
- Multi-area map sizing (1x1, 2x1, 1x2, 2x2)
- Custom graphics and palette per-map
- Visual effects (mosaic, subscreen overlay)
- ASM patching and ROM version detection
- Feature-specific UI adaptation
## Quick Reference by Component
### ROM (`src/rom/`)
- See: [rom_architecture.md](rom_architecture.md)
- Key Classes: Rom, ReadTransaction, WriteTransaction
- Key Files: `rom.h`, `rom.cc`, `transaction.h`, `snes.h`
### Game Data (`src/zelda3/game_data.h`)
- See: [rom_architecture.md](rom_architecture.md)
- Key Struct: GameData
- Key Functions: LoadGameData(), SaveGameData()
### Graphics (`src/app/gfx/`)
- See: [graphics_system_architecture.md](graphics_system_architecture.md)
- Key Classes: Arena, Bitmap, SnesPalette, IRenderer
- Key Files: `resource/arena.h`, `core/bitmap.h`, `util/compression.h`
### Dungeon Editor (`src/app/editor/dungeon/`, `src/zelda3/dungeon/`)
- See: [dungeon_editor_system.md](dungeon_editor_system.md), [room_data_persistence.md](room_data_persistence.md)
- Key Classes: DungeonEditorV2, DungeonObjectEditor, Room, DungeonRoomLoader
- Key Files: `dungeon_editor_v2.h`, `dungeon_object_editor.h`, `room.h`
### Overworld Editor (`src/app/editor/overworld/`, `src/zelda3/overworld/`)
- See: [overworld_editor_system.md](overworld_editor_system.md), [overworld_map_data.md](overworld_map_data.md)
- Key Classes: OverworldEditor, Overworld, OverworldMap, OverworldEntityRenderer
- Key Files: `overworld_editor.h`, `overworld.h`, `overworld_map.h`
### Undo/Redo
- See: [undo_redo_system.md](undo_redo_system.md)
- Key Classes: DungeonObjectEditor (UndoPoint structure)
- Key Files: `dungeon_object_editor.h`
### UI/Layout System (`src/app/editor/system/`, `src/app/editor/ui/`)
- See: [editor_card_layout_system.md](editor_card_layout_system.md)
- Key Classes: EditorCardRegistry, LayoutManager, LayoutPresets, UICoordinator
- Key Files: `editor_card_registry.h`, `layout_manager.h`, `layout_presets.h`, `ui_coordinator.h`
### Agent UI System (`src/app/editor/agent/`)
- See: [editor_card_layout_system.md](editor_card_layout_system.md#agent-ui-system)
- Key Classes: AgentUiController, AgentSessionManager, AgentSidebar, AgentChatCard, AgentChatView
- Key Files: `agent_ui_controller.h`, `agent_session.h`, `agent_sidebar.h`, `agent_chat_card.h`
### ZSCustomOverworld
- See: [zscustomoverworld_integration.md](zscustomoverworld_integration.md), [overworld_map_data.md](overworld_map_data.md)
- Key Classes: OverworldMap, Overworld, OverworldVersionHelper
- Key Files: `overworld.cc`, `overworld_map.cc`, `overworld_version_helper.h`
## Design Patterns Used
### 1. Modular/Component-Based Design
Large systems are decomposed into smaller, single-responsibility classes:
- Example: DungeonEditorV2 (coordinator) → DungeonRoomLoader, DungeonCanvasViewer, DungeonObjectEditor (components)
- See: [dungeon_editor_system.md](dungeon_editor_system.md#high-level-overview)
### 2. Callback-Based Communication
Components communicate without circular dependencies:
- Example: ObjectEditorCard receives callbacks from DungeonObjectEditor
- See: [dungeon_editor_system.md](dungeon_editor_system.md#best-practices-for-contributors)
### 3. Singleton Pattern
Global resource management via Arena:
- Example: `gfx::Arena::Get()` for all graphics sheet access
- See: [graphics_system_architecture.md](graphics_system_architecture.md#core-components)
### 4. Progressive/Deferred Loading
Heavy operations performed asynchronously to maintain responsiveness:
- Example: Graphics sheets loaded on-demand with priority queue
- Example: Overworld map textures created when visible
- See: [overworld_editor_system.md](overworld_editor_system.md#deferred-loading)
### 5. Snapshot-Based Undo/Redo
State snapshots before destructive operations:
- Example: UndoPoint structure captures entire room object state
- See: [undo_redo_system.md](undo_redo_system.md)
### 6. Card-Based UI Architecture
VSCode-style dockable card system with centralized registry:
- Example: EditorCardRegistry manages all editor window metadata
- Example: LayoutManager uses DockBuilder to arrange cards
- See: [editor_card_layout_system.md](editor_card_layout_system.md)
### 7. Multi-Session State Management
Support for multiple concurrent sessions (ROMs, agents):
- Example: AgentSessionManager maintains multiple agent sessions with shared context
- Example: Card IDs prefixed with session ID for isolation
- See: [editor_card_layout_system.md](editor_card_layout_system.md#session-aware-card-ids)
## Contributing Guidelines
When adding new functionality:
1. **Follow Existing Patterns**: Use component-based design, callbacks, and RAII principles
2. **Update Documentation**: Add architectural notes to relevant documents
3. **Write Tests**: Create unit tests in `test/unit/` for new components
4. **Use Proper Error Handling**: Employ `absl::Status` and `absl::StatusOr<T>`
5. **Coordinate with State**: Use Arena/Singleton patterns for shared state
6. **Enable Undo/Redo**: Snapshot state before destructive operations
7. **Defer Heavy Work**: Use texture queues and async loading for performance
For detailed guidelines, see the **Best Practices** sections in individual architecture documents.
## Related Documents
- **[../../CLAUDE.md](../../CLAUDE.md)** - Project overview and development guidelines
- **[../../README.md](../../README.md)** - Project introduction
- **[../release-checklist.md](../release-checklist.md)** - Release process documentation
## Architecture Evolution
This architecture reflects the project's maturity at the time of documentation. Key evolution points:
- **DungeonEditorV2**: Replacement for older monolithic DungeonEditor with proper component delegation
- **Arena System**: Centralized graphics resource management replacing scattered SDL operations
- **ZSCustomOverworld v3 Support**: Extended OverworldMap and Overworld to support expanded ROM features
- **Progressive Loading**: Deferred texture creation to prevent UI freezes during large ROM loads
- **EditorCardRegistry**: VSCode-style card management replacing ad-hoc window visibility tracking
- **Multi-Agent Sessions**: Support for concurrent AI agents with shared context and pop-out cards
- **Unified Visibility Management**: Single source of truth for component visibility (emulator, cards)
## Status and Maintenance
All architecture documents are maintained alongside the code:
- Documents are reviewed during code reviews
- Architecture changes require documentation updates
- Status field indicates completeness (Draft/In Progress/Complete)
- Last updated timestamp indicates freshness
For questions about architecture decisions, consult:
1. Relevant architecture document
2. Source code comments
3. Commit history for design rationale

View File

@@ -0,0 +1,90 @@
# Collaboration Framework
**Status**: ACTIVE
**Mission**: Accelerate `yaze` development through strategic division of labor between Architecture and Automation specialists.
**See also**: [personas.md](./personas.md) for detailed role definitions.
---
## Team Structure
### Architecture Team (System Specialists)
**Focus**: Core C++, Emulator logic, UI systems, Build architecture.
**Active Personas**:
* `backend-infra-engineer`: CMake, packaging, CI plumbing.
* `snes-emulator-expert`: Emulator core, CPU/PPU logic, debugging.
* `imgui-frontend-engineer`: UI rendering, ImGui widgets.
* `zelda3-hacking-expert`: ROM data, gameplay logic.
**Responsibilities**:
* Diagnosing complex C++ compilation/linker errors.
* Designing system architecture and refactoring.
* Implementing core emulator features.
* Resolving symbol conflicts and ODR violations.
### Automation Team (Tooling Specialists)
**Focus**: Scripts, CI Optimization, CLI tools, Test Harness.
**Active Personas**:
* `ai-infra-architect`: Agent infrastructure, CLI/TUI, Network layer.
* `test-infrastructure-expert`: Test harness, flake triage, gMock.
* `GEMINI_AUTOM`: General scripting, log analysis, quick prototyping.
**Responsibilities**:
* Creating helper scripts (`scripts/`).
* Optimizing CI/CD pipelines (speed, caching).
* Building CLI tools (`z3ed`).
* Automating repetitive tasks (formatting, linting).
---
## Collaboration Protocol
### 1. Work Division Guidelines
#### **For Build Failures**:
| Failure Type | Primary Owner | Support Role |
|--------------|---------------|--------------|
| Compiler errors (Logic) | Architecture | Automation (log analysis) |
| Linker errors (Symbols) | Architecture | Automation (tracking scripts) |
| CMake configuration | Architecture | Automation (preset validation) |
| CI Infrastructure | Automation | Architecture (requirements) |
#### **For Code Quality**:
| Issue Type | Primary Owner | Support Role |
|------------|---------------|--------------|
| Formatting/Linting | Automation | Architecture (complex cases) |
| Logic/Security | Architecture | Automation (scanning tools) |
### 2. Handoff Process
When passing work between roles:
1. **Generate Context**: Use `z3ed agent handoff` to package your current state.
2. **Log Intent**: Post to [coordination-board.md](./coordination-board.md).
3. **Specify Deliverables**: Clearly state what was done and what is next.
**Example Handoff**:
```
### 2025-11-20 snes-emulator-expert handoff
- TASK: PPU Sprite Rendering (Phase 1)
- HANDOFF TO: test-infrastructure-expert
- DELIVERABLES:
- Implemented 8x8 sprite fetching in `ppu.cc`
- Added unit tests in `ppu_test.cc`
- REQUESTS:
- REQUEST → test-infrastructure-expert: Add regression tests for sprite priority flipping.
```
---
## Anti-Patterns to Avoid
### For Architecture Agents
-**Ignoring Automation**: Don't manually do what a script could do forever. Request tooling from the Automation team.
-**Siloing**: Don't keep architectural decisions in your head; document them.
### For Automation Agents
-**Over-Engineering**: Don't build a complex tool for a one-off task.
-**Masking Issues**: Don't script around a root cause; request a proper fix from Architecture.

View File

@@ -1,7 +1,7 @@
# Dungeon Editor System Architecture
**Status**: Draft
**Last Updated**: 2025-11-21
**Status**: Active
**Last Updated**: 2025-11-26
**Related Code**: `src/app/editor/dungeon/`, `src/zelda3/dungeon/`, `test/integration/dungeon_editor_v2_test.cc`, `test/e2e/dungeon_editor_smoke_test.cc`
## Overview
@@ -15,12 +15,32 @@ layout and delegates most logic to small components:
- **DungeonObjectInteraction** (`dungeon_object_interaction.{h,cc}`): Selection, multi-select, drag/move, copy/paste, and ghost previews on the canvas.
- **DungeonObjectSelector** (`dungeon_object_selector.{h,cc}`): Asset-browser style object picker and compact editors for sprites/items/doors/chests/properties (UI only).
- **ObjectEditorCard** (`object_editor_card.{h,cc}`): Unified object editor card.
- **DungeonEditorSystem** (`zelda3/dungeon/dungeon_editor_system.{h,cc}`): Planned orchestration layer for sprites/items/doors/chests/room properties (mostly stubbed today).
- **DungeonEditorSystem** (`zelda3/dungeon/dungeon_editor_system.{h,cc}`): Orchestration layer for sprites/items/doors/chests/room properties.
- **Room Model** (`zelda3/dungeon/room.{h,cc}`): Holds room metadata, objects, sprites, background buffers, and encodes objects back to ROM.
The editor acts as a coordinator: it wires callbacks between selector/interaction/canvas, tracks
tabbed room cards, and queues texture uploads through `gfx::Arena`.
## Important ImGui Patterns
**Critical**: The dungeon editor uses many `BeginChild`/`EndChild` pairs. Always ensure `EndChild()` is called OUTSIDE the if block:
```cpp
// ✅ CORRECT
if (ImGui::BeginChild("##RoomsList", ImVec2(0, 0), true)) {
// Draw content
}
ImGui::EndChild(); // ALWAYS called
// ❌ WRONG - causes ImGui state corruption
if (ImGui::BeginChild("##RoomsList", ImVec2(0, 0), true)) {
// Draw content
ImGui::EndChild(); // BUG: Not called when BeginChild returns false!
}
```
**Avoid duplicate rendering**: Don't call `RenderRoomGraphics()` in `DrawRoomGraphicsCard()` - it's already called in `DrawRoomTab()` when the room loads. The graphics card should only display already-rendered data.
## Data Flow (intended)
1. **Load**

View File

@@ -0,0 +1,103 @@
# Dungeon Object Tile Ordering Reference
This document describes the tile ordering patterns used in ALTTP dungeon object rendering, based on analysis of the ZScream reference implementation.
## Key Finding
**There is NO simple global rule** for when to use ROW-MAJOR vs COLUMN-MAJOR ordering. The choice is made on a **per-object basis** based on the visual appearance and extension direction of each object.
## Core Draw Patterns
ZScream uses five primary draw routine patterns:
### 1. RightwardsXbyY (Horizontal Extension)
- **Direction**: Extends rightward
- **Tile ordering**: Tiles fill each vertical slice, then move right
- **Usage**: Horizontal walls, rails, decorations (objects 0x00-0x5F range)
- **Pattern**: For 2x4, column 0 gets tiles 0-3, column 1 gets tiles 4-7
### 2. DownwardsXbyY (Vertical Extension)
- **Direction**: Extends downward
- **Tile ordering**: Tiles fill each horizontal slice, then move down
- **Usage**: Vertical walls, pillars, decorations (objects 0x60-0x98 range)
- **Pattern**: For 4x2, row 0 gets tiles 0-3, row 1 gets tiles 4-7
### 3. ArbitraryXByY (Generic Grid)
- **Direction**: No extension, fixed grid
- **Tile ordering**: Row-first (X outer loop, Y inner loop)
- **Usage**: Floors, generic rectangular objects
### 4. ArbitraryYByX (Column-First Grid)
- **Direction**: No extension, fixed grid
- **Tile ordering**: Column-first (Y outer loop, X inner loop)
- **Usage**: Beds, pillars, furnaces
### 5. Arbitrary4x4in4x4SuperSquares (Tiled Blocks)
- **Direction**: Both, repeating pattern
- **Tile ordering**: 4x4 blocks in 32x32 super-squares
- **Usage**: Floors, conveyor belts, ceiling blocks
## Object Groups and Their Patterns
| Object Range | Description | Pattern |
|--------------|-------------|---------|
| 0x00 | Rightwards 2x2 wall | RightwardsXbyY (COLUMN-MAJOR per slice) |
| 0x01-0x02 | Rightwards 2x4 walls | RightwardsXbyY (COLUMN-MAJOR per slice) |
| 0x03-0x06 | Rightwards 2x4 spaced | RightwardsXbyY (COLUMN-MAJOR per slice) |
| 0x60 | Downwards 2x2 wall | DownwardsXbyY (interleaved) |
| 0x61-0x62 | Downwards 4x2 walls | DownwardsXbyY (ROW-MAJOR per slice) |
| 0x63-0x64 | Downwards 4x2 both BG | DownwardsXbyY (ROW-MAJOR per slice) |
| 0x65-0x66 | Downwards 4x2 spaced | DownwardsXbyY (needs verification) |
## Verified Fixes
### Objects 0x61-0x62 (Left/Right Walls)
These use `DrawDownwards4x2_1to15or26` and require **ROW-MAJOR** ordering:
```
Row 0: tiles[0], tiles[1], tiles[2], tiles[3] at x+0, x+1, x+2, x+3
Row 1: tiles[4], tiles[5], tiles[6], tiles[7] at x+0, x+1, x+2, x+3
```
This was verified by comparing yaze output with ZScream and confirmed working.
## Objects Needing Verification
Before changing any other routines, verify against ZScream by:
1. Loading the same room in both editors
2. Comparing the visual output
3. Checking the specific object IDs in question
4. Only then updating the code
Objects that may need review:
- 0x65-0x66 (DrawDownwardsDecor4x2spaced4_1to16)
- Other 4x2/2x4 patterns
## Implementation Notes
### 2x2 Patterns
The 2x2 patterns use an interleaved ordering that produces identical visual results whether interpreted as row-major or column-major:
```
ZScream order: tiles[0]@(0,0), tiles[2]@(1,0), tiles[1]@(0,1), tiles[3]@(1,1)
yaze order: tiles[0]@(0,0), tiles[1]@(0,1), tiles[2]@(1,0), tiles[3]@(1,1)
Result: Same positions for same tiles
```
### Why This Matters
The tile data in ROM contains the actual graphics. If tiles are placed in wrong positions, objects will appear scrambled, inverted, or wrong. The h_flip/v_flip flags in tile data handle mirroring - the draw routine just needs to place tiles at correct positions.
## References
- ZScream source: `ZeldaFullEditor/Rooms/Object_Draw/Subtype1_Draw.cs`
- ZScream types: `ZeldaFullEditor/Data/Types/DungeonObjectDraw.cs`
- yaze implementation: `src/zelda3/dungeon/object_drawer.cc`

View File

@@ -0,0 +1,532 @@
# Editor Panel (Card) and Layout System Architecture
> Migration note: Phase 2 renames Card → Panel (`PanelWindow`, `PanelManager`,
> `PanelDescriptor`). The concepts below still use legacy Card naming; apply the
> new Panel terms when implementing changes.
This document describes the yaze editor's card-based UI system, layout management, and how they integrate with the agent system.
## Overview
The yaze editor uses a modular card-based architecture inspired by VSCode's workspace model:
- **Cards** = Dockable ImGui windows representing editor components
- **Categories** = Logical groupings (Dungeon, Overworld, Graphics, etc.)
- **Layouts** = DockBuilder configurations defining window arrangements
- **Presets** = Named visibility configurations for quick switching
## System Components
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ EditorManager │
│ (Central coordinator - owns all components below) │
├──────────────────────┬───────────────────────┬──────────────────────────────┤
│ EditorCardRegistry │ LayoutManager │ UICoordinator │
│ ───────────────── │ ───────────── │ ───────────── │
│ • Card metadata │ • DockBuilder │ • UI state flags │
│ • Visibility mgmt │ • Default layouts │ • Menu drawing │
│ • Session prefixes │ • Window arrange │ • Popup coordination │
│ • Workspace presets│ • Per-editor setup │ • Command palette │
├──────────────────────┴───────────────────────┴──────────────────────────────┤
│ LayoutPresets │
│ (Static definitions - default cards per editor type) │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Component Relationships
```
┌──────────────────┐
│ EditorManager │
│ (coordinator) │
└────────┬─────────┘
│ owns
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌───────────────┐
│EditorCardRegistry│ │LayoutManager │ │ UICoordinator │
└────────┬────────┘ └───────┬──────┘ └───────┬───────┘
│ │ │
│ queries │ uses │ delegates
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌───────────────┐
│ LayoutPresets │ │ EditorCard │ │ EditorCard │
│ (card defaults)│ │ Registry │ │ Registry │
└─────────────────┘ │ (window │ │ (emulator │
│ titles) │ │ visibility) │
└──────────────┘ └───────────────┘
```
---
## EditorCardRegistry
**File:** `src/app/editor/system/editor_card_registry.h`
### CardInfo Structure
Every card is registered with complete metadata:
```cpp
struct CardInfo {
std::string card_id; // "dungeon.room_selector"
std::string display_name; // "Room Selector"
std::string window_title; // " Rooms List" (matches ImGui::Begin)
std::string icon; // ICON_MD_LIST
std::string category; // "Dungeon"
std::string shortcut_hint; // "Ctrl+Shift+R"
bool* visibility_flag; // &show_room_selector_
EditorCard* card_instance; // Optional card pointer
std::function<void()> on_show; // Callback when shown
std::function<void()> on_hide; // Callback when hidden
int priority; // Menu ordering (lower = higher)
// Disabled state support
std::function<bool()> enabled_condition; // ROM-dependent cards
std::string disabled_tooltip; // "Load a ROM first"
};
```
### Card Categories
| Category | Icon | Purpose |
|-------------|---------------------------|------------------------------|
| Dungeon | `ICON_MD_CASTLE` | Dungeon room editing |
| Overworld | `ICON_MD_MAP` | Overworld map editing |
| Graphics | `ICON_MD_IMAGE` | Graphics/tile sheet editing |
| Palette | `ICON_MD_PALETTE` | Palette editing |
| Sprite | `ICON_MD_PERSON` | Sprite management |
| Music | `ICON_MD_MUSIC_NOTE` | Audio/music editing |
| Message | `ICON_MD_MESSAGE` | Text/message editing |
| Screen | `ICON_MD_TV` | Screen/UI editing |
| Emulator | `ICON_MD_VIDEOGAME_ASSET` | Emulation & debugging |
| Assembly | `ICON_MD_CODE` | ASM code editing |
| Settings | `ICON_MD_SETTINGS` | Application settings |
| Memory | `ICON_MD_MEMORY` | Memory inspection |
| Agent | `ICON_MD_SMART_TOY` | AI agent controls |
### Session-Aware Card IDs
Cards support multi-session (multiple ROMs open):
```
Single session: "dungeon.room_selector"
Multiple sessions: "s0.dungeon.room_selector", "s1.dungeon.room_selector"
```
The registry automatically prefixes card IDs using `MakeCardId()` and `GetPrefixedCardId()`.
### VSCode-Style Sidebar Layout
```
┌────┬─────────────────────────────────┬────────────────────────────────────────────┐
│ AB │ Side Panel │ Main Docking Space │
│ │ (250px width) │ │
│ ├─────────────────────────────────┤ │
│ 48 │ ▶ Dungeon │ ┌────────────────────────────────────┐ │
│ px │ ☑ Control Panel │ │ │ │
│ │ ☑ Room Selector │ │ Docked Editor Windows │ │
│ w │ ☐ Object Editor │ │ │ │
│ i │ ☐ Room Matrix │ │ │ │
│ d │ │ │ │ │
│ e │ ▶ Graphics │ │ │ │
│ │ ☐ Sheet Browser │ │ │ │
│ │ ☐ Tile Editor │ │ │ │
│ │ │ └────────────────────────────────────┘ │
│ │ ▶ Palette │ │
│ │ ☐ Control Panel │ │
├────┴─────────────────────────────────┴───────────────────────────────────────────┤
│ Status Bar │
└──────────────────────────────────────────────────────────────────────────────────┘
Activity Bar (category icons)
```
### Unified Visibility Management
The registry is the **single source of truth** for component visibility:
```cpp
// Emulator visibility (delegated from UICoordinator)
bool IsEmulatorVisible() const;
void SetEmulatorVisible(bool visible);
void ToggleEmulatorVisible();
void SetEmulatorVisibilityChangedCallback(std::function<void(bool)> cb);
```
### Card Validation System
Catches window title mismatches during development:
```cpp
struct CardValidationResult {
std::string card_id;
std::string expected_title; // From CardInfo::GetWindowTitle()
bool found_in_imgui; // Whether ImGui found window
std::string message; // Human-readable status
};
std::vector<CardValidationResult> ValidateCards() const;
void DrawValidationReport(bool* p_open);
```
---
## LayoutPresets
**File:** `src/app/editor/ui/layout_presets.h`
### Default Layouts Per Editor
Each editor type has a defined set of default and optional cards:
```cpp
struct PanelLayoutPreset {
std::string name; // "Overworld Default"
std::string description; // Human-readable
EditorType editor_type; // EditorType::kOverworld
std::vector<std::string> default_visible_cards; // Shown on first open
std::vector<std::string> optional_cards; // Available but hidden
};
```
### Editor Default Cards
| Editor | Default Cards | Optional Cards |
|------------|-------------------------------------------|---------------------------------------|
| Overworld | Canvas, Tile16 Selector | Tile8, Area GFX, Scratch, Usage Stats |
| Dungeon | Control Panel, Room Selector | Object Editor, Palette, Room Matrix |
| Graphics | Sheet Browser, Sheet Editor | Player Animations, Prototype Viewer |
| Palette | Control Panel, OW Main | Quick Access, OW Animated, Dungeon |
| Sprite | Vanilla Editor | Custom Editor |
| Screen | Dungeon Maps | Title, Inventory, OW Map, Naming |
| Music | Tracker | Instrument Editor, Assembly |
| Message | Message List, Message Editor | Font Atlas, Dictionary |
| Assembly | Editor | File Browser |
| Emulator | PPU Viewer | CPU Debugger, Memory, Breakpoints |
| Agent | Configuration, Status, Chat | Prompt Editor, Profiles, History |
### Named Workspace Presets
| Preset Name | Focus | Key Cards |
|-------------------|----------------------|-------------------------------------------|
| Minimal | Essential editing | Main canvas only |
| Developer | Debug/development | Emulator, Assembly, Memory, CPU Debugger |
| Designer | Visual/artistic | Graphics, Palette, Sprites, Screens |
| Modder | Full-featured | Everything enabled |
| Overworld Expert | Complete OW toolkit | All OW cards + Palette + Graphics |
| Dungeon Expert | Complete dungeon | All dungeon cards + Palette + Graphics |
| Testing | QA focused | Emulator, Save States, CPU, Memory, Agent |
| Audio | Music focused | Tracker, Instruments, Assembly, APU |
---
## LayoutManager
**File:** `src/app/editor/ui/layout_manager.h`
Manages ImGui DockBuilder layouts for each editor type.
### Default Layout Patterns
**Overworld Editor:**
```
┌─────────────────────────────┬──────────────┐
│ │ │
│ Overworld Canvas (75%) │ Tile16 (25%) │
│ (Main editing area) │ Selector │
│ │ │
└─────────────────────────────┴──────────────┘
```
**Dungeon Editor:**
```
┌─────┬───────────────────────────────────┐
│ │ │
│Room │ Dungeon Controls (85%) │
│(15%)│ (Main editing area, maximized) │
│ │ │
└─────┴───────────────────────────────────┘
```
**Graphics Editor:**
```
┌──────────────┬──────────────────────────┐
│ │ │
│ Sheet │ Sheet Editor (75%) │
│ Browser │ (Main canvas with tabs) │
│ (25%) │ │
└──────────────┴──────────────────────────┘
```
**Message Editor:**
```
┌─────────────┬──────────────────┬──────────┐
│ Message │ Message │ Font │
│ List (25%) │ Editor (50%) │ Atlas │
│ │ │ (25%) │
│ ├──────────────────┤ │
│ │ │Dictionary│
└─────────────┴──────────────────┴──────────┘
```
---
## Agent UI System
The agent UI system provides AI-assisted editing with a multi-agent architecture.
### Component Hierarchy
```
┌────────────────────────────────────────────────────────────────────────┐
│ AgentUiController │
│ (Central coordinator for all agent UI components) │
├─────────────────────┬─────────────────────┬────────────────────────────┤
│ AgentSessionManager│ AgentSidebar │ AgentChatCard[] │
│ ──────────────────│ ───────────── │ ─────────────── │
│ • Session lifecycle│ • Tab bar │ • Dockable windows │
│ • Active session │ • Model selector │ • Full chat view │
│ • Card open state │ • Chat compact │ • Per-agent instance │
│ │ • Proposals panel│ │
├─────────────────────┼─────────────────────┼────────────────────────────┤
│ AgentEditor │ AgentChatView │ AgentProposalsPanel │
│ ───────────── │ ────────────── │ ─────────────────── │
│ • Configuration │ • Message list │ • Code proposals │
│ • Profile mgmt │ • Input box │ • Accept/reject │
│ • Status display │ • Send button │ • Apply changes │
└─────────────────────┴─────────────────────┴────────────────────────────┘
```
### AgentSession Structure
```cpp
struct AgentSession {
std::string agent_id; // Unique identifier (UUID)
std::string display_name; // "Agent 1", "Agent 2"
AgentUIContext context; // Shared state with all views
bool is_active = false; // Currently selected in tab bar
bool has_card_open = false; // Pop-out card is visible
// Callbacks shared between sidebar and pop-out cards
ChatCallbacks chat_callbacks;
ProposalCallbacks proposal_callbacks;
CollaborationCallbacks collaboration_callbacks;
};
```
### AgentSessionManager
Manages multiple concurrent agent sessions:
```cpp
class AgentSessionManager {
public:
std::string CreateSession(const std::string& name = "");
void CloseSession(const std::string& agent_id);
AgentSession* GetActiveSession();
AgentSession* GetSession(const std::string& agent_id);
void SetActiveSession(const std::string& agent_id);
void OpenCardForSession(const std::string& agent_id);
void CloseCardForSession(const std::string& agent_id);
size_t GetSessionCount() const;
std::vector<AgentSession>& GetAllSessions();
};
```
### Sidebar Layout
```
┌─────────────────────────────────────────┐
│ [Agent 1] [Agent 2] [+] │ ← Tab bar with new agent button
├─────────────────────────────────────────┤
│ Model: gemini-2 ▼ 👤 [↗ Pop-out] │ ← Header with model selector
├─────────────────────────────────────────┤
│ │
│ 💬 User: How do I edit tiles? │
│ │
│ 🤖 Agent: You can use the Tile16... │ ← Chat messages (scrollable)
│ │
├─────────────────────────────────────────┤
│ [Type a message...] [Send] │ ← Input box
├─────────────────────────────────────────┤
│ ▶ Proposals (3) │ ← Collapsible section
│ • prop-001 ✓ Applied │
│ • prop-002 ⏳ Pending │
│ • prop-003 ❌ Rejected │
└─────────────────────────────────────────┘
```
### Pop-Out Card Flow
```
User clicks [↗ Pop-out] button in sidebar
AgentSidebar::pop_out_callback_()
AgentUiController::PopOutAgent(agent_id)
├─► Create AgentChatCard(agent_id, &session_manager_)
├─► card->SetToastManager(toast_manager_)
├─► card->SetAgentService(agent_service)
├─► session_manager_.OpenCardForSession(agent_id)
└─► open_cards_.push_back(std::move(card))
Each frame in Update():
AgentUiController::DrawOpenCards()
├─► For each card in open_cards_:
│ bool open = true;
│ card->Draw(&open);
│ if (!open) {
│ session_manager_.CloseCardForSession(card->agent_id());
│ Remove from open_cards_
│ }
└─► Pop-out cards render in main docking space
```
### State Synchronization
Both sidebar and pop-out cards share the same `AgentSession::context`:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ AgentUIContext │
│ (Shared state for all views of same agent session) │
├─────────────────────────────────────────────────────────────────────────┤
│ agent_config_ │ Provider, model, API keys, flags │
│ chat_messages_ │ Conversation history │
│ pending_proposals_│ Code changes awaiting approval │
│ collaboration_ │ Multi-user collaboration state │
│ rom_ │ Reference to loaded ROM │
│ changed_ │ Flag for detecting config changes │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
│ │ │
┌─────────┴───────┐ ┌────┴────┐ ┌──────┴──────┐
│ AgentSidebar │ │AgentChat │ │AgentChat │
│ (compact view) │ │ Card 1 │ │ Card 2 │
│ (right panel) │ │ (docked) │ │ (floating) │
└─────────────────┘ └──────────┘ └────────────┘
```
---
## Card Registration Pattern
Cards are registered during editor initialization:
```cpp
void DungeonEditor::Initialize(EditorDependencies& deps) {
deps.card_registry->RegisterCard({
.card_id = MakeCardId("dungeon.room_selector"),
.display_name = "Room Selector",
.window_title = " Rooms List", // Must match ImGui::Begin()
.icon = ICON_MD_LIST,
.category = "Dungeon",
.shortcut_hint = "Ctrl+Shift+R",
.visibility_flag = &show_room_selector_,
.enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
.disabled_tooltip = "Load a ROM to access room selection",
.priority = 10
});
}
```
### Registration Best Practices
1. **Use `MakeCardId()`** - Applies session prefixing if needed
2. **Match window_title exactly** - Must match ImGui::Begin() call
3. **Use Material Design icons** - `ICON_MD_*` constants
4. **Set category correctly** - Groups in sidebar
5. **Provide visibility_flag** - Points to bool member variable
6. **Include enabled_condition** - For ROM-dependent cards
7. **Set priority** - Lower = higher in menus
---
## Initialization Flow
```
Application Startup
EditorManager::Initialize()
├─► Create EditorCardRegistry
├─► Create LayoutManager (linked to registry)
├─► Create UICoordinator (with registry reference)
└─► For each Editor:
└─► Editor::Initialize(deps)
└─► deps.card_registry->RegisterCard(...)
(registers all cards for this editor)
```
## Editor Switch Flow
```
User clicks editor in menu
EditorManager::SwitchToEditor(EditorType type)
├─► HideCurrentEditorCards()
│ └─► card_registry_.HideAllCardsInCategory(old_category)
├─► LayoutManager::InitializeEditorLayout(type, dockspace_id)
│ └─► Build{EditorType}Layout() using DockBuilder
├─► LayoutPresets::GetDefaultCards(type)
│ └─► Returns default_visible_cards for this editor
├─► For each default card:
│ card_registry_.ShowCard(session_id, card_id)
└─► current_editor_ = editors_[type]
```
---
## File Locations
| Component | Header | Implementation |
|----------------------|--------------------------------------------------|--------------------------------------------------|
| EditorCardRegistry | `src/app/editor/system/editor_card_registry.h` | `src/app/editor/system/editor_card_registry.cc` |
| LayoutManager | `src/app/editor/ui/layout_manager.h` | `src/app/editor/ui/layout_manager.cc` |
| LayoutPresets | `src/app/editor/ui/layout_presets.h` | `src/app/editor/ui/layout_presets.cc` |
| UICoordinator | `src/app/editor/ui/ui_coordinator.h` | `src/app/editor/ui/ui_coordinator.cc` |
| EditorManager | `src/app/editor/editor_manager.h` | `src/app/editor/editor_manager.cc` |
| AgentUiController | `src/app/editor/agent/agent_ui_controller.h` | `src/app/editor/agent/agent_ui_controller.cc` |
| AgentSessionManager | `src/app/editor/agent/agent_session.h` | `src/app/editor/agent/agent_session.cc` |
| AgentSidebar | `src/app/editor/agent/agent_sidebar.h` | `src/app/editor/agent/agent_sidebar.cc` |
| AgentChatCard | `src/app/editor/agent/agent_chat_card.h` | `src/app/editor/agent/agent_chat_card.cc` |
| AgentChatView | `src/app/editor/agent/agent_chat_view.h` | `src/app/editor/agent/agent_chat_view.cc` |
| AgentProposalsPanel | `src/app/editor/agent/agent_proposals_panel.h` | `src/app/editor/agent/agent_proposals_panel.cc` |
| AgentState | `src/app/editor/agent/agent_state.h` | (header-only) |
---
## See Also
- [Graphics System Architecture](graphics_system_architecture.md)
- [Dungeon Editor System](dungeon_editor_system.md)
- [Overworld Editor System](overworld_editor_system.md)

View File

@@ -1,31 +1,30 @@
# EditorManager Architecture & Refactoring Guide
**Date**: October 15, 2025
**Status**: Refactoring in progress - Core complete, quality fixes needed
**Priority**: Fix remaining visibility issues before release
**Last Updated**: November 26, 2025
**Status**: COMPLETE - Merged to v0.3.9
**Related**: [Feature Parity Analysis](../roadmaps/feature-parity-analysis.md)
---
## Table of Contents
1. [Current State](#current-state)
2. [Completed Work](#completed-work)
3. [Critical Issues Remaining](#critical-issues-remaining)
4. [Architecture Patterns](#architecture-patterns)
5. [Testing Plan](#testing-plan)
6. [File Reference](#file-reference)
3. [Architecture Patterns](#architecture-patterns)
4. [File Reference](#file-reference)
---
## Current State
### Build Status
**Compiles successfully** (no errors)
**All critical visibility issues FIXED**
**Welcome screen ImGui state override FIXED**
**DockBuilder layout system IMPLEMENTED**
**Global Search migrated to UICoordinator**
**Shortcut conflicts resolved**
**Code Reduction**: EditorManager 2341 → 2072 lines (-11.7%)
**Compiles successfully** (no errors)
**All critical visibility issues FIXED**
**Welcome screen ImGui state override FIXED**
**DockBuilder layout system IMPLEMENTED**
**Global Search migrated to UICoordinator**
**Shortcut conflicts resolved**
**Code Reduction**: 3710 → 2076 lines (-44%)
### What Works
- All popups (Save As, Display Settings, Help menus) - no crashes
@@ -288,7 +287,7 @@ if (visibility && *visibility) {
// BEFORE (ui_coordinator.cc):
auto* current_editor = editor_manager_->GetCurrentEditorSet();
for (auto* editor : current_editor->active_editors_) {
if (*editor->active() && editor_registry_.IsCardBasedEditor(editor->type())) {
if (*editor->active() && editor_registry_.IsPanelBasedEditor(editor->type())) {
active_editor = editor;
break; // Not implemented Takes first match, not necessarily focused
}
@@ -296,7 +295,7 @@ for (auto* editor : current_editor->active_editors_) {
// AFTER:
auto* active_editor = editor_manager_->GetCurrentEditor(); // Direct focused editor
if (!active_editor || !editor_registry_.IsCardBasedEditor(active_editor->type())) {
if (!active_editor || !editor_registry_.IsPanelBasedEditor(active_editor->type())) {
return;
}
```
@@ -402,7 +401,7 @@ if (cpu_visible && *cpu_visible) {
**Solution Applied**: Changed `ui_coordinator.cc` to use `GetCurrentEditor()`:
```cpp
auto* active_editor = editor_manager_->GetCurrentEditor();
if (!active_editor || !editor_registry_.IsCardBasedEditor(active_editor->type())) {
if (!active_editor || !editor_registry_.IsPanelBasedEditor(active_editor->type())) {
return;
}
```

View File

@@ -0,0 +1,177 @@
# Graphics System Architecture
**Status**: Complete
**Last Updated**: 2025-11-21
**Related Code**: `src/app/gfx/`, `src/app/editor/graphics/`
This document outlines the architecture of the Graphics System in YAZE, including resource management, compression pipelines, and rendering workflows.
## Overview
The graphics system is designed to handle SNES-specific image formats (indexed color, 2BPP/3BPP) while efficiently rendering them using modern hardware acceleration via SDL2. It uses a centralized resource manager (`Arena`) to pool resources and manage lifecycle.
## Core Components
### 1. The Arena (`src/app/gfx/resource/arena.h`)
The `Arena` is a singleton class that acts as the central resource manager for all graphics.
**Responsibilities**:
* **Resource Management**: Manages the lifecycle of `SDL_Texture` and `SDL_Surface` objects using RAII wrappers with custom deleters
* **Graphics Sheets**: Holds a fixed array of 223 `Bitmap` objects representing the game's complete graphics space (indexed 0-222)
* **Background Buffers**: Manages `BackgroundBuffer`s for SNES BG1 and BG2 layer rendering
* **Deferred Rendering**: Implements a command queue (`QueueTextureCommand`) to batch texture creation/updates, preventing UI freezes during heavy loads
* **Memory Pooling**: Reuses textures and surfaces to minimize allocation overhead
**Key Methods**:
* `QueueTextureCommand(type, bitmap)`: Queue a texture operation for batch processing
* `ProcessTextureQueue(renderer)`: Process all queued texture commands
* `NotifySheetModified(sheet_index)`: Notify when a graphics sheet changes to synchronize editors
* `gfx_sheets()`: Get all 223 graphics sheets
* `mutable_gfx_sheet(index)`: Get mutable reference to a specific sheet
### 2. Bitmap (`src/app/gfx/core/bitmap.h`)
Represents a single graphics sheet or image optimized for SNES ROM editing.
**Key Features**:
* **Data Storage**: Stores raw pixel data as `std::vector<uint8_t>` (indices into a palette)
* **Palette**: Each bitmap owns a `SnesPalette` (256 colors maximum)
* **Texture Management**: Manages an `SDL_Texture` handle and syncs CPU pixel data to GPU
* **Dirty Tracking**: Tracks modified regions to minimize texture upload bandwidth
* **Tile Extraction**: Provides methods like `Get8x8Tile()`, `Get16x16Tile()` for SNES tile operations
**Important Synchronization Rules**:
* Never modify `SDL_Texture` directly - always modify the `Bitmap` data
* Use `set_data()` for bulk updates to keep CPU and GPU in sync
* Use `WriteToPixel()` for single-pixel modifications
* Call `UpdateTexture()` to sync changes to GPU
### 3. Graphics Editor (`src/app/editor/graphics/graphics_editor.cc`)
The primary UI for viewing and modifying graphics.
* **Sheet Editor**: Pixel-level editing of all 223 sheets
* **Palette Integration**: Fetches palette groups from the ROM (Overworld, Dungeon, Sprites) to render sheets correctly
* **Tools**: Pencil, Fill, Select, Zoom
* **Real-time Display**: Uses `Canvas` class for drawing interface
### 4. IRenderer Interface (`src/app/gfx/backend/irenderer.h`)
Abstract interface for the rendering backend (currently implemented by `SdlRenderer`). This decouples graphics logic from SDL-specific calls, enabling:
* Testing with mock renderers
* Future backend swaps (e.g., Vulkan, Metal)
## Rendering Pipeline
### 1. Loading Phase
**Source**: ROM compressed data
**Process**:
1. Iterates through all 223 sheet indices
2. Determines format based on index range (2BPP, 3BPP compressed, 3BPP uncompressed)
3. Calls decompression functions
4. Converts to internal 8-bit indexed format
5. Stores result in `Arena.gfx_sheets_`
**Performance**: Uses deferred loading via texture queue to avoid blocking
### 2. Composition Phase (Rooms/Overworld)
**Process**:
1. Room/Overworld logic draws tiles from `gfx_sheets` into a `BackgroundBuffer` (wraps a `Bitmap`)
2. This drawing happens on CPU, manipulating indexed pixel data
3. Each room/map maintains its own `Bitmap` of rendered data
**Key Classes**:
* `BackgroundBuffer`: Manages BG1 and BG2 layer rendering for a single room/area
* Methods like `Room::RenderRoomGraphics()` handle composition
### 3. Texture Update Phase
**Process**:
1. Editor checks if bitmaps are marked "dirty" (modified since last render)
2. Modified bitmaps queue a `TextureCommand::UPDATE` to Arena
3. Arena processes queue, uploading pixel data to SDL textures
4. This batching avoids per-frame texture uploads
### 4. Display Phase
**Process**:
1. `Canvas` or UI elements request the `SDL_Texture` from a `Bitmap`
2. Texture is rendered to screen using ImGui or direct SDL calls
3. Grid, overlays, and selection highlights are drawn on top
## Compression Pipeline
YAZE uses the **LC-LZ2** algorithm (often called "Hyrule Magic" compression) for ROM I/O.
### Supported Formats
| Format | Sheets | Bits Per Pixel | Usage | Location |
|--------|--------|--------|-------|----------|
| 3BPP (Compressed) | 0-112, 127-217 | 3 | Most graphics | Standard ROM |
| 2BPP (Compressed) | 113-114, 218-222 | 2 | HUD, Fonts, Effects | Standard ROM |
| 3BPP (Uncompressed) | 115-126 | 3 | Link Player Sprites | 0x080000 |
### Loading Process
**Entry Point**: `src/app/rom.cc:Rom::LoadFromFile()`
1. Iterates through all 223 sheet indices
2. Determines format based on index range
3. Calls `gfx::lc_lz2::DecompressV2()` (or `DecompressV1()` for compatibility)
4. For uncompressed sheets (115-126), copies raw data directly
5. Converts result to internal 8-bit indexed format
6. Stores in `Arena.gfx_sheets_[index]`
### Saving Process
**Process**:
1. Get mutable reference: `auto& sheet = Arena::Get().mutable_gfx_sheet(index)`
2. Make modifications to `sheet.mutable_data()`
3. Notify Arena: `Arena::Get().NotifySheetModified(index)`
4. When saving ROM:
* Convert 8-bit indexed data back to 2BPP/3BPP format
* Compress using `gfx::lc_lz2::CompressV3()`
* Write to ROM, handling pointer table updates if sizes change
## Link Graphics (Player Sprites)
**Location**: ROM offset `0x080000`
**Format**: Uncompressed 3BPP
**Sheet Indices**: 115-126
**Editor**: `GraphicsEditor` provides a "Player Animations" view
**Structure**: Sheets are assembled into poses using OAM (Object Attribute Memory) tables
## Canvas Interactions
The `Canvas` class (`src/app/gui/canvas/canvas.h`) is the primary rendering engine.
**Drawing Operations**:
* `DrawBitmap()`: Renders a sheet texture to the canvas
* `DrawSolidTilePainter()`: Preview of brush before commit
* `DrawTileOnBitmap()`: Commits pixel changes to Bitmap data
**Selection and Tools**:
* `DrawSelectRect()`: Rectangular region selection
* Context Menu: Right-click for Zoom, Grid, view resets
**Coordinate Systems**:
* Canvas Pixels: Unscaled (128-512 range depending on sheet)
* Screen Pixels: Scaled by zoom level
* Tile Coordinates: 8x8 or 16x16 tiles for SNES editing
## Best Practices
* **Never modify `SDL_Texture` directly**: Always modify the `Bitmap` data and call `UpdateTexture()` or queue it
* **Use `QueueTextureCommand`**: For bulk updates, queue commands to avoid stalling the main thread
* **Respect Palettes**: Remember that `Bitmap` data is just indices. Visual result depends on the associated `SnesPalette`
* **Sheet Modification**: When modifying a global graphics sheet, notify `Arena` via `NotifySheetModified()` to propagate changes to all editors
* **Deferred Loading**: Always use the texture queue system for heavy operations to prevent UI freezes
## Future Improvements
* **Vulkan/Metal Backend**: The `IRenderer` interface allows for potentially swapping SDL2 for a more modern API
* **Compute Shaders**: Palette swapping could potentially be moved to GPU using shaders instead of CPU-side pixel manipulation
* **Streaming Graphics**: Load/unload sheets on demand for very large ROM patches

View File

@@ -0,0 +1,33 @@
# Layout Designer (December 2025)
Canonical reference for the ImGui layout designer utility that lives in `src/app/editor/layout_designer/`. Use this in place of the older phase-by-phase notes and mockups.
## Current Capabilities
- **Two modes**: Panel Layout (dock graph editing) and Widget Design (panel internals) toggled in the toolbar of `LayoutDesignerWindow`.
- **Panel layout mode**: Palette from `PanelManager` descriptors with search/category filter, drag-and-drop into a dock tree with split drop-zones, selection + property editing, zoom controls, optional code preview, and a theme panel. JSON export writes via `LayoutSerializer::SaveToFile`; import/export dialogs are stubbed.
- **Widget design mode**: Palette from `yaze_widgets`, canvas + properties UI, and code generation through `WidgetCodeGenerator` (deletion/undo/redo are still TODOs).
- **Runtime import**: `ImportFromRuntime()` builds a flat layout from the registered `PanelDescriptor`s (no live dock positions yet). `PreviewLayout()` is stubbed and does not apply to the running dockspace.
## Integration Quick Start
```cpp
// EditorManager member
layout_designer::LayoutDesignerWindow layout_designer_;
// Init once with the panel manager
layout_designer_.Initialize(&panel_manager_);
// Open from menu or shortcut
layout_designer_.Open();
// Draw every frame
if (layout_designer_.IsOpen()) {
layout_designer_.Draw();
}
```
## Improvement Backlog (code-aligned)
1. **Preview/apply pipeline**: Implement `LayoutDesignerWindow::PreviewLayout()` to transform a `LayoutDefinition` into DockBuilder operations (use `PanelDescriptor::GetWindowTitle()` and the active dockspace ID from `LayoutManager`). Ensure session-aware panel IDs and call back into `LayoutManager`/`PanelManager` so visibility state stays in sync.
2. **Serialization round-trip**: Finish `LayoutSerializer::FromJson()` and wire real open/save dialogs. Validate versions/author fields and surface parse errors in the UI. Add a simple JSON schema example to `layout_designer/README.md` once load works.
3. **Runtime import fidelity**: Replace the flat import in `ImportFromRuntime()` with actual dock sampling (dock nodes, split ratios, and current visible panels), filtering out dashboard/welcome. Capture panel visibility per session instead of assuming all-visible defaults.
4. **Editing polish**: Implement delete/undo/redo for panels/widgets, and make widget deletion/selection consistent across both modes. Reduce debug logging spam (`DragDrop` noise) once the drop pipeline is stable.
5. **Export path**: Hook `ExportCode()` to write the generated code preview to disk and optionally emit a `LayoutManager` preset stub for quick integration.

View File

@@ -0,0 +1,281 @@
# Object Selection System Integration Guide
## Overview
The `ObjectSelection` class provides a clean, composable selection system for dungeon objects. It follows the Single Responsibility Principle by focusing solely on selection state management and operations, while leaving input handling and canvas interaction to `DungeonObjectInteraction`.
## Architecture
```
DungeonCanvasViewer
└── DungeonObjectInteraction (handles input, coordinates)
└── ObjectSelection (manages selection state)
```
## Integration Steps
### 1. Add ObjectSelection to DungeonObjectInteraction
**File**: `src/app/editor/dungeon/dungeon_object_interaction.h`
```cpp
#include "object_selection.h"
class DungeonObjectInteraction {
public:
// ... existing code ...
// Expose selection system
ObjectSelection& selection() { return selection_; }
const ObjectSelection& selection() const { return selection_; }
private:
// Replace existing selection state with ObjectSelection
ObjectSelection selection_;
// Remove these (now handled by ObjectSelection):
// std::vector<size_t> selected_object_indices_;
// bool object_select_active_;
// ImVec2 object_select_start_;
// ImVec2 object_select_end_;
};
```
### 2. Update HandleCanvasMouseInput Method
**File**: `src/app/editor/dungeon/dungeon_object_interaction.cc`
```cpp
void DungeonObjectInteraction::HandleCanvasMouseInput() {
const ImGuiIO& io = ImGui::GetIO();
if (!canvas_->IsMouseHovering()) {
return;
}
ImVec2 mouse_pos = io.MousePos;
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 canvas_mouse_pos = ImVec2(mouse_pos.x - canvas_pos.x,
mouse_pos.y - canvas_pos.y);
// Determine selection mode based on modifiers
ObjectSelection::SelectionMode mode = ObjectSelection::SelectionMode::Single;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
mode = ObjectSelection::SelectionMode::Add;
} else if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) {
mode = ObjectSelection::SelectionMode::Toggle;
}
// Handle left click - single object selection or object placement
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (object_loaded_) {
// Place object at click position
auto [room_x, room_y] = CanvasToRoomCoordinates(
static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y));
PlaceObjectAtPosition(room_x, room_y);
} else {
// Try to select object at cursor position
TrySelectObjectAtCursor(static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y), mode);
}
}
// Handle right click drag - rectangle selection
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !object_loaded_) {
selection_.BeginRectangleSelection(static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y));
}
if (selection_.IsRectangleSelectionActive()) {
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
selection_.UpdateRectangleSelection(static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y));
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
if (rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
auto& room = (*rooms_)[current_room_id_];
selection_.EndRectangleSelection(room.GetTileObjects(), mode);
} else {
selection_.CancelRectangleSelection();
}
}
}
// Handle Ctrl+A - Select All
if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_A)) {
if (rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
auto& room = (*rooms_)[current_room_id_];
selection_.SelectAll(room.GetTileObjects().size());
}
}
// Handle dragging selected objects (if any selected and not placing)
if (selection_.HasSelection() && !object_loaded_) {
HandleObjectDragging(canvas_mouse_pos);
}
}
```
### 3. Add Helper Method for Click Selection
```cpp
void DungeonObjectInteraction::TrySelectObjectAtCursor(
int canvas_x, int canvas_y, ObjectSelection::SelectionMode mode) {
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) {
return;
}
auto& room = (*rooms_)[current_room_id_];
const auto& objects = room.GetTileObjects();
// Convert canvas coordinates to room coordinates
auto [room_x, room_y] = CanvasToRoomCoordinates(canvas_x, canvas_y);
// Find object at cursor (check in reverse order to prioritize top objects)
for (int i = objects.size() - 1; i >= 0; --i) {
auto [obj_x, obj_y, obj_width, obj_height] =
ObjectSelection::GetObjectBounds(objects[i]);
// Check if cursor is within object bounds
if (room_x >= obj_x && room_x < obj_x + obj_width &&
room_y >= obj_y && room_y < obj_y + obj_height) {
selection_.SelectObject(i, mode);
return;
}
}
// No object found - clear selection if Single mode
if (mode == ObjectSelection::SelectionMode::Single) {
selection_.ClearSelection();
}
}
```
### 4. Update Rendering Methods
Replace existing selection highlight methods:
```cpp
void DungeonObjectInteraction::DrawSelectionHighlights() {
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) {
return;
}
auto& room = (*rooms_)[current_room_id_];
selection_.DrawSelectionHighlights(canvas_, room.GetTileObjects());
}
void DungeonObjectInteraction::DrawSelectBox() {
selection_.DrawRectangleSelectionBox(canvas_);
}
```
### 5. Update Delete/Copy/Paste Operations
```cpp
void DungeonObjectInteraction::HandleDeleteSelected() {
if (!selection_.HasSelection() || !rooms_) {
return;
}
if (current_room_id_ < 0 || current_room_id_ >= 296) {
return;
}
if (mutation_hook_) {
mutation_hook_();
}
auto& room = (*rooms_)[current_room_id_];
// Get sorted indices in descending order
auto indices = selection_.GetSelectedIndices();
std::sort(indices.rbegin(), indices.rend());
// Delete from highest index to lowest (avoid index shifts)
for (size_t index : indices) {
room.RemoveTileObject(index);
}
selection_.ClearSelection();
if (cache_invalidation_callback_) {
cache_invalidation_callback_();
}
}
void DungeonObjectInteraction::HandleCopySelected() {
if (!selection_.HasSelection() || !rooms_) {
return;
}
if (current_room_id_ < 0 || current_room_id_ >= 296) {
return;
}
auto& room = (*rooms_)[current_room_id_];
const auto& objects = room.GetTileObjects();
clipboard_.clear();
for (size_t index : selection_.GetSelectedIndices()) {
if (index < objects.size()) {
clipboard_.push_back(objects[index]);
}
}
has_clipboard_data_ = !clipboard_.empty();
}
```
## Keyboard Shortcuts
The selection system supports standard keyboard shortcuts:
| Shortcut | Action |
|----------|--------|
| **Left Click** | Select single object (replace selection) |
| **Shift + Left Click** | Add object to selection |
| **Ctrl + Left Click** | Toggle object in selection |
| **Right Click + Drag** | Rectangle selection |
| **Ctrl + A** | Select all objects |
| **Delete** | Delete selected objects |
| **Ctrl + C** | Copy selected objects |
| **Ctrl + V** | Paste objects |
## Visual Feedback
The selection system provides clear visual feedback:
1. **Selected Objects**: Pulsing animated border with corner handles
2. **Rectangle Selection**: Semi-transparent box with colored border
3. **Multiple Selection**: All selected objects highlighted simultaneously
## Testing
See `test/unit/object_selection_test.cc` for comprehensive unit tests covering:
- Single selection
- Multi-selection (Shift/Ctrl)
- Rectangle selection
- Select all
- Coordinate conversion
- Bounding box calculation
## Benefits of This Design
1. **Separation of Concerns**: Selection logic is isolated from input handling
2. **Testability**: Pure functions for selection operations
3. **Reusability**: ObjectSelection can be used in other editors
4. **Maintainability**: Clear API with well-defined responsibilities
5. **Performance**: Uses `std::set` for O(log n) lookups and automatic sorting
6. **Type Safety**: Uses enum for selection modes instead of booleans
7. **Theme Integration**: All colors sourced from `AgentUITheme`
## Future Enhancements
Potential future improvements:
- Lasso selection (free-form polygon)
- Selection filters (by object type, layer)
- Selection history (undo/redo selection changes)
- Selection groups (named selections)
- Marquee zoom (zoom to selected objects)

View File

@@ -0,0 +1,405 @@
# Object Selection System - Interaction Flow
## Visual Flow Diagrams
### 1. Single Object Selection (Left Click)
```
┌─────────────────────────────────────────────────────┐
│ User Input: Left Click on Object │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonObjectInteraction::HandleCanvasMouseInput() │
│ - Detect left click │
│ - Get mouse position │
│ - Convert to room coordinates │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ TrySelectObjectAtCursor(x, y, mode) │
│ - Iterate objects in reverse order │
│ - Check if cursor within object bounds │
│ - Find topmost object at cursor │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ObjectSelection::SelectObject(index, Single) │
│ - Clear previous selection │
│ - Add object to selection (set.insert) │
│ - Trigger selection changed callback │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Visual Feedback │
│ - Draw pulsing border (yellow-gold, 0.85f alpha) │
│ - Draw corner handles (cyan-white, 0.85f alpha) │
│ - Animate pulse at 4 Hz │
└─────────────────────────────────────────────────────┘
```
### 2. Multi-Selection (Shift+Click)
```
┌─────────────────────────────────────────────────────┐
│ User Input: Shift + Left Click │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ HandleCanvasMouseInput() │
│ - Detect Shift key down │
│ - Set mode = SelectionMode::Add │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ObjectSelection::SelectObject(index, Add) │
│ - Keep existing selection │
│ - Add new object (set.insert) │
│ - Trigger callback │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Visual Feedback │
│ - Highlight ALL selected objects │
│ - Each with pulsing border + handles │
└─────────────────────────────────────────────────────┘
```
### 3. Toggle Selection (Ctrl+Click)
```
┌─────────────────────────────────────────────────────┐
│ User Input: Ctrl + Left Click │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ HandleCanvasMouseInput() │
│ - Detect Ctrl key down │
│ - Set mode = SelectionMode::Toggle │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ObjectSelection::SelectObject(index, Toggle) │
│ - If selected: Remove (set.erase) │
│ - If not selected: Add (set.insert) │
│ - Trigger callback │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Visual Feedback │
│ - Update highlights for current selection │
│ - Removed objects no longer highlighted │
└─────────────────────────────────────────────────────┘
```
### 4. Rectangle Selection (Right Click + Drag)
```
┌─────────────────────────────────────────────────────┐
│ User Input: Right Click + Drag │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Phase 1: Mouse Down (Right Button) │
│ ObjectSelection::BeginRectangleSelection(x, y) │
│ - Store start position │
│ - Set rectangle_selection_active = true │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Phase 2: Mouse Drag │
│ ObjectSelection::UpdateRectangleSelection(x, y) │
│ - Update end position │
│ - Draw rectangle preview │
│ • Border: accent_color @ 0.85f alpha │
│ • Fill: accent_color @ 0.15f alpha │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Phase 3: Mouse Release │
│ ObjectSelection::EndRectangleSelection(objects) │
│ - Convert canvas coords to room coords │
│ - For each object: │
│ • Get object bounds │
│ • Check AABB intersection with rectangle │
│ • If intersects: Add to selection │
│ - Set rectangle_selection_active = false │
│ - Trigger callback │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Visual Feedback │
│ - Highlight all selected objects │
│ - Remove rectangle preview │
└─────────────────────────────────────────────────────┘
```
### 5. Select All (Ctrl+A)
```
┌─────────────────────────────────────────────────────┐
│ User Input: Ctrl + A │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ HandleCanvasMouseInput() │
│ - Detect Ctrl + A key combination │
│ - Get current room object count │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ObjectSelection::SelectAll(object_count) │
│ - Clear previous selection │
│ - Add all object indices (0..count-1) │
│ - Trigger callback │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Visual Feedback │
│ - Highlight ALL objects in room │
│ - May cause performance impact if many objects │
└─────────────────────────────────────────────────────┘
```
## State Transitions
```
┌────────────┐
│ No │◄─────────────────────┐
│ Selection │ │
└──────┬─────┘ │
│ │
│ Left Click │ Esc or Clear
▼ │
┌────────────┐ │
│ Single │◄─────────┐ │
│ Selection │ │ │
└──────┬─────┘ │ │
│ │ │
│ Shift+Click │ Ctrl+Click│
│ Right+Drag │ (deselect)│
▼ │ │
┌────────────┐ │ │
│ Multi │──────────┘ │
│ Selection │──────────────────────┘
└────────────┘
```
## Rendering Pipeline
```
┌─────────────────────────────────────────────────────┐
│ DungeonCanvasViewer::DrawDungeonCanvas(room_id) │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 1. Draw Room Background Layers (BG1, BG2) │
│ - Load room graphics │
│ - Render to bitmaps │
│ - Draw bitmaps to canvas │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 2. Draw Sprites │
│ - Render sprite markers (8x8 squares) │
│ - Color-code by layer │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 3. Handle Object Interaction │
│ DungeonObjectInteraction::HandleCanvasMouseInput()│
│ - Process mouse/keyboard input │
│ - Update selection state │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 4. Draw Selection Visuals (TOP LAYER) │
│ ObjectSelection::DrawSelectionHighlights() │
│ - For each selected object: │
│ • Convert room coords to canvas coords │
│ • Apply canvas scale │
│ • Draw pulsing border │
│ • Draw corner handles │
│ ObjectSelection::DrawRectangleSelectionBox() │
│ - If active: Draw rectangle preview │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 5. Draw Canvas Overlays │
│ - Grid lines │
│ - Debug overlays (if enabled) │
└─────────────────────────────────────────────────────┘
```
## Data Flow for Object Operations
### Delete Selected Objects
```
┌─────────────────────────────────────────────────────┐
│ User Input: Delete Key │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonObjectInteraction::HandleDeleteSelected() │
│ 1. Get selected indices from ObjectSelection │
│ 2. Sort indices in descending order │
│ 3. For each index (high to low): │
│ - Call room.RemoveTileObject(index) │
│ 4. Clear selection │
│ 5. Trigger cache invalidation (re-render) │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Room::RenderRoomGraphics() │
│ - Re-render room with deleted objects removed │
└─────────────────────────────────────────────────────┘
```
### Copy/Paste Selected Objects
```
Copy Flow:
┌─────────────────────────────────────────────────────┐
│ User Input: Ctrl+C │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonObjectInteraction::HandleCopySelected() │
│ 1. Get selected indices from ObjectSelection │
│ 2. Copy objects to clipboard_ vector │
│ 3. Set has_clipboard_data_ = true │
└─────────────────────────────────────────────────────┘
Paste Flow:
┌─────────────────────────────────────────────────────┐
│ User Input: Ctrl+V │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonObjectInteraction::HandlePasteObjects() │
│ 1. Get mouse position │
│ 2. Calculate offset from first clipboard object │
│ 3. For each clipboard object: │
│ - Create copy with offset position │
│ - Clamp to room bounds (0-63) │
│ - Add to room │
│ 4. Trigger re-render │
└─────────────────────────────────────────────────────┘
```
## Performance Considerations
### Selection State Storage
```
std::set<size_t> selected_indices_;
Advantages:
✓ O(log n) insert/delete/lookup
✓ Automatic sorting
✓ No duplicates
✓ Cache-friendly for small selections
Trade-offs:
✗ Slightly higher memory overhead
✗ Not as cache-friendly for iteration (vs vector)
Decision: Justified for correctness guarantees
```
### Rendering Optimization
```
void DrawSelectionHighlights() {
if (selected_indices_.empty()) {
return; // Early exit - O(1)
}
// Only render visible objects (canvas culling)
for (size_t index : selected_indices_) {
if (IsObjectVisible(index)) {
DrawHighlight(index); // O(k) where k = selected count
}
}
}
Complexity: O(k) where k = selected object count
Typical case: k < 20 objects selected
Worst case: k = 296 (all objects) - rare
```
## Memory Layout
```
ObjectSelection Instance (~64 bytes)
├── selected_indices_ (std::set<size_t>)
│ └── Red-Black Tree
│ ├── Node overhead: ~32 bytes per node
│ └── Typical selection: 5 objects = ~160 bytes
├── rectangle_selection_active_ (bool) = 1 byte
├── rect_start_x_ (int) = 4 bytes
├── rect_start_y_ (int) = 4 bytes
├── rect_end_x_ (int) = 4 bytes
├── rect_end_y_ (int) = 4 bytes
└── selection_changed_callback_ (std::function) = 32 bytes
Total: ~64 bytes + (32 bytes × selected_count)
Example: 10 objects selected = ~384 bytes
Negligible compared to room graphics (~2MB)
```
## Integration Checklist
When integrating ObjectSelection into DungeonObjectInteraction:
- [ ] Add `ObjectSelection selection_;` member
- [ ] Remove old selection state variables
- [ ] Update `HandleCanvasMouseInput()` to use selection modes
- [ ] Add `TrySelectObjectAtCursor()` helper
- [ ] Update `DrawSelectionHighlights()` to delegate to ObjectSelection
- [ ] Update `DrawSelectBox()` to delegate to ObjectSelection
- [ ] Update `HandleDeleteSelected()` to use `selection_.GetSelectedIndices()`
- [ ] Update `HandleCopySelected()` to use `selection_.GetSelectedIndices()`
- [ ] Update clipboard operations
- [ ] Add Ctrl+A handler for select all
- [ ] Test single selection
- [ ] Test multi-selection (Shift+click)
- [ ] Test toggle selection (Ctrl+click)
- [ ] Test rectangle selection
- [ ] Test select all (Ctrl+A)
- [ ] Test copy/paste/delete operations
- [ ] Verify visual feedback (borders, handles)
- [ ] Verify theme color usage
- [ ] Run unit tests
- [ ] Test performance with many objects
---
**Diagram Format**: ASCII art compatible with markdown viewers
**Last Updated**: 2025-11-26

View File

@@ -0,0 +1,450 @@
# Object Selection System Architecture
## Overview
The Object Selection System provides comprehensive selection functionality for the dungeon editor. It's designed following the Single Responsibility Principle, separating selection state management from input handling and rendering.
**Version**: 1.0
**Date**: 2025-11-26
**Location**: `src/app/editor/dungeon/object_selection.{h,cc}`
## Architecture Diagram
```
┌─────────────────────────────────────────────────────┐
│ DungeonEditorV2 (Main Editor) │
│ - Coordinates all components │
│ - Card-based UI system │
│ - Handles Save/Load/Undo/Redo │
└────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonCanvasViewer (Canvas Rendering) │
│ - Room graphics display │
│ - Layer management (BG1/BG2) │
│ - Sprite rendering │
└────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ DungeonObjectInteraction (Input Handling) │
│ - Mouse input processing │
│ - Keyboard shortcut handling │
│ - Coordinate conversion │
│ - Drag operations │
│ - Copy/Paste/Delete │
└────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ ObjectSelection (Selection State) │
│ - Selection state management │
│ - Multi-selection logic │
│ - Rectangle selection │
│ - Visual rendering │
│ - Bounding box calculations │
└─────────────────────────────────────────────────────┘
```
## Component Responsibilities
### ObjectSelection (New Component)
**Purpose**: Manages selection state and provides selection operations
**Key Responsibilities**:
- Single object selection
- Multi-selection (Shift, Ctrl modifiers)
- Rectangle drag selection
- Select all functionality
- Visual feedback rendering
- Coordinate conversion utilities
**Design Principles**:
- **Stateless Operations**: Pure functions where possible
- **Composable**: Can be integrated into any editor
- **Testable**: All operations have unit tests
- **Type-Safe**: Uses enums instead of magic booleans
### DungeonObjectInteraction (Enhanced)
**Purpose**: Handles user input and coordinates object manipulation
**Integration Points**:
```cpp
// Before (scattered state)
std::vector<size_t> selected_object_indices_;
bool object_select_active_;
ImVec2 object_select_start_;
ImVec2 object_select_end_;
// After (delegated to ObjectSelection)
ObjectSelection selection_;
```
## Selection Modes
The system supports four distinct selection modes:
### 1. Single Selection (Default)
**Trigger**: Left click on object
**Behavior**: Replace current selection with clicked object
**Use Case**: Basic object selection
```cpp
selection_.SelectObject(index, ObjectSelection::SelectionMode::Single);
```
### 2. Add Selection (Shift+Click)
**Trigger**: Shift + Left click
**Behavior**: Add object to existing selection
**Use Case**: Building multi-object selections incrementally
```cpp
selection_.SelectObject(index, ObjectSelection::SelectionMode::Add);
```
### 3. Toggle Selection (Ctrl+Click)
**Trigger**: Ctrl + Left click
**Behavior**: Toggle object in/out of selection
**Use Case**: Fine-tuning selections by removing specific objects
```cpp
selection_.SelectObject(index, ObjectSelection::SelectionMode::Toggle);
```
### 4. Rectangle Selection (Drag)
**Trigger**: Right click + drag
**Behavior**: Select all objects within rectangle
**Use Case**: Bulk selection of objects
```cpp
selection_.BeginRectangleSelection(x, y);
selection_.UpdateRectangleSelection(x, y);
selection_.EndRectangleSelection(objects, mode);
```
## Keyboard Shortcuts
| Shortcut | Action | Implementation |
|----------|--------|----------------|
| **Left Click** | Select single object | `SelectObject(index, Single)` |
| **Shift + Click** | Add to selection | `SelectObject(index, Add)` |
| **Ctrl + Click** | Toggle in selection | `SelectObject(index, Toggle)` |
| **Right Drag** | Rectangle select | `Begin/Update/EndRectangleSelection()` |
| **Ctrl + A** | Select all | `SelectAll(count)` |
| **Delete** | Delete selected | `HandleDeleteSelected()` |
| **Ctrl + C** | Copy selected | `HandleCopySelected()` |
| **Ctrl + V** | Paste objects | `HandlePasteObjects()` |
| **Esc** | Clear selection | `ClearSelection()` |
## Visual Feedback
### Selected Objects
- **Border**: Pulsing animated outline (yellow-gold)
- **Handles**: Four corner handles (cyan-white at 0.85f alpha)
- **Animation**: Sinusoidal pulse at 4 Hz
```cpp
// Animation formula
float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 4.0f);
```
### Rectangle Selection
- **Border**: Accent color at 0.85f alpha (high visibility)
- **Fill**: Accent color at 0.15f alpha (subtle background)
- **Thickness**: 2.0f pixels
### Entity Visibility Standards
All entity rendering follows yaze's visibility standards:
- **High-contrast colors**: Bright yellow-gold, cyan-white
- **Alpha value**: 0.85f for primary visibility
- **Background alpha**: 0.15f for fills
## Coordinate Systems
### Room Coordinates (Tiles)
- **Range**: 0-63 (64x64 tile rooms)
- **Unit**: Tiles
- **Origin**: Top-left corner (0, 0)
### Canvas Coordinates (Pixels)
- **Range**: 0-511 (unscaled, 8 pixels per tile)
- **Unit**: Pixels
- **Origin**: Top-left corner (0, 0)
- **Scale**: Subject to canvas zoom (global_scale)
### Conversion Functions
```cpp
// Tile → Pixel (unscaled)
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) {
return {room_x * 8, room_y * 8};
}
// Pixel → Tile (unscaled)
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) {
return {canvas_x / 8, canvas_y / 8};
}
```
## Bounding Box Calculation
Objects have variable sizes based on their `size_` field:
```cpp
// Object size encoding
uint8_t size_h = (object.size_ & 0x0F); // Horizontal size
uint8_t size_v = (object.size_ >> 4) & 0x0F; // Vertical size
// Dimensions (in tiles)
int width = size_h + 1;
int height = size_v + 1;
```
### Examples:
| `size_` | Width | Height | Total |
|---------|-------|--------|-------|
| `0x00` | 1 | 1 | 1x1 |
| `0x11` | 2 | 2 | 2x2 |
| `0x23` | 4 | 3 | 4x3 |
| `0xFF` | 16 | 16 | 16x16 |
## Theme Integration
All colors are sourced from `AgentUITheme`:
```cpp
const auto& theme = AgentUI::GetTheme();
// Selection colors
theme.dungeon_selection_primary // Yellow-gold (pulsing border)
theme.dungeon_selection_secondary // Cyan (secondary elements)
theme.dungeon_selection_handle // Cyan-white (corner handles)
theme.accent_color // UI accent (rectangle selection)
```
**Critical Rule**: NEVER use hardcoded `ImVec4` colors. Always use theme system.
## Implementation Details
### Selection State Storage
Uses `std::set<size_t>` for selected indices:
**Advantages**:
- O(log n) insertion/deletion
- O(log n) lookup
- Automatic sorting
- No duplicates
**Trade-offs**:
- Slightly higher memory overhead than vector
- Justified by performance and correctness guarantees
### Rectangle Selection Algorithm
**Intersection Test**:
```cpp
bool IsObjectInRectangle(const RoomObject& object,
int min_x, int min_y, int max_x, int max_y) {
auto [obj_x, obj_y, obj_width, obj_height] = GetObjectBounds(object);
int obj_min_x = obj_x;
int obj_max_x = obj_x + obj_width - 1;
int obj_min_y = obj_y;
int obj_max_y = obj_y + obj_height - 1;
bool x_overlap = (obj_min_x <= max_x) && (obj_max_x >= min_x);
bool y_overlap = (obj_min_y <= max_y) && (obj_max_y >= min_y);
return x_overlap && y_overlap;
}
```
This uses standard axis-aligned bounding box (AABB) intersection.
## Testing Strategy
### Unit Tests
Location: `test/unit/object_selection_test.cc`
**Coverage**:
- Single selection (replace existing)
- Multi-selection (Shift+click add)
- Toggle selection (Ctrl+click toggle)
- Rectangle selection (all modes)
- Select all
- Coordinate conversion
- Bounding box calculation
- Callback invocation
**Test Patterns**:
```cpp
// Setup
ObjectSelection selection;
std::vector<RoomObject> objects = CreateTestObjects();
// Action
selection.SelectObject(0, ObjectSelection::SelectionMode::Single);
// Verify
EXPECT_TRUE(selection.IsObjectSelected(0));
EXPECT_EQ(selection.GetSelectionCount(), 1);
```
### Integration Points
Test integration with:
1. `DungeonObjectInteraction` for input handling
2. `DungeonCanvasViewer` for rendering
3. `DungeonEditorV2` for undo/redo
## Performance Characteristics
| Operation | Complexity | Notes |
|-----------|------------|-------|
| `SelectObject` | O(log n) | Set insertion |
| `IsObjectSelected` | O(log n) | Set lookup |
| `GetSelectedIndices` | O(n) | Convert set to vector |
| `SelectObjectsInRect` | O(m * log n) | m objects checked |
| `DrawSelectionHighlights` | O(k) | k selected objects |
Where:
- n = total objects in selection
- m = total objects in room
- k = selected object count
## API Examples
### Single Selection
```cpp
// Replace selection with object 5
selection_.SelectObject(5, ObjectSelection::SelectionMode::Single);
```
### Building Multi-Selection
```cpp
// Start with object 0
selection_.SelectObject(0, ObjectSelection::SelectionMode::Single);
// Add objects 2, 4, 6
selection_.SelectObject(2, ObjectSelection::SelectionMode::Add);
selection_.SelectObject(4, ObjectSelection::SelectionMode::Add);
selection_.SelectObject(6, ObjectSelection::SelectionMode::Add);
// Toggle object 4 (remove it)
selection_.SelectObject(4, ObjectSelection::SelectionMode::Toggle);
// Result: Objects 0, 2, 6 selected
```
### Rectangle Selection
```cpp
// Begin selection at (10, 10)
selection_.BeginRectangleSelection(10, 10);
// Update to (50, 50) as user drags
selection_.UpdateRectangleSelection(50, 50);
// Complete selection (add mode)
selection_.EndRectangleSelection(objects, ObjectSelection::SelectionMode::Add);
```
### Working with Selected Objects
```cpp
// Get all selected indices (sorted)
auto indices = selection_.GetSelectedIndices();
// Get primary (first) selection
if (auto primary = selection_.GetPrimarySelection()) {
size_t index = primary.value();
// Use primary object...
}
// Check selection state
if (selection_.HasSelection()) {
size_t count = selection_.GetSelectionCount();
// Process selected objects...
}
```
## Integration Guide
See `OBJECT_SELECTION_INTEGRATION.md` for step-by-step integration instructions.
**Key Steps**:
1. Add `ObjectSelection` member to `DungeonObjectInteraction`
2. Update input handling to use selection modes
3. Replace manual selection state with `ObjectSelection` API
4. Implement click selection helper
5. Update rendering to use `ObjectSelection::Draw*()` methods
## Future Enhancements
### Planned Features
- **Lasso Selection**: Free-form polygon selection
- **Selection Filters**: Filter by object type, layer, size
- **Selection History**: Undo/redo for selection changes
- **Selection Groups**: Named selections (e.g., "All Chests")
- **Smart Selection**: Select similar objects (by type/size)
- **Marquee Zoom**: Zoom to fit selected objects
### API Extensions
```cpp
// Future API ideas
void SelectByType(int16_t object_id);
void SelectByLayer(RoomObject::LayerType layer);
void SelectSimilar(size_t reference_index);
void SaveSelectionGroup(const std::string& name);
void LoadSelectionGroup(const std::string& name);
```
## Debugging
### Enable Debug Logging
```cpp
// In object_selection.cc
#define SELECTION_DEBUG_LOGGING
// Logs will appear like:
// [ObjectSelection] SelectObject: index=5, mode=Single
// [ObjectSelection] Selection count: 3
```
### Visual Debugging
Use the Debug Controls card in the dungeon editor:
1. Enable "Show Object Bounds"
2. Filter by object type/layer
3. Inspect selection state in real-time
### Common Issues
**Issue**: Objects not selecting on click
**Solution**: Check object bounds calculation, verify coordinate conversion
**Issue**: Selection persists after clear
**Solution**: Ensure `NotifySelectionChanged()` is called
**Issue**: Visual artifacts during drag
**Solution**: Verify canvas scale is applied correctly in rendering
## References
- **ZScream**: Reference implementation for dungeon object selection
- **ImGui Test Engine**: Automated UI testing framework
- **yaze Canvas System**: `src/app/gui/canvas/canvas.h`
- **Theme System**: `src/app/editor/agent/agent_ui_theme.h`
## Changelog
### Version 1.0 (2025-11-26)
- Initial implementation
- Single/multi/rectangle selection
- Visual feedback with theme integration
- Comprehensive unit test coverage
- Integration with DungeonObjectInteraction
---
**Maintainer**: yaze development team
**Last Updated**: 2025-11-26

View File

@@ -0,0 +1,63 @@
# Overworld Editor Architecture
**Status**: Draft
**Last Updated**: 2025-11-21
**Related Code**: `src/app/editor/overworld/`, `src/zelda3/overworld/`
This document outlines the architecture of the Overworld Editor in YAZE.
## High-Level Overview
The Overworld Editor allows users to view and modify the game's overworld maps, including terrain (tiles), entities (sprites, entrances, exits, items), and map properties.
### Key Components
| Component | Location | Responsibility |
|-----------|----------|----------------|
| **OverworldEditor** | `src/app/editor/overworld/` | Main UI coordinator. Manages the `Overworld` data object, handles user input (mouse/keyboard), manages sub-editors (Tile16, GfxGroup), and renders the main view. |
| **Overworld** | `src/zelda3/overworld/` | System coordinator. Manages the collection of `OverworldMap` objects, global tilesets, palettes, and loading/saving of the entire overworld structure. |
| **OverworldMap** | `src/zelda3/overworld/` | Data model for a single overworld screen (Area). Manages its own graphics, properties, and ZSCustomOverworld data. |
| **OverworldEntityRenderer** | `src/app/editor/overworld/` | Helper class to render entities (sprites, entrances, etc.) onto the canvas. |
| **MapPropertiesSystem** | `src/app/editor/overworld/` | UI component for editing map-specific properties (music, palette, etc.). |
| **Tile16Editor** | `src/app/editor/overworld/` | Sub-editor for modifying the 16x16 tile definitions. |
## Interaction Flow
1. **Initialization**:
* `OverworldEditor` is initialized with a `Rom` pointer.
* It calls `overworld_.Load(rom)` which triggers the loading of all maps and global data.
2. **Rendering**:
* `OverworldEditor::DrawOverworldCanvas` is the main rendering loop.
* It iterates through visible `OverworldMap` objects.
* Each `OverworldMap` maintains a `Bitmap` of its visual state.
* `OverworldEditor` draws these bitmaps onto a `gui::Canvas`.
* `OverworldEntityRenderer` draws entities on top of the map.
3. **Editing**:
* **Tile Painting**: User selects a tile from the `Tile16Selector` (or scratch pad) and clicks on the map. `OverworldEditor` updates the `Overworld` data model (`SetTile`).
* **Entity Manipulation**: User can drag/drop entities. `OverworldEditor` updates the corresponding data structures in `Overworld`.
* **Properties**: Changes in the `MapPropertiesSystem` update the `OverworldMap` state and trigger a re-render.
## Coordinate Systems
* **Global Coordinates**: The overworld is conceptually a large grid.
* Light World: 8x8 maps.
* Dark World: 8x8 maps.
* Special Areas: Independent maps.
* **Map Coordinates**: Each map is 512x512 pixels (32x32 tiles of 16x16 pixels).
* **Tile Coordinates**: Objects are placed on a 16x16 grid within a map.
## Large Maps
ALttP combines smaller maps into larger scrolling areas (e.g., 2x2).
* **Parent Map**: The top-left map typically holds the main properties.
* **Child Maps**: The other 3 maps inherit properties from the parent but contain their own tile data.
* **ZSCustomOverworld**: Introduces more flexible map sizing (Wide, Tall, Large). The `Overworld` class handles the logic for configuring these (`ConfigureMultiAreaMap`).
## Deferred Loading
To improve performance, `OverworldEditor` implements a deferred texture creation system.
* Map data is loaded from ROM, but textures are not created immediately.
* `EnsureMapTexture` is called only when a map becomes visible, creating the SDL texture on-demand.
* `ProcessDeferredTextures` creates a batch of textures each frame to avoid stalling the UI.

View File

@@ -0,0 +1,54 @@
# Overworld Map Data Structure
**Status**: Draft
**Last Updated**: 2025-11-21
**Related Code**: `src/zelda3/overworld/overworld_map.h`, `src/zelda3/overworld/overworld_map.cc`
This document details the internal structure of an Overworld Map in YAZE.
## Overview
An `OverworldMap` represents a single screen of the overworld. In vanilla ALttP, these are indexed 0x00 to 0xBF.
### Key Data Structures
* **Map Index**: unique identifier (0-159).
* **Parent Index**: For large maps, points to the "main" map that defines properties.
* **Graphics**:
* `current_gfx_`: The raw graphics tiles loaded for this map.
* `current_palette_`: The 16-color palette rows used by this map.
* `bitmap_data_`: The rendered pixels (indexed color).
* **Properties**:
* `message_id_`: ID of the text message displayed when entering.
* `area_music_`: Music track IDs.
* `sprite_graphics_`: Which sprite sheets are loaded.
### Persistence (Loading/Saving)
* **Loading**:
* `Overworld::LoadOverworldMaps` iterates through all map IDs.
* `OverworldMap` constructor initializes basic data.
* `BuildMap` decompresses the tile data from ROM (Map32/Map16 conversion).
* **Saving**:
* `Overworld::SaveOverworldMaps` serializes the tile data back to the compressed format.
* It handles checking for space and repointing if the data size increases.
### ZSCustomOverworld Integration
The `OverworldMap` class has been extended to support ZSCustomOverworld (ZSO) features.
* **Custom Properties**:
* `area_specific_bg_color_`: Custom background color per map.
* `subscreen_overlay_`: ID for custom cloud/fog overlays.
* `animated_gfx_`: ID for custom animated tiles (water, flowers).
* `mosaic_expanded_`: Flags for per-map mosaic effects.
* **Data Storage**:
* These properties are stored in expanded ROM areas defined by ZSO.
* `LoadCustomOverworldData` reads these values from their specific ROM addresses.
### Overlay System
Some maps have interactive overlays (e.g., the cloud layer in the Desert Palace entrance).
* `overlay_id_`: ID of the overlay.
* `overlay_data_`: The compressed tile data for the overlay layer.
* The editor renders this on top of the base map if enabled.

View File

@@ -0,0 +1,278 @@
# ROM Architecture
This document describes the decoupled ROM architecture that separates generic SNES ROM handling from Zelda3-specific game data.
## Overview
The ROM system is split into two main components:
1. **`src/rom/`** - Generic SNES ROM container (game-agnostic)
2. **`src/zelda3/game_data.h`** - Zelda3-specific data structures
This separation enables:
- Cleaner code organization with single-responsibility modules
- Easier testing with mock ROMs
- Future support for other SNES games
- Better encapsulation of game-specific logic
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (EditorManager, Editors, CLI) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ zelda3::GameData │
│ - palette_groups (dungeon, overworld, sprites, etc.) │
│ - graphics_buffer (raw graphics data) │
│ - gfx_bitmaps (rendered graphics sheets) │
│ - blockset/spriteset/paletteset IDs │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Rom │
│ - LoadFromFile() / LoadFromData() │
│ - ReadByte() / WriteByte() / ReadWord() / WriteByte() │
│ - ReadTransaction() / WriteTransaction() │
│ - Save() │
└─────────────────────────────────────────────────────────────┘
```
## Core Components
### Rom Class (`src/rom/rom.h`)
The `Rom` class is a generic SNES ROM container with no game-specific logic:
```cpp
class Rom {
public:
// Loading
absl::Status LoadFromFile(const std::string& filename);
absl::Status LoadFromData(const std::vector<uint8_t>& data);
// Byte-level access
absl::StatusOr<uint8_t> ReadByte(size_t offset) const;
absl::Status WriteByte(size_t offset, uint8_t value);
absl::StatusOr<uint16_t> ReadWord(size_t offset) const;
absl::Status WriteWord(size_t offset, uint16_t value);
// Transactional access (RAII pattern)
absl::StatusOr<ReadTransaction> ReadTransaction(size_t offset, size_t size);
absl::StatusOr<WriteTransaction> WriteTransaction(size_t offset, size_t size);
// Persistence
absl::Status Save(const SaveSettings& settings);
// Properties
size_t size() const;
const uint8_t* data() const;
bool is_loaded() const;
};
```
### GameData Struct (`src/zelda3/game_data.h`)
The `GameData` struct holds all Zelda3-specific data:
```cpp
namespace zelda3 {
struct GameData {
// ROM reference (non-owning)
Rom* rom() const;
void set_rom(Rom* rom);
// Version info
zelda3_version version = zelda3_version::US;
std::string title;
// Graphics Resources
std::vector<uint8_t> graphics_buffer;
std::array<std::vector<uint8_t>, kNumGfxSheets> raw_gfx_sheets;
std::array<gfx::Bitmap, kNumGfxSheets> gfx_bitmaps;
std::array<gfx::Bitmap, kNumLinkSheets> link_graphics;
gfx::Bitmap font_graphics;
// Palette Groups
gfx::PaletteGroupMap palette_groups;
// Blockset/Spriteset/Paletteset IDs
std::array<std::array<uint8_t, 8>, kNumMainBlocksets> main_blockset_ids;
std::array<std::array<uint8_t, 4>, kNumRoomBlocksets> room_blockset_ids;
std::array<std::array<uint8_t, 4>, kNumSpritesets> spriteset_ids;
std::array<std::array<uint8_t, 4>, kNumPalettesets> paletteset_ids;
};
// Loading/Saving functions
absl::Status LoadGameData(Rom& rom, GameData& data, const LoadOptions& options = {});
absl::Status SaveGameData(Rom& rom, GameData& data);
} // namespace zelda3
```
### Transaction Classes (`src/rom/transaction.h`)
RAII wrappers for safe ROM access:
```cpp
class ReadTransaction {
public:
const uint8_t* data() const;
size_t size() const;
// Automatically validates bounds on construction
};
class WriteTransaction {
public:
uint8_t* data();
size_t size();
// Changes written on destruction or explicit commit
};
```
## Editor Integration
### EditorDependencies
Editors receive both `Rom*` and `GameData*` through the `EditorDependencies` struct:
```cpp
struct EditorDependencies {
Rom* rom = nullptr;
zelda3::GameData* game_data = nullptr; // Zelda3-specific game state
// ... other dependencies
};
```
### Base Editor Class
The `Editor` base class provides accessors for both:
```cpp
class Editor {
public:
// Set GameData for Zelda3-specific data access
virtual void set_game_data(zelda3::GameData* game_data) {
dependencies_.game_data = game_data;
}
// Accessors
Rom* rom() const { return dependencies_.rom; }
zelda3::GameData* game_data() const { return dependencies_.game_data; }
};
```
### GameData Propagation
The `EditorManager` propagates GameData to all editors after loading:
```cpp
// In EditorManager::LoadRom()
RETURN_IF_ERROR(zelda3::LoadGameData(*current_rom, current_session->game_data));
// Propagate to all editors
auto* game_data = &current_session->game_data;
current_editor_set->GetDungeonEditor()->set_game_data(game_data);
current_editor_set->GetOverworldEditor()->set_game_data(game_data);
current_editor_set->GetGraphicsEditor()->set_game_data(game_data);
current_editor_set->GetScreenEditor()->set_game_data(game_data);
current_editor_set->GetPaletteEditor()->set_game_data(game_data);
current_editor_set->GetSpriteEditor()->set_game_data(game_data);
```
## Accessing Game Data
### Before (Old Architecture)
```cpp
// Graphics buffer was on Rom class
auto& gfx_buffer = rom_->graphics_buffer();
// Palettes were on Rom class
const auto& palette = rom_->palette_group().dungeon_main[0];
```
### After (New Architecture)
```cpp
// Graphics buffer is on GameData
auto& gfx_buffer = game_data_->graphics_buffer;
// Palettes are on GameData
const auto& palette = game_data_->palette_groups.dungeon_main[0];
```
## File Structure
```
src/
├── rom/ # Generic SNES ROM module
│ ├── CMakeLists.txt
│ ├── rom.h # Rom class declaration
│ ├── rom.cc # Rom class implementation
│ ├── rom_diagnostics.h # Checksum/validation utilities
│ ├── rom_diagnostics.cc
│ ├── transaction.h # RAII transaction wrappers
│ └── snes.h # SNES hardware constants
└── zelda3/
├── game_data.h # GameData struct and loaders
├── game_data.cc # LoadGameData/SaveGameData impl
└── ... # Other Zelda3-specific modules
```
## Migration Guide
When updating code to use the new architecture:
1. **Change includes**: `#include "app/rom.h"``#include "rom/rom.h"`
2. **Add GameData include**: `#include "zelda3/game_data.h"`
3. **Update graphics access**:
```cpp
// Old
rom_->mutable_graphics_buffer()
// New
game_data_->graphics_buffer
```
4. **Update palette access**:
```cpp
// Old
rom_->palette_group().dungeon_main
// New
game_data_->palette_groups.dungeon_main
```
5. **Update LoadFromData calls**:
```cpp
// Old
rom.LoadFromData(data, false);
// New
rom.LoadFromData(data); // No second parameter
```
6. **For classes that need GameData**:
- Add `zelda3::GameData* game_data_` member
- Add `void set_game_data(zelda3::GameData*)` method
- Or use `game_data()` accessor from Editor base class
## Best Practices
1. **Use GameData for Zelda3-specific data**: Never store palettes or graphics on Rom
2. **Use Rom for raw byte access**: Load/save operations, byte reads/writes
3. **Propagate GameData early**: Set game_data before calling Load() on editors
4. **Use transactions for bulk access**: More efficient than individual byte reads
5. **Check game_data() before use**: Return error if null when required
## Related Documents
- [graphics_system_architecture.md](graphics_system_architecture.md) - Graphics loading and Arena system
- [dungeon_editor_system.md](dungeon_editor_system.md) - Dungeon editor architecture
- [overworld_editor_system.md](overworld_editor_system.md) - Overworld editor architecture

View File

@@ -0,0 +1,72 @@
# Room Data Persistence & Loading
**Status**: Complete
**Last Updated**: 2025-11-21
**Related Code**: `src/app/editor/dungeon/dungeon_room_loader.cc`, `src/app/editor/dungeon/dungeon_room_loader.h`, `src/zelda3/dungeon/room.cc`, `src/zelda3/dungeon/dungeon_rom_addresses.h`
This document details how dungeon rooms are loaded from and saved to the SNES ROM in YAZE, including the pointer table system, room size calculations, and thread safety considerations.
## Loading Process
The `DungeonRoomLoader` component is responsible for the heavy lifting of reading room data from the ROM. It handles:
* Decompression of ROM data
* Pointer table lookups
* Object parsing
* Room size calculations for safe editing
### Single Room Loading
**Method**: `DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room)`
**Process**:
1. **Validation**: Checks if ROM is loaded and room ID is in valid range (0x000-0x127)
2. **ROM Lookup**: Uses pointer table at `kRoomObjectLayoutPointer` to find the room data offset
3. **Decompression**: Decompresses the room data using SNES compression format
4. **Object Parsing**: Calls `room.LoadObjects()` to parse the object byte stream into structured `RoomObject` vectors
5. **Metadata Loading**: Loads room properties (graphics, palette, music) from ROM headers
### Bulk Loading (Multithreaded)
```cpp
absl::Status LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms);
```
To improve startup performance, YAZE loads all 296 rooms in parallel using `std::async`.
* **Concurrency**: Determines optimal thread count (up to 8).
* **Batching**: Divides rooms into batches for each thread.
* **Thread Safety**: Uses `std::mutex` when collecting results (sizes, palettes) into shared vectors.
* **Performance**: This significantly reduces the initial load time compared to serial loading.
## Data Structure & Size Calculation
ALttP stores dungeon rooms in a compressed format packed into ROM banks. Because rooms vary in size, editing them can change their length, potentially overwriting adjacent data.
### Size Calculation
The loader calculates the size of each room to ensure safe editing:
1. **Pointers**: Reads the pointer table to find the start address of each room.
2. **Bank Sorting**: Groups rooms by their ROM bank.
3. **Delta Calculation**: Sorts pointers within a bank and calculates the difference between adjacent room pointers to determine the available space for each room.
4. **End of Bank**: The last room in a bank is limited by the bank boundary (0xFFFF).
### Graphics Loading
1. **Graphics Loading**: `LoadRoomGraphics` reads the blockset configuration from the room header.
2. **Rendering**: `RenderRoomGraphics` draws the room's tiles into `bg1` and `bg2` buffers.
3. **Palette**: The loader resolves the palette ID from the header and loads the corresponding SNES palette colors.
## Saving Strategy (Planned/In-Progress)
When saving a room:
1. **Serialization**: Convert `RoomObject`s back into the game's byte format.
2. **Size Check**: Compare the new size against the calculated `room_size`.
3. **Repointing**:
* If the new data fits, overwrite in place.
* If it exceeds the space, the room must be moved to free space (expanded ROM area), and the pointer table updated.
* *Note: Repointing logic is a critical safety feature to prevent ROM corruption.*
## Key Challenges
* **Bank Boundaries**: SNES addressing is bank-based. Data cannot easily cross bank boundaries.
* **Shared Data**: Some graphics and palettes are shared between rooms. Modifying a shared resource requires care (or un-sharing/forking the data).
* **Pointer Tables**: There are multiple pointer tables (headers, objects, sprites, chests) that must be kept in sync.

View File

@@ -0,0 +1,140 @@
graph TB
subgraph "Development Environment"
DE[Developer Machine]
PC[Pre-commit Hooks]
PP[Pre-push Validation]
TC[Test Cache]
DE --> PC
PC --> PP
PP --> TC
end
subgraph "Test Build System"
PCH[Precompiled Headers]
INC[Incremental Build]
DEP[Dependency Tracking]
MOC[Mock Libraries]
PCH --> INC
INC --> DEP
DEP --> MOC
end
subgraph "Test Execution Engine"
TS[Test Selector]
TP[Test Parser]
TSH[Test Sharding]
PE[Parallel Executor]
RA[Result Aggregator]
TS --> TP
TP --> TSH
TSH --> PE
PE --> RA
end
subgraph "CI/CD Pipeline"
S1[Stage 1: Smoke<br/>2 min]
S2[Stage 2: Unit<br/>5 min]
S3[Stage 3: Integration<br/>15 min]
S4[Stage 4: Nightly<br/>60 min]
S1 --> S2
S2 --> S3
S3 -.-> S4
end
subgraph "Test Categories"
SMK[Smoke Tests<br/>Critical Path]
UNT[Unit Tests<br/>Fast Isolated]
INT[Integration Tests<br/>Multi-Component]
E2E[E2E Tests<br/>Full Workflows]
BEN[Benchmarks<br/>Performance]
FUZ[Fuzz Tests<br/>Security]
SMK --> UNT
UNT --> INT
INT --> E2E
E2E --> BEN
BEN --> FUZ
end
subgraph "Platform Testing"
MAC[macOS<br/>Metal/GPU]
WIN[Windows<br/>DirectX]
LIN[Linux<br/>Vulkan]
MAC -.-> GPU1[GPU Tests]
WIN -.-> GPU2[Rendering Tests]
LIN -.-> GPU3[Graphics Tests]
end
subgraph "Test Data Management"
ROM[ROM Files]
FIX[Fixtures]
MOK[Mocks]
GEN[Generated Data]
ROM --> TDC[Test Data Cache]
FIX --> TDC
MOK --> TDC
GEN --> TDC
end
subgraph "Monitoring & Analytics"
COL[Metrics Collector]
DB[Metrics Database]
DASH[Dashboard]
ALT[Alerting]
REP[Reports]
COL --> DB
DB --> DASH
DB --> ALT
DB --> REP
end
subgraph "Result Processing"
XML[JUnit XML]
JSON[JSON Output]
COV[Coverage Data]
PROF[Profile Data]
XML --> AGG[Aggregator]
JSON --> AGG
COV --> AGG
PROF --> AGG
AGG --> DB
end
subgraph "Caching Layer"
BIN[Binary Cache]
RES[Result Cache]
CCOV[Coverage Cache]
DEP2[Dependency Cache]
BIN --> CACHE[Distributed Cache]
RES --> CACHE
CCOV --> CACHE
DEP2 --> CACHE
end
%% Connections
DE --> TS
PP --> S1
TSH --> MAC
TSH --> WIN
TSH --> LIN
PE --> XML
RA --> COL
S3 --> COL
CACHE --> S1
TDC --> INT
style S1 fill:#90EE90
style S2 fill:#87CEEB
style S3 fill:#FFB6C1
style S4 fill:#DDA0DD
style DASH fill:#FFD700
style PE fill:#FF6347

View File

@@ -0,0 +1,49 @@
# YAZE Design Language & Interface Guidelines
This document defines the standard for User Interface (UI) development in `yaze`. All new components and refactors must adhere to these rules to ensure a consistent, theme-able, and configurable experience.
## 1. Core Philosophy
* **Configurability First:** Never assume a user's workflow. Every panel must be dockable, movable, and toggleable. Default layouts are just starting points.
* **Theme Compliance:** **Never** use hardcoded colors (e.g., `ImVec4(1, 0, 0, 1)`). All colors must be derived from the `ThemeManager` or standard `ImGui::GetStyle().Colors`.
* **Zelda-Native Inputs:** Hexadecimal is the first-class citizen for data. Decimal is for UI settings (window size, scaling).
## 2. Theming & Colors
* **Semantic Colors:** Use the `gui::Theme` abstraction.
* **Do:** `ImGui::PushStyleColor(ImGuiCol_Text, theme->GetColor(gui::ThemeCol_Error))`
* **Don't:** `ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f))`
* **Theme Integrity:** If a custom widget needs a color not in standard ImGui (e.g., "SRAM Modified" highlight), add it to `EnhancedTheme` in `theme_manager.h` rather than defining it locally.
* **Transparency:** Use `ImGui::GetStyle().Alpha` modifiers for disabled states rather than hardcoded grey values to support dark/light modes equally.
## 3. Layout Structure
The application uses a "VSCode-like" anatomy:
* **Activity Bar (Left):** Global context switching (Editor, Settings, Agent).
* *Rule:* Icons only. No text. Tooltips required.
* **Sidebar (Left, Docked):** Context-specific tools (e.g., Room List for Dungeon Editor).
* *Rule:* Must be collapsible. Width must be persistable.
* **Primary View (Center):** The canvas or main editor (e.g., Dungeon View).
* *Rule:* This is the "Central Node" in ImGui docking terms. It should rarely be hidden.
* **Panel Area (Bottom/Right):** Auxiliary tools (Log, Hex Inspector).
* *Rule:* Tabbed by default to save space.
## 4. Widget Standards
### A. Input Fields
* **Hexadecimal:** Use `gui::InputHexByte` / `gui::InputHexWord` wrapper.
* *Requirement:* Must support Scroll Wheel to increment/decrement values.
* *Requirement:* Monospace font is mandatory for hex values.
* **Text:** Use `gui::InputText` wrappers that handle `std::string` resizing automatically.
### B. Icons
* **Library:** Use Material Design icons via `ICON_MD_...` macros.
* **Alignment:** Icons must be vertically aligned with text. Use `ImGui::AlignTextToFramePadding()` before text if the icon causes misalignment.
### C. Containers
* **Collapsibles:** Prefer `ImGui::CollapsingHeader` for major sections and `ImGui::TreeNode` for hierarchy.
* **Tabs:** Use `ImGui::BeginTabBar` only for switching between distinct *views* (e.g., "Visual Editor" vs "Text Editor"). Do not use tabs for property categorization (use headers instead).
* **Tables:** Use `ImGui::BeginTable` with `ImGuiTableFlags_BordersInnerV` for property lists.
* *Format:* 2 Columns (Label, Control). Column 1 fixed width, Column 2 stretch.
## 5. Interaction Patterns
* **Hover:** All non-obvious interactions must have a `gui::Tooltip`.
* **Context Menus:** Right-click on *any* game object (sprite, tile) must show a context menu.
* **Drag & Drop:** "Source" and "Target" payloads must be strictly typed (e.g., `"PAYLOAD_SPRITE_ID"`).

View File

@@ -0,0 +1,176 @@
# YAZE UI Layout Documentation
This document describes the layout logic for the YAZE editor interface, specifically focusing on the menu bar and sidebar interactions.
## Menu Bar Layout
The main menu bar in `UICoordinator::DrawMenuBarExtras` handles the right-aligned status cluster.
### Right-Aligned Status Cluster
The status cluster in `DrawMenuBarExtras` includes (in order from left to right):
1. **Version**: `vX.Y.Z` (May be hidden on narrow windows)
2. **Dirty Indicator**: Warning-colored dot (Visible when ROM has unsaved changes)
3. **Session Switcher**: Layers icon (Visible when multiple sessions are open, may be hidden on narrow windows)
4. **Notification Bell**: Bell icon (Always visible - high priority)
### Panel Toggle Buttons
Panel toggle buttons are drawn at the end of the menu bar using screen coordinates:
1. **Panel Toggles**: Icons for Agent, Proposals, Settings, Properties
2. **WASM Toggle**: Chevron icon (Visible only in Emscripten builds)
These are positioned using `ImGui::SetCursorScreenPos()` with coordinates calculated from the true viewport (not the dockspace window). This ensures they remain in a fixed position even when panels open/close and the dockspace resizes.
### Button Styling
All menu bar icon buttons use consistent styling via `DrawMenuBarIconButton()`:
- Transparent background
- `SurfaceContainerHigh` color on hover
- `SurfaceContainerHighest` color when active/pressed
- `TextSecondary` color for inactive icons
- `Primary` color for active icons (e.g., when a panel is open)
### Sizing Calculation
The `cluster_width` is calculated dynamically using `GetMenuBarIconButtonWidth()` which accounts for:
- Icon text width (using `ImGui::CalcTextSize`)
- Frame padding (`FramePadding.x * 2`)
- Item spacing between elements (6px)
The number of panel toggle buttons is determined at compile time:
- With `YAZE_WITH_GRPC`: 4 buttons (Agent, Proposals, Settings, Properties)
- Without `YAZE_WITH_GRPC`: 3 buttons (Proposals, Settings, Properties)
### Responsive Behavior
When the window is too narrow to display all elements, they are hidden progressively based on priority:
1. **Always shown**: Notification bell, WASM toggle, dirty indicator
2. **High priority**: Version text
3. **Medium priority**: Session switcher button
4. **Low priority**: Panel toggle buttons
The available width is calculated as:
```cpp
float available_width = menu_bar_end - menu_items_end - padding;
```
### Right Panel Interaction
When the Right Panel (Agent, Settings, etc.) is expanded, it occupies the right side of the viewport.
The menubar uses **screen coordinate positioning** for optimal UX:
1. **Fixed Panel Toggles**: Panel toggle buttons are positioned using `ImGui::SetCursorScreenPos()` with coordinates calculated from the true viewport. This keeps them at a fixed screen position regardless of dockspace resizing.
2. **Status Cluster**: Version, dirty indicator, session button, and notification bell are drawn inside the dockspace menu bar using relative positioning. They shift naturally when panels open/close as the dockspace resizes.
```cpp
// Panel toggle screen positioning (in DrawMenuBarExtras)
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float panel_screen_x = viewport->WorkPos.x + viewport->WorkSize.x - panel_region_width;
if (panel_manager->IsPanelExpanded()) {
panel_screen_x -= panel_manager->GetPanelWidth();
}
ImGui::SetCursorScreenPos(ImVec2(panel_screen_x, menu_bar_y));
```
This ensures users can quickly toggle panels without chasing moving buttons.
## Menu Bar Positioning Patterns
When adding or modifying menu bar elements, choose the appropriate positioning strategy:
### Pattern 1: Relative Positioning (Elements That Shift)
Use standard `ImGui::SameLine()` with window-relative coordinates for elements that should move naturally when the dockspace resizes:
```cpp
const float window_width = ImGui::GetWindowWidth();
float start_pos = window_width - element_width - padding;
ImGui::SameLine(start_pos);
ImGui::Text("Shifting Element");
```
**Use for:** Version text, dirty indicator, session button, notification bell
**Behavior:** These elements shift left when a panel opens (dockspace shrinks)
### Pattern 2: Screen Positioning (Elements That Stay Fixed)
Use `ImGui::SetCursorScreenPos()` with true viewport coordinates for elements that should remain at a fixed screen position:
```cpp
// Get TRUE viewport dimensions (not affected by dockspace resize)
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float screen_x = viewport->WorkPos.x + viewport->WorkSize.x - element_width;
// Adjust for any open panels
if (panel_manager->IsPanelExpanded()) {
screen_x -= panel_manager->GetPanelWidth();
}
// Keep Y from current menu bar context
float screen_y = ImGui::GetCursorScreenPos().y;
// Position and draw
ImGui::SetCursorScreenPos(ImVec2(screen_x, screen_y));
ImGui::Button("Fixed Element");
```
**Use for:** Panel toggle buttons, any UI that should stay accessible when panels open
**Behavior:** These elements stay at a fixed screen position regardless of dockspace size
### Key Coordinate Functions
| Function | Returns | Use Case |
|----------|---------|----------|
| `ImGui::GetWindowWidth()` | Dockspace window width | Relative positioning within menu bar |
| `ImGui::GetMainViewport()->WorkSize.x` | True viewport width | Fixed screen positioning |
| `ImGui::GetWindowPos()` | Window screen position | Converting between coordinate systems |
| `ImGui::GetCursorScreenPos()` | Current cursor screen position | Getting Y coordinate for screen positioning |
| `ImGui::SetCursorScreenPos()` | N/A (sets position) | Positioning at absolute screen coordinates |
### Common Pitfall
Do NOT use `ImGui::GetWindowWidth()` when calculating fixed positions. The window width changes when panels open/close, causing elements to shift. Always use `ImGui::GetMainViewport()` for fixed positioning.
## Right Panel Styling
### Panel Header
The panel header uses an elevated background (`SurfaceContainerHigh`) with:
- Icon in primary color
- Title in standard text color
- Large close button (28x28) with rounded corners
- Keyboard shortcut: **Escape** closes the panel
### Panel Content Styling
Content uses consistent styling helpers:
- `BeginPanelSection()` / `EndPanelSection()`: Collapsible sections with icons
- `DrawPanelDivider()`: Themed separators
- `DrawPanelLabel()`: Secondary text color labels
- `DrawPanelValue()`: Label + value pairs
- `DrawPanelDescription()`: Wrapped disabled text for descriptions
### Color Scheme
- **Backgrounds**: `SurfaceContainer` for panel, `SurfaceContainerHigh` for sections
- **Borders**: `Outline` color
- **Text**: Primary for titles, Secondary for labels, Disabled for descriptions
- **Accents**: Primary color for icons and active states
## Sidebar Layout
The left sidebar (`EditorCardRegistry`) provides navigation for editor cards.
### Placeholder Sidebar
When no ROM is loaded, `EditorManager::DrawPlaceholderSidebar` renders a placeholder.
- **Theme**: Uses `Surface Container` color for background to distinguish it from the main window.
- **Content**: Displays "Open ROM" and "New Project" buttons.
- **Behavior**: Fills the full height of the viewport work area (below the dockspace menu bar).
### Active Sidebar
When a ROM is loaded, the sidebar displays editor categories and cards.
- **Width**: Fixed width defined in `EditorCardRegistry`.
- **Collapse**: Can be collapsed via the hamburger menu in the menu bar or `Ctrl+B`.
- **Theme**: Matches the placeholder sidebar for consistency.
## Theme Integration
The UI uses `ThemeManager` for consistent colors:
- **Sidebar Background**: `gui::GetSurfaceContainerVec4()`
- **Sidebar Border**: `gui::GetOutlineVec4()`
- **Text**: `gui::GetTextSecondaryVec4()` (for placeholders)

View File

@@ -0,0 +1,58 @@
# Undo/Redo System Architecture
**Status**: Draft
**Last Updated**: 2025-11-21
**Related Code**: `src/zelda3/dungeon/dungeon_object_editor.h`, `src/zelda3/dungeon/dungeon_editor_system.h`
This document outlines the Undo/Redo architecture used in the Dungeon Editor.
## Overview
The system employs a command pattern approach where the state of the editor is snapshotted before destructive operations. Currently, there are two distinct undo stacks:
1. **DungeonObjectEditor Stack**: Handles granular operations within the Object Editor (insert, move, delete, resize).
2. **DungeonEditorSystem Stack**: (Planned/Partial) Intended for high-level operations and other modes (sprites, items), but currently delegates to the Object Editor for object operations.
## DungeonObjectEditor Implementation
The `DungeonObjectEditor` maintains its own local history of `UndoPoint`s.
### Data Structures
```cpp
struct UndoPoint {
std::vector<RoomObject> objects; // Snapshot of all objects in the room
SelectionState selection; // Snapshot of selection (indices, drag state)
EditingState editing; // Snapshot of editing mode/settings
std::chrono::steady_clock::time_point timestamp;
};
```
### Workflow
1. **Snapshot Creation**: Before any operation that modifies the room (Insert, Delete, Move, Resize), `CreateUndoPoint()` is called.
2. **Snapshot Storage**: The current state (objects list, selection, mode) is copied into an `UndoPoint` and pushed to `undo_history_`.
3. **Limit**: The history size is capped (currently 50) to limit memory usage.
4. **Undo**:
* The current state is moved to `redo_history_`.
* The last `UndoPoint` is popped from `undo_history_`.
* `ApplyUndoPoint()` restores the `objects` vector and selection state to the room.
5. **Redo**:
* Similar to Undo, but moves from `redo_history_` back to active state and `undo_history_`.
### Batch Operations
For batch operations (e.g., `BatchMoveObjects`, `PasteObjects`), a single `UndoPoint` is created before the loop that processes all items. This ensures that one "Undo" command reverts the entire batch operation.
## DungeonEditorSystem Role
The `DungeonEditorSystem` acts as a high-level coordinator.
* **Delegation**: When `Undo()`/`Redo()` is called on the system while in `kObjects` mode, it forwards the call to `DungeonObjectEditor`.
* **Future Expansion**: It has its own `undo_history_` structure intended to capture broader state (sprites, chests, entrances), but this is currently a TODO.
## Best Practices for Contributors
* **Always call `CreateUndoPoint()`** before modifying the object list.
* **Snapshot effectively**: The current implementation snapshots the *entire* object list. For very large rooms (which are rare in ALttP), this might be optimized in the future, but it's sufficient for now.
* **State Consistency**: Ensure `UndoPoint` captures enough state to fully restore the context (e.g., selection). If you add new state variables that affect editing, add them to `UndoPoint`.

View File

@@ -0,0 +1,133 @@
# ZSCustomOverworld Integration
**Status**: Draft
**Last Updated**: 2025-11-21
**Related Code**: `src/zelda3/overworld/overworld.cc`, `src/zelda3/overworld/overworld_map.cc`
This document details how YAZE integrates with ZSCustomOverworld (ZSO), a common assembly patch for ALttP that expands overworld capabilities.
## Feature Support
YAZE supports the following ZSO features:
### 1. Multi-Area Maps (Map Sizing)
Vanilla ALttP has limited map configurations. ZSO allows any map to be part of a larger scrolling area.
* **Area Sizes**: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2).
* **Implementation**: `Overworld::ConfigureMultiAreaMap` updates the internal ROM tables that define map relationships and scrolling behavior.
### 2. Custom Graphics & Palettes
* **Per-Area Background Color**: Allows specific background colors for each map, overriding the default world color.
* Storage: `OverworldCustomAreaSpecificBGPalette` (0x140000)
* **Animated Graphics**: Assigns different animated tile sequences (water, lava) per map.
* Storage: `OverworldCustomAnimatedGFXArray` (0x1402A0)
* **Main Palette Override**: Allows changing the main 16-color palette per map.
### 3. Visual Effects
* **Mosaic Effect**: Enables the pixelation effect on a per-map basis.
* Storage: `OverworldCustomMosaicArray` (0x140200)
* **Subscreen Overlay**: Controls cloud/fog layers.
* Storage: `OverworldCustomSubscreenOverlayArray` (0x140340)
## ASM Patching
YAZE includes the capability to apply the ZSO ASM patch directly to a ROM.
* **Method**: `OverworldEditor::ApplyZSCustomOverworldASM`
* **Process**:
1. Checks current ROM version.
2. Uses `core::AsarWrapper` to apply the assembly patch.
3. Updates version markers in the ROM header.
4. Initializes the new data structures in expanded ROM space.
## Versioning & ROM Detection
The editor detects the ZSO version present in the ROM to enable/disable features.
### Version Detection
- **Source**: `overworld_version_helper.h` - Contains version detection logic
- **Check Point**: ROM header byte `asm_version` at `0x140145` indicates which ZSO version is installed
- **Supported Versions**: Vanilla (0xFF), v1, v2, v3 (with v3 being the most feature-rich)
- **Key Method**: `OverworldMap::SetupCustomTileset(uint8_t asm_version)` - Initializes custom properties based on detected version
### Version Feature Matrix
| Feature | Address | Vanilla | v1 | v2 | v3 |
|---------|---------|---------|----|----|-----|
| Custom BG Colors | 0x140000 | No | No | Yes | Yes |
| Main Palette Array | 0x140040 | No | No | Yes | Yes |
| Area Enum (Wide/Tall) | 0x1417F8 | No | No | No | Yes |
| Diggable Tiles | 0x140980 | No | No | No | Yes |
| Custom Tile GFX | 0x1409B0 | No | No | No | Yes |
### Version Checking in Save Operations
**CRITICAL**: All save functions that write to custom ASM address space (0x140000+) must check ROM version before writing. This prevents vanilla ROM corruption.
**Correct Pattern:**
```cpp
absl::Status Overworld::SaveAreaSpecificBGColors() {
auto version = OverworldVersionHelper::GetVersion(*rom_);
if (!OverworldVersionHelper::SupportsCustomBGColors(version)) {
return absl::OkStatus(); // Vanilla/v1 ROM - skip custom address writes
}
// ... proceed with writing to 0x140000+
}
```
**Functions with Version Checks:**
- `SaveAreaSpecificBGColors()` - Requires v2+ (custom BG colors)
- `SaveCustomOverworldASM()` - Gates v2+ and v3+ features separately
- `SaveDiggableTiles()` - Requires v3+ (diggable tiles)
- `SaveAreaSizes()` - Requires v3+ (area enum support)
### UI Adaptation
- `MapPropertiesSystem` shows/hides ZSO-specific controls based on detected version
- Version 1 controls are hidden if ROM doesn't have v1 ASM patch
- Version 3 controls appear only when ROM has v3+ patch installed
- Helpful messages displayed for unsupported features (e.g., "Requires ZSCustomOverworld v3+")
### Storage Locations
ROM addresses for ZSCustomOverworld data (expanded ROM area):
| Feature | Constant | Address | Size | Notes |
|---------|----------|---------|------|-------|
| Area-Specific BG Palette | OverworldCustomAreaSpecificBGPalette | 0x140000 | 2 bytes × 160 maps | Per-map override for background color |
| Main Palette Override | OverworldCustomMainPaletteArray | 0x140160 | 1 byte × 160 maps | Per-map main palette selection |
| Mosaic Effect | OverworldCustomMosaicArray | 0x140200 | 1 byte × 160 maps | Pixelation effect per-map |
| Subscreen Overlay | OverworldCustomSubscreenOverlayArray | 0x140340 | 2 bytes × 160 maps | Cloud/fog layer IDs |
| Animated GFX | OverworldCustomAnimatedGFXArray | 0x1402A0 | 1 byte × 160 maps | Water, lava animation sets |
| Custom Tile GFX | OverworldCustomTileGFXGroupArray | 0x140480 | 8 bytes × 160 maps | Custom tile graphics groups |
| Feature Enables | Various (0x140141-0x140148) | — | 1 byte each | Toggle flags for each feature |
## Implementation Details
### Configuration Method
```cpp
// This is the critical method for multi-area map configuration
absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size);
```
**Process**:
1. Takes parent map index and desired size
2. Updates ROM parent ID table at appropriate address based on ZSO version
3. Recalculates scroll positions for area boundaries
4. Persists changes back to ROM
5. Reloads affected map data
**Never set `area_size` property directly** - Always use `ConfigureMultiAreaMap()` to ensure ROM consistency.
### Custom Properties Access
```cpp
// Get mutable reference to a map
auto& map = overworld_.maps[map_id];
// Check if custom features are available
if (rom->asm_version >= 1) {
map.SetupCustomTileset(rom->asm_version);
// Now custom properties are initialized
uint16_t custom_bg_color = map.area_specific_bg_color_;
}
// Modify custom properties
map.subscreen_overlay_ = new_overlay_id; // Will be saved to ROM on next save
```

View File

@@ -0,0 +1,198 @@
# Dungeon Object Rendering Fix Plan
## Completed Phases
### Phase 1: BG Layer Draw Order - COMPLETED (2025-11-26)
**File:** `src/app/editor/dungeon/dungeon_canvas_viewer.cc:900-971`
**Fix:** Swapped draw order - BG2 drawn first (background), then BG1 (foreground with objects)
**Result:** Objects on BG1 no longer covered by BG2
### Phase 2: Wall Rendering Investigation - COMPLETED (2025-11-26)
**Finding:** Bitmap initialization was the issue, not draw routines
**Root Cause:** Test code was creating bitmap with `{0}` which only allocated 1 byte instead of 262144
**Verification:** Created ROM-dependent integration tests:
- `test/integration/zelda3/dungeon_graphics_transparency_test.cc`
- All 5 tests pass confirming:
- Graphics buffer has 13% transparent pixels
- Room graphics buffer has 31.9% transparent pixels
- Wall objects load 8 tiles each correctly
- BG1 has 24,000 non-zero pixels after object drawing
**Wall tiles confirmed working:** 0x090, 0x092, 0x093, 0x096, 0x098, 0x099, 0x0A2, 0x0A4, 0x0A5, 0x0AC, 0x0AD
### Phase 3: Subtype1 Tile Count Lookup Table - COMPLETED (2025-11-26)
**File:** `src/zelda3/dungeon/object_parser.cc:18-57`
**Fix:** Added `kSubtype1TileLengths[0xF8]` lookup table from ZScream's DungeonObjectData.cs
**Changes:**
- Added 248-entry tile count lookup table for Subtype 1 objects
- Modified `GetSubtype1TileCount()` helper function to use lookup table
- Updated `GetObjectSubtype()` and `ParseSubtype1()` to use dynamic tile counts
- Objects now load correct number of tiles (e.g., 0xC1 = 68 tiles, 0x33 = 16 tiles)
**Source:** [ZScream DungeonObjectData.cs](https://github.com/Zarby89/ZScreamDungeon)
## All Critical Phases Complete
**Root Cause Summary**: Multiple issues - layer draw order (FIXED), bitmap sizing (FIXED), tile counts (FIXED).
## Critical Findings
### Finding 1: Hardcoded Tile Count (ROOT CAUSE)
- **Location**: `src/zelda3/dungeon/object_parser.cc:141,160,178`
- **Issue**: `ReadTileData(tile_data_ptr, 8)` always reads 8 tiles
- **Impact**:
- Simple objects (walls: 8 tiles) render correctly
- Medium objects (carpets: 16 tiles) render incomplete
- Complex objects (Agahnim's altar: 84 tiles) severely broken
- **Fix**: Use ZScream's `subtype1Lengths[]` lookup table
### Finding 2: Type 2/Type 3 Boundary Collision
- **Location**: `src/zelda3/dungeon/room_object.cc:184-190, 204-208`
- **Issue**: Type 2 objects with Y positions 3,7,11,...,63 encode to `b3 >= 0xF8`, triggering incorrect Type 3 decoding
- **Impact**: 512 object placements affected
### Finding 3: Type 2 Subtype Index Mask
- **Location**: `src/zelda3/dungeon/object_parser.cc:77, 147-148`
- **Issue**: Uses mask `0x7F` for 256 IDs, causing IDs 0x180-0x1FF to alias to 0x100-0x17F
- **Fix**: Use `object_id & 0xFF` or `object_id - 0x100`
### Finding 4: Type 3 Subtype Heuristic
- **Location**: `src/zelda3/dungeon/room_object.cc:18-28, 74`
- **Issue**: `GetSubtypeTable()` uses `id_ >= 0x200` but Type 3 IDs are 0xF00-0xFFF
- **Fix**: Change to `id_ >= 0xF00`
### Finding 5: Object ID Validation Range
- **Location**: `src/zelda3/dungeon/room.cc:966`
- **Issue**: Validates `r.id_ <= 0x3FF` but decoder can produce IDs up to 0xFFF
- **Fix**: Change to `r.id_ <= 0xFFF`
### Finding 6: tile_objects_ Not Cleared on Reload
- **Location**: `src/zelda3/dungeon/room.cc:908`
- **Issue**: Calling LoadObjects() twice causes object duplication
- **Fix**: Add `tile_objects_.clear()` at start of LoadObjects()
### Finding 7: Incomplete Draw Routine Registry
- **Location**: `src/zelda3/dungeon/object_drawer.cc:170`
- **Issue**: Reserves 35 routines but only initializes 17 (indices 0-16)
- **Impact**: Object IDs mapping to routines 17-34 fallback to 1x1 drawing
## Implementation Plan
### Phase 1: Fix BG Layer Draw Order (CRITICAL - DO FIRST)
**File:** `src/app/editor/dungeon/dungeon_canvas_viewer.cc`
**Location:** `DrawRoomBackgroundLayers()` (lines 900-968)
**Problem:** BG1 is drawn first, then BG2 is drawn ON TOP with 255 alpha, covering BG1 content.
**Fix:** Swap the draw order - draw BG2 first (background), then BG1 (foreground):
```cpp
void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) {
// ... validation code ...
// Draw BG2 FIRST (background layer - underneath)
if (layer_settings.bg2_visible && bg2_bitmap.is_active() ...) {
// ... existing BG2 draw code ...
}
// Draw BG1 SECOND (foreground layer - on top)
if (layer_settings.bg1_visible && bg1_bitmap.is_active() ...) {
// ... existing BG1 draw code ...
}
}
```
### Phase 2: Investigate North/South Wall Draw Routines
**Observation:** Left/right walls (vertical, Downwards routines 0x60+) work, but up/down walls (horizontal, Rightwards routines 0x00-0x0B) don't.
**Files to check:**
- `src/zelda3/dungeon/object_drawer.cc` - Rightwards draw routines
- Object-to-routine mapping for wall IDs
**Wall Object IDs:**
- 0x00: Ceiling (routine 0 - DrawRightwards2x2_1to15or32)
- 0x01-0x02: North walls (routine 1 - DrawRightwards2x4_1to15or26)
- 0x03-0x04: South walls (routine 2 - DrawRightwards2x4spaced4_1to16)
- 0x60+: East/West walls (Downwards routines - WORKING)
**Possible issues:**
1. Object tiles not being loaded for Subtype1 0x00-0x0B
2. Draw routines have coordinate bugs
3. Objects assigned to wrong layer (BG2 instead of BG1)
### Phase 3: Subtype1 Tile Count Fix
**Files to modify:**
- `src/zelda3/dungeon/object_parser.cc`
Add ZScream's tile count lookup table:
```cpp
static constexpr uint8_t kSubtype1TileLengths[0xF8] = {
04,08,08,08,08,08,08,04,04,05,05,05,05,05,05,05,
05,05,05,05,05,05,05,05,05,05,05,05,05,05,05,05,
05,09,03,03,03,03,03,03,03,03,03,03,03,03,03,06,
06,01,01,16,01,01,16,16,06,08,12,12,04,08,04,03,
03,03,03,03,03,03,03,00,00,08,08,04,09,16,16,16,
01,18,18,04,01,08,08,01,01,01,01,18,18,15,04,03,
04,08,08,08,08,08,08,04,04,03,01,01,06,06,01,01,
16,01,01,16,16,08,16,16,04,01,01,04,01,04,01,08,
08,12,12,12,12,18,18,08,12,04,03,03,03,01,01,06,
08,08,04,04,16,04,04,01,01,01,01,01,01,01,01,01,
01,01,01,01,24,01,01,01,01,01,01,01,01,01,01,01,
01,01,16,03,03,08,08,08,04,04,16,04,04,04,01,01,
01,68,01,01,08,08,08,08,08,08,08,01,01,28,28,01,
01,08,08,00,00,00,00,01,08,08,08,08,21,16,04,08,
08,08,08,08,08,08,08,08,08,01,01,01,01,01,01,01,
01,01,01,01,01,01,01,01
};
```
### Phase 4: Object Type Detection Fixes (Deferred)
- Type 2/Type 3 boundary collision
- Type 2 index mask (0x7F vs 0xFF)
- Type 3 detection heuristic (0x200 vs 0xF00)
### Phase 5: Validation & Lifecycle Fixes (Deferred)
- Object ID validation range (0x3FF → 0xFFF)
- tile_objects_ not cleared on reload
### Phase 6: Draw Routine Completion (Deferred)
- Complete draw routine registry (routines 17-34)
## Testing Strategy
### Test Objects by Complexity
| Object ID | Tiles | Description | Expected Result |
|-----------|-------|-------------|-----------------|
| 0x000 | 4 | Ceiling | Works |
| 0x001 | 8 | Wall (north) | Works |
| 0x033 | 16 | Carpet | Should render fully |
| 0x0C1 | 68 | Chest platform | Should render fully |
| 0x215 | 80 | Prison cell | Should render fully |
### Rooms to Test
- Room 0x00 (Simple walls)
- Room with carpets
- Agahnim's tower rooms
- Fortune teller room (uses 242-tile objects)
## Files to Read Before Implementation
1. `/Users/scawful/Code/yaze/src/zelda3/dungeon/object_parser.cc` - **PRIMARY** - Find the hardcoded `8` in tile loading
2. `/Users/scawful/Code/ZScreamDungeon/ZeldaFullEditor/Data/DungeonObjectData.cs` - Verify tile table values
## Estimated Impact
- **Phase 1 alone** should fix ~90% of broken Subtype1 objects (most common type)
- Simple walls/floors already work (they use 4-8 tiles)
- Carpets (16 tiles), chest platforms (68 tiles), and complex objects will now render fully
## Risk Assessment
- **Low Risk**: Adding a lookup table is additive, doesn't change existing logic flow
- **Mitigation**: Compare visual output against ZScream for a few test rooms

View File

@@ -0,0 +1,263 @@
# Editor System Refactoring - Complete Summary
**Date:** 2025-11-27
**Status:** ✅ Phase 1 & 2 Complete
**Build Status:** ✅ Full app compiles successfully
## Overview
Completed incremental refactoring of the editor manager and UI system focusing on:
- **Phase 1:** Layout initialization and reset reliability
- **Phase 2:** Sidebar UX improvements and state persistence
## Phase 1: Layout Initialization/Reset
### Objectives
- Fix layout reset not properly rebuilding dockspaces
- Ensure emulator layout initializes correctly
- Add rebuild flag system for deferred layout updates
### Key Changes
**1. RebuildLayout() Method** (`layout_manager.h` + `.cc`)
- Forces layout rebuild even if already initialized
- Validates dockspace exists before building
- Tracks last dockspace ID and editor type
- Duplicates InitializeEditorLayout logic but clears flags first
**2. Rebuild Flag Integration** (`editor_manager.cc`)
- Update() loop checks `IsRebuildRequested()`
- Validates ImGui frame scope before rebuilding
- Determines correct editor type (Emulator or current)
- Auto-clears flag after rebuild
**3. Emulator Layout Trigger** (`editor_manager.cc`)
- `SwitchToEditor(kEmulator)` triggers `InitializeEditorLayout`
- Frame validation ensures ImGui context ready
- Layout built with 7 emulator cards docked properly
**4. Emulator in Sidebar** (`editor_manager.cc`)
- "Emulator" added to active_categories when visible
- Emulator cards appear in sidebar alongside other editors
### Coverage: All 11 Editor Types
| Editor | Build Method | Status |
|--------|--------------|--------|
| Overworld | BuildOverworldLayout | ✅ |
| Dungeon | BuildDungeonLayout | ✅ |
| Graphics | BuildGraphicsLayout | ✅ |
| Palette | BuildPaletteLayout | ✅ |
| Screen | BuildScreenLayout | ✅ |
| Music | BuildMusicLayout | ✅ |
| Sprite | BuildSpriteLayout | ✅ |
| Message | BuildMessageLayout | ✅ |
| Assembly | BuildAssemblyLayout | ✅ |
| Settings | BuildSettingsLayout | ✅ |
| Emulator | BuildEmulatorLayout | ✅ |
## Phase 2: Sidebar UX Improvements
### Issues Addressed
- Sidebar state didn't persist (always started collapsed)
- Expand button in menu bar (inconsistent with collapse button position)
- No visual feedback for active category
- Categories didn't show enabled/disabled state
- Layout offset broken when sidebar collapsed
- Menu bar could overflow with no indication
### Key Changes
**1. State Persistence** (`user_settings.h/cc`, `editor_manager.cc`)
```cpp
// Added to UserSettings::Preferences
bool sidebar_collapsed = false;
bool sidebar_tree_view_mode = true;
std::string sidebar_active_category;
```
- Auto-saves on every toggle/switch via callbacks
- Restored on app startup
**2. Fixed Expand Button** (`editor_card_registry.cc`)
- Collapsed sidebar shows 16px thin strip
- Expand button at same position as collapse button
- Both sidebars (icon + tree) have symmetric behavior
**3. Category Enabled States** (`editor_card_registry.h/cc`)
- Categories requiring ROM grayed out (40% opacity)
- Tooltip: "📁 Open a ROM first | Use File > Open ROM..."
- Emulator always enabled (doesn't require ROM)
- Click disabled category → No action
**4. Enhanced Visual Feedback**
- **Active category:** 4px accent bar, 90% accent button color
- **Inactive category:** 50% opacity, 130% brightness on hover
- **Disabled category:** 30% opacity, minimal hover
- **Rich tooltips:** Icon + name + status + shortcuts
**5. Fixed Layout Offset** (`editor_manager.h`)
```cpp
GetLeftLayoutOffset() {
if (collapsed) return 16.0f; // Reserve strip space
return tree_mode ? 200.0f : 48.0f;
}
```
- Dockspace no longer overlaps collapsed sidebar
- Right panel interaction doesn't break sidebar
**6. Responsive Menu Bar** (`ui_coordinator.cc`)
- Progressive hiding: Version → Session → Dirty
- Notification bell shows hidden elements in tooltip
- Bell always visible as fallback information source
## Architecture Improvements
### Callback System
**Pattern:** User Action → UI Component → Callback → Save Settings
**Callbacks Added:**
```cpp
card_registry_.SetSidebarStateChangedCallback((collapsed, tree_mode) Save);
card_registry_.SetCategoryChangedCallback((category) Save);
card_registry_.SetShowEmulatorCallback(() ShowEmulator);
card_registry_.SetShowSettingsCallback(() ShowSettings);
card_registry_.SetShowCardBrowserCallback(() ShowCardBrowser);
```
### Layout Rebuild Flow
```
Menu "Reset Layout"
→ OnResetWorkspaceLayout() queued as deferred action
→ EditorManager::ResetWorkspaceLayout()
→ ClearInitializationFlags()
→ RequestRebuild()
→ RebuildLayout(type, dockspace_id) // Immediate if in frame
→ Next Update(): Checks rebuild_requested_ flag
→ RebuildLayout() if not done yet
→ ClearRebuildRequest()
```
### Multi-Session Coordination
**Sidebar State:** Global (not per-session)
- UI preference persists across all sessions
- Switching sessions doesn't change sidebar layout
**Categories Shown:** Session-aware
- Active editors contribute categories
- Emulator adds "Emulator" when visible
- Multiple sessions can show different categories
## Files Modified
| File | Phase 1 | Phase 2 | Lines Changed |
|------|---------|---------|---------------|
| layout_manager.h | ✅ | | +15 |
| layout_manager.cc | ✅ | | +132 |
| editor_manager.h | ✅ | ✅ | +8 |
| editor_manager.cc | ✅ | ✅ | +55 |
| editor_card_registry.h | | ✅ | +25 |
| editor_card_registry.cc | | ✅ | +95 |
| user_settings.h | | ✅ | +5 |
| user_settings.cc | | ✅ | +12 |
| ui_coordinator.h | | ✅ | +3 |
| ui_coordinator.cc | | ✅ | +50 |
**Total:** 10 files, ~400 lines of improvements
## Testing Verification
### Compilation
✅ Full app builds successfully (zero errors)
✅ Editor library builds independently
✅ All dependencies resolve correctly
### Integration Points Verified
✅ Layout reset works for all 11 editor types
✅ Emulator layout initializes on first open
✅ Emulator layout resets properly
✅ Sidebar state persists across launches
✅ Sidebar doesn't overlap/conflict with right panel
✅ Category enabled states work correctly
✅ Menu bar responsive behavior functions
✅ Callbacks trigger and save without errors
## User Experience Before/After
### Layout Reset
**Before:**
- Inconsistent - sometimes worked, sometimes didn't
- Emulator layout ignored
- No fallback mechanism
**After:**
- Reliable - uses RebuildLayout() to force reset
- Emulator layout properly handled
- Deferred rebuild if not in valid frame
### Sidebar Interaction
**Before:**
- Always started collapsed
- Expand button in menu bar (far from collapse)
- No visual feedback for active category
- All categories always enabled
- Sidebar disappeared when right panel opened
**After:**
- Starts in saved state (default: expanded, tree view)
- Expand button in same spot as collapse (16px strip)
- 4px accent bar shows active category
- ROM-requiring categories grayed out with helpful tooltips
- Sidebar reserves 16px even when collapsed (no disappearing)
### Menu Bar
**Before:**
- Could overflow with no indication
- All elements always shown regardless of space
**After:**
- Progressive hiding when tight: Version → Session → Dirty
- Hidden elements shown in notification bell tooltip
- Bell always visible as info source
## Known Limitations & Future Work
### Not Implemented (Deferred)
- Sidebar collapse/expand animation
- Category priority/ordering system
- Collapsed sidebar showing vertical category icons
- Dockspace smooth resize on view mode toggle
### Phase 3 Scope (Next)
- Agent chat widget integration improvements
- Proposals panel update notifications
- Unified panel toggle behavior
### Phase 4 Scope (Future)
- ShortcutRegistry as single source of truth
- Shortcut conflict detection
- Visual shortcut cheat sheet
## Summary
**Phase 1 + 2 Together Provide:**
- ✅ Reliable layout management across all editors
- ✅ Professional sidebar UX matching VSCode
- ✅ State persistence for user preferences
- ✅ Clear visual feedback and enabled states
- ✅ Responsive design adapting to space constraints
- ✅ Proper emulator integration throughout
**Architecture Quality:**
- Clean callback architecture for state management
- Proper separation of concerns (UI vs persistence)
- Defensive coding (frame validation, null checks)
- Comprehensive logging for debugging
**Ready for production use and Phase 3 development.**

View File

@@ -0,0 +1,153 @@
# Layout Reset Implementation - Verification Summary
**Date:** 2025-11-27
**Status:** ✅ Complete
**Build Status:** ✅ Compiles successfully
## Changes Implemented
### 1. RebuildLayout() Method (LayoutManager)
**File:** `src/app/editor/ui/layout_manager.h` + `.cc`
**Added:**
- `void RebuildLayout(EditorType type, ImGuiID dockspace_id)` - Forces layout rebuild even if already initialized
- `ImGuiID last_dockspace_id_` - Tracks last used dockspace for rebuild operations
- `EditorType current_editor_type_` - Tracks current editor type
**Features:**
- Validates dockspace exists before rebuilding
- Clears initialization flag to force rebuild
- Rebuilds layout using same logic as InitializeEditorLayout
- Finalizes with DockBuilderFinish and marks as initialized
- Comprehensive logging for debugging
### 2. Rebuild Flag Integration (EditorManager)
**File:** `src/app/editor/editor_manager.cc` (Update loop, lines 651-675)
**Added:**
- Check for `layout_manager_->IsRebuildRequested()` in Update() loop
- Validates ImGui frame state before rebuilding
- Determines correct editor type (Emulator or current_editor_)
- Executes rebuild and clears flag
**Flow:**
```
Update() → Check rebuild_requested_ → Validate frame → Determine editor type → RebuildLayout() → Clear flag
```
### 3. Emulator Layout Trigger (EditorManager)
**File:** `src/app/editor/editor_manager.cc` (SwitchToEditor, lines 1918-1927)
**Enhanced:**
- Emulator now triggers `InitializeEditorLayout(kEmulator)` on activation
- Frame validation ensures ImGui context is valid
- Logging confirms layout initialization
### 4. Emulator in Sidebar (EditorManager)
**File:** `src/app/editor/editor_manager.cc` (Update loop, lines 741-747)
**Added:**
- "Emulator" category added to active_categories when `IsEmulatorVisible()` is true
- Prevents duplicate entries with `std::find` check
- Emulator cards now appear in sidebar when emulator is active
## Editor Type Coverage
All editor types have complete layout support:
| Editor Type | Build Method | Cards Shown on Init | Verified |
|------------|--------------|---------------------|----------|
| kOverworld | BuildOverworldLayout | canvas, tile16_selector | ✅ |
| kDungeon | BuildDungeonLayout | room_list, canvas, object_editor | ✅ |
| kGraphics | BuildGraphicsLayout | sheet_browser, sheet_editor | ✅ |
| kPalette | BuildPaletteLayout | 5 palette cards | ✅ |
| kScreen | BuildScreenLayout | dungeon_map, title, inventory, naming | ✅ |
| kMusic | BuildMusicLayout | tracker, instrument, assembly | ✅ |
| kSprite | BuildSpriteLayout | vanilla, custom | ✅ |
| kMessage | BuildMessageLayout | list, editor, font, dictionary | ✅ |
| kAssembly | BuildAssemblyLayout | editor, output, docs | ✅ |
| kSettings | BuildSettingsLayout | navigation, content | ✅ |
| kEmulator | BuildEmulatorLayout | 7 emulator cards | ✅ |
## Testing Verification
### Compilation Tests
- ✅ Full build with no errors
- ✅ No warnings related to layout/rebuild functionality
- ✅ All dependencies resolve correctly
### Code Flow Verification
**Layout Reset Flow:**
1. User triggers Window → Reset Layout
2. `MenuOrchestrator::OnResetWorkspaceLayout()` queues deferred action
3. Next frame: `EditorManager::ResetWorkspaceLayout()` executes
4. `LayoutManager::ClearInitializationFlags()` clears all flags
5. `LayoutManager::RequestRebuild()` sets rebuild_requested_ = true
6. Immediate re-initialization for active editor
7. Next frame: Update() checks flag and calls `RebuildLayout()` as fallback
**Editor Switch Flow (Emulator Example):**
1. User presses Ctrl+Shift+E or clicks View → Emulator
2. `MenuOrchestrator::OnShowEmulator()` calls `EditorManager::ShowEmulator()`
3. `ShowEmulator()` calls `SwitchToEditor(EditorType::kEmulator)`
4. Frame validation ensures ImGui context is valid
5. `SetEmulatorVisible(true)` activates emulator
6. `SetActiveCategory("Emulator")` updates sidebar state
7. `InitializeEditorLayout(kEmulator)` builds dock layout (if not already initialized)
8. Emulator cards appear in sidebar (Update loop adds "Emulator" to active_categories)
**Rebuild Flow:**
1. Rebuild requested via `layout_manager_->RequestRebuild()`
2. Next Update() tick checks `IsRebuildRequested()`
3. Validates ImGui frame and dockspace
4. Determines current editor type
5. Calls `RebuildLayout(type, dockspace_id)`
6. RebuildLayout validates dockspace exists
7. Clears initialization flag
8. Removes and rebuilds dockspace
9. Shows appropriate cards via card_registry
10. Finalizes and marks as initialized
## Known Limitations
- Build*Layout methods could be made static (linter warning) - deferred to future cleanup
- Layout persistence (SaveCurrentLayout/LoadLayout) not yet implemented - marked TODO
- Rebuild animation/transitions not implemented - future enhancement
## Next Steps (Phase 2 - Sidebar Improvements)
As outlined in the plan roadmap:
1. Add category registration system
2. Persist sidebar collapse/tree mode state
3. Improve category switching UX
4. Add animation for sidebar expand/collapse
## Verification Commands
```bash
# Compile with layout changes
cmake --build build --target yaze
# Check for layout-related warnings
cmake --build build 2>&1 | grep -i layout
# Verify method exists in binary (macOS)
nm build/bin/Debug/yaze.app/Contents/MacOS/yaze | grep RebuildLayout
```
## Summary
✅ All Phase 1 objectives completed:
- RebuildLayout() method implemented with validation
- Rebuild flag hooked into Update() loop
- Emulator layout initialization fixed
- Emulator category appears in sidebar
- All 11 editor types verified
The layout reset system now works reliably across all editor types, with proper validation, logging, and fallback mechanisms.

Some files were not shown because too many files have changed in this diff Show More