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

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

View File

@@ -0,0 +1,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

@@ -0,0 +1,339 @@
# Configuration Matrix Documentation
This document defines all CMake configuration flags, their interactions, and the tested configuration combinations for the yaze project.
**Last Updated**: 2025-11-20
**Owner**: CLAUDE_MATRIX_TEST (Platform Matrix Testing Specialist)
## 1. CMake Configuration Flags
### Core Build Options
| Flag | Default | Purpose | Notes |
|------|---------|---------|-------|
| `YAZE_BUILD_GUI` | ON | Build GUI application (ImGui-based editor) | Required for desktop users |
| `YAZE_BUILD_CLI` | ON | Build CLI tools (shared libraries) | Needed for z3ed CLI |
| `YAZE_BUILD_Z3ED` | ON | Build z3ed CLI executable | Requires `YAZE_BUILD_CLI=ON` |
| `YAZE_BUILD_EMU` | ON | Build emulator components | Optional; adds ~50MB to binary |
| `YAZE_BUILD_LIB` | ON | Build static library (`libyaze.a`) | For library consumers |
| `YAZE_BUILD_TESTS` | ON | Build test suite | Required for CI validation |
### Feature Flags
| Flag | Default | Purpose | Dependencies |
|------|---------|---------|--------------|
| `YAZE_ENABLE_GRPC` | ON | Enable gRPC agent support | Requires protobuf, gRPC libraries |
| `YAZE_ENABLE_JSON` | ON | Enable JSON support (nlohmann) | Used by AI services |
| `YAZE_ENABLE_AI` | ON | Enable AI agent features (legacy) | **Deprecated**: use `YAZE_ENABLE_AI_RUNTIME` |
| `YAZE_ENABLE_REMOTE_AUTOMATION` | depends on `YAZE_ENABLE_GRPC` | Enable remote GUI automation (gRPC servers) | Requires `YAZE_ENABLE_GRPC=ON` |
| `YAZE_ENABLE_AI_RUNTIME` | depends on `YAZE_ENABLE_AI` | Enable AI runtime (Gemini/Ollama, advanced routing) | Requires `YAZE_ENABLE_AI=ON` |
| `YAZE_BUILD_AGENT_UI` | depends on `YAZE_BUILD_GUI` | Build ImGui agent/chat panels in GUI | Requires `YAZE_BUILD_GUI=ON` |
| `YAZE_ENABLE_AGENT_CLI` | depends on `YAZE_BUILD_CLI` | Build conversational agent CLI stack | Auto-enabled if `YAZE_BUILD_CLI=ON` or `YAZE_BUILD_Z3ED=ON` |
| `YAZE_ENABLE_HTTP_API` | depends on `YAZE_ENABLE_AGENT_CLI` | Enable HTTP REST API server | Requires `YAZE_ENABLE_AGENT_CLI=ON` |
### Optimization & Debug Flags
| Flag | Default | Purpose | Notes |
|------|---------|---------|-------|
| `YAZE_ENABLE_LTO` | OFF | Link-time optimization | Increases build time by ~30% |
| `YAZE_ENABLE_SANITIZERS` | OFF | AddressSanitizer/UBSanitizer | For memory safety debugging |
| `YAZE_ENABLE_COVERAGE` | OFF | Code coverage tracking | For testing metrics |
| `YAZE_UNITY_BUILD` | OFF | Unity (Jumbo) builds | May hide include issues |
### Development & CI Options
| Flag | Default | Purpose | Notes |
|------|---------|---------|-------|
| `YAZE_ENABLE_ROM_TESTS` | OFF | Enable ROM-dependent tests | Requires `zelda3.sfc` file |
| `YAZE_MINIMAL_BUILD` | OFF | Minimal CI build (skip optional features) | Used in resource-constrained CI |
| `YAZE_SUPPRESS_WARNINGS` | ON | Suppress compiler warnings | Use OFF for verbose builds |
## 2. Flag Interactions & Constraints
### Automatic Constraint Resolution
The CMake configuration automatically enforces these constraints:
```cmake
# REMOTE_AUTOMATION forces GRPC
if(YAZE_ENABLE_REMOTE_AUTOMATION AND NOT YAZE_ENABLE_GRPC)
set(YAZE_ENABLE_GRPC ON CACHE BOOL ... FORCE)
endif()
# Disabling REMOTE_AUTOMATION forces GRPC OFF
if(NOT YAZE_ENABLE_REMOTE_AUTOMATION)
set(YAZE_ENABLE_GRPC OFF CACHE BOOL ... FORCE)
endif()
# AI_RUNTIME forces AI enabled
if(YAZE_ENABLE_AI_RUNTIME AND NOT YAZE_ENABLE_AI)
set(YAZE_ENABLE_AI ON CACHE BOOL ... FORCE)
endif()
# Disabling AI_RUNTIME forces AI OFF
if(NOT YAZE_ENABLE_AI_RUNTIME)
set(YAZE_ENABLE_AI OFF CACHE BOOL ... FORCE)
endif()
# BUILD_CLI or BUILD_Z3ED forces AGENT_CLI ON
if((YAZE_BUILD_CLI OR YAZE_BUILD_Z3ED) AND NOT YAZE_ENABLE_AGENT_CLI)
set(YAZE_ENABLE_AGENT_CLI ON CACHE BOOL ... FORCE)
endif()
# HTTP_API forces AGENT_CLI ON
if(YAZE_ENABLE_HTTP_API AND NOT YAZE_ENABLE_AGENT_CLI)
set(YAZE_ENABLE_AGENT_CLI ON CACHE BOOL ... FORCE)
endif()
# AGENT_UI requires BUILD_GUI
if(YAZE_BUILD_AGENT_UI AND NOT YAZE_BUILD_GUI)
set(YAZE_BUILD_AGENT_UI OFF CACHE BOOL ... FORCE)
endif()
```
### Dependency Graph
```
YAZE_ENABLE_REMOTE_AUTOMATION
├─ Requires: YAZE_ENABLE_GRPC
└─ Requires: gRPC libraries, protobuf
YAZE_ENABLE_AI_RUNTIME
├─ Requires: YAZE_ENABLE_AI
├─ Requires: yaml-cpp, OpenSSL
└─ Requires: Gemini/Ollama HTTP clients
YAZE_BUILD_AGENT_UI
├─ Requires: YAZE_BUILD_GUI
└─ Requires: ImGui bindings
YAZE_ENABLE_AGENT_CLI
├─ Requires: YAZE_BUILD_CLI OR YAZE_BUILD_Z3ED
└─ Requires: ftxui, various CLI handlers
YAZE_ENABLE_HTTP_API
├─ Requires: YAZE_ENABLE_AGENT_CLI
└─ Requires: cpp-httplib
YAZE_ENABLE_JSON
├─ Requires: nlohmann_json
└─ Used by: Gemini AI service, HTTP API
```
## 3. Tested Configuration Matrix
### Rationale
Testing all 2^N combinations is infeasible (18 flags = 262,144 combinations). Instead, we test:
1. **Baseline**: All defaults (realistic user scenario)
2. **Extremes**: All ON, All OFF (catch hidden assumptions)
3. **Interactions**: Known problematic combinations
4. **CI Presets**: Predefined workflows (dev, ci, minimal, release)
5. **Platform-specific**: Windows GRPC, macOS universal binary, Linux GCC
### Matrix Definition
#### Tier 1: Core Platform Builds (CI Standard)
These run on every PR and push:
| Name | Platform | GRPC | AI | AGENT_UI | CLI | Tests | Purpose |
|------|----------|------|----|-----------|----|-------|---------|
| `ci-linux` | Linux | ON | OFF | OFF | ON | ON | Server-side agent |
| `ci-macos` | macOS | ON | OFF | ON | ON | ON | Agent UI + CLI |
| `ci-windows` | Windows | ON | OFF | OFF | ON | ON | Core Windows build |
#### Tier 2: Feature Combination Tests (Nightly or On-Demand)
These test specific flag combinations:
| Name | GRPC | REMOTE_AUTO | JSON | AI | AI_RUNTIME | AGENT_UI | HTTP_API | Tests |
|------|------|-------------|------|----|----------- |----------|----------|-------|
| `minimal` | OFF | OFF | ON | OFF | OFF | OFF | OFF | ON |
| `grpc-only` | ON | OFF | ON | OFF | OFF | OFF | OFF | ON |
| `full-ai` | ON | ON | ON | ON | ON | ON | ON | ON |
| `cli-only` | ON | ON | ON | ON | ON | OFF | ON | ON |
| `gui-only` | OFF | OFF | ON | OFF | OFF | ON | OFF | ON |
| `http-api` | ON | ON | ON | ON | ON | OFF | ON | ON |
| `no-json` | ON | ON | OFF | ON | OFF | OFF | OFF | ON |
| `all-off` | OFF | OFF | OFF | OFF | OFF | OFF | OFF | ON |
#### Tier 3: Platform-Specific Builds
| Name | Platform | Configuration | Special Notes |
|------|----------|----------------|-----------------|
| `win-ai` | Windows | Full AI + gRPC | CI Windows-specific preset |
| `win-arm` | Windows ARM64 | Debug, no AI | ARM64 architecture test |
| `mac-uni` | macOS | Universal binary | ARM64 + x86_64 |
| `lin-ai` | Linux | Full AI + gRPC | Server-side full stack |
## 4. Problematic Combinations
### Known Issue Patterns
#### Pattern A: GRPC Without REMOTE_AUTOMATION
**Status**: FIXED IN CMAKE
**Symptom**: gRPC headers included but no automation server compiled
**Why it matters**: Causes link errors if server code missing
**Resolution**: REMOTE_AUTOMATION now forces GRPC=ON via CMake constraint
#### Pattern B: HTTP_API Without AGENT_CLI
**Status**: FIXED IN CMAKE
**Symptom**: HTTP API endpoints defined but no CLI handler context
**Why it matters**: REST API has no command dispatcher
**Resolution**: HTTP_API now forces AGENT_CLI=ON via CMake constraint
#### Pattern C: AGENT_UI Without BUILD_GUI
**Status**: FIXED IN CMAKE
**Symptom**: ImGui panels compiled for headless build
**Why it matters**: Wastes space, may cause UI binding issues
**Resolution**: AGENT_UI now disabled if BUILD_GUI=OFF
#### Pattern D: AI_RUNTIME Without JSON
**Status**: TESTING
**Symptom**: Gemini service requires JSON parsing
**Why it matters**: Gemini HTTPS support needs JSON deserialization
**Resolution**: Gemini only linked when both AI_RUNTIME AND JSON enabled
#### Pattern E: Windows + GRPC + gRPC v1.67.1
**Status**: DOCUMENTED
**Symptom**: MSVC compatibility issues with older gRPC versions
**Why it matters**: gRPC <1.68.0 has MSVC ABI mismatches
**Resolution**: ci-windows preset pins to tested stable version
#### Pattern F: macOS ARM64 + Unknown Dependencies
**Status**: DOCUMENTED
**Symptom**: Homebrew brew dependencies may not have arm64 support
**Why it matters**: Cross-architecture builds fail silently
**Resolution**: mac-uni preset tests both architectures
## 5. Test Coverage by Configuration
### What Each Configuration Validates
#### Minimal Build
- Core editor functionality without AI/CLI
- Smallest binary size
- Most compatible (no gRPC, no network)
- Target users: GUI-only, offline users
#### gRPC Only
- Server-side agent without AI services
- GUI automation without language model
- Useful for: Headless automation
#### Full AI Stack
- All features enabled
- Gemini + Ollama support
- Advanced routing + proposal planning
- Target users: AI-assisted ROM hacking
#### CLI Only
- z3ed command-line tool
- No GUI components
- Server-side focused
- Target users: Scripting, CI/CD integration
#### GUI Only
- Traditional desktop editor
- No network services
- Suitable for: Casual players
#### HTTP API
- REST endpoints for external tools
- Integration with other ROM editors
- JSON-based communication
#### No JSON
- Validates JSON is truly optional
- Tests Ollama-only mode (no Gemini)
- Smaller binary alternative
#### All Off
- Validates minimum viable configuration
- Basic ROM reading/writing only
- Edge case handling
## 6. Running Configuration Matrix Tests
### Local Testing
```bash
# Run entire local matrix
./scripts/test-config-matrix.sh
# Run specific configuration
./scripts/test-config-matrix.sh --config minimal
./scripts/test-config-matrix.sh --config full-ai
# Smoke test only (no full build)
./scripts/test-config-matrix.sh --smoke
# Verbose output
./scripts/test-config-matrix.sh --verbose
```
### CI Testing
Matrix tests run nightly via `.github/workflows/matrix-test.yml`:
```yaml
# Automatic testing of all Tier 2 combinations on all platforms
# Run time: ~45 minutes (parallel execution)
# Triggered: On schedule (2 AM UTC daily) or manual dispatch
```
### Building Specific Preset
```bash
# Linux
cmake --preset ci-linux -B build_ci -DYAZE_ENABLE_GRPC=ON
cmake --build build_ci
# Windows
cmake --preset ci-windows -B build_ci
cmake --build build_ci --config RelWithDebInfo
# macOS Universal
cmake --preset mac-uni -B build_uni -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
cmake --build build_uni
```
## 7. Configuration Dependencies Reference
### For Pull Requests
Use this checklist when modifying CMake configuration:
- [ ] Added new `option()`? Document in Section 1 above
- [ ] New dependency? Document in Section 2 (Dependency Graph)
- [ ] New feature flag? Add to relevant Tier in Section 3
- [ ] Problematic combination? Document in Section 4
- [ ] Update test matrix script if testing approach changes
### For Developers
Quick reference when debugging build issues:
1. **gRPC link errors?** Check: `YAZE_ENABLE_GRPC=ON` requires `YAZE_ENABLE_REMOTE_AUTOMATION=ON` (auto-enforced)
2. **Gemini compile errors?** Verify: `YAZE_ENABLE_AI_RUNTIME=ON AND YAZE_ENABLE_JSON=ON`
3. **Agent UI missing?** Check: `YAZE_BUILD_GUI=ON AND YAZE_BUILD_AGENT_UI=ON`
4. **CLI commands not found?** Verify: `YAZE_ENABLE_AGENT_CLI=ON` (auto-forced by `YAZE_BUILD_CLI=ON`)
5. **HTTP API endpoints undefined?** Check: `YAZE_ENABLE_HTTP_API=ON` forces `YAZE_ENABLE_AGENT_CLI=ON`
## 8. Future Improvements
Potential enhancements as project evolves:
- [ ] Separate AI_RUNTIME from ENABLE_AI (currently coupled)
- [ ] Add YAZE_ENABLE_GRPC_STRICT flag for stricter server-side validation
- [ ] Document platform-specific library version constraints
- [ ] Add automated configuration lint tool
- [ ] Track binary size impact per feature flag combination
- [ ] Add performance benchmarks for each Tier 2 configuration

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)

File diff suppressed because it is too large Load Diff

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
```