backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
218
docs/internal/architecture/README.md
Normal file
218
docs/internal/architecture/README.md
Normal 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
|
||||
90
docs/internal/architecture/collaboration_framework.md
Normal file
90
docs/internal/architecture/collaboration_framework.md
Normal 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.
|
||||
339
docs/internal/architecture/configuration_matrix.md
Normal file
339
docs/internal/architecture/configuration_matrix.md
Normal 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
|
||||
@@ -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**
|
||||
|
||||
103
docs/internal/architecture/dungeon_tile_ordering.md
Normal file
103
docs/internal/architecture/dungeon_tile_ordering.md
Normal 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`
|
||||
532
docs/internal/architecture/editor_card_layout_system.md
Normal file
532
docs/internal/architecture/editor_card_layout_system.md
Normal 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)
|
||||
1432
docs/internal/architecture/editor_manager.md
Normal file
1432
docs/internal/architecture/editor_manager.md
Normal file
File diff suppressed because it is too large
Load Diff
177
docs/internal/architecture/graphics_system_architecture.md
Normal file
177
docs/internal/architecture/graphics_system_architecture.md
Normal 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
|
||||
33
docs/internal/architecture/layout-designer.md
Normal file
33
docs/internal/architecture/layout-designer.md
Normal 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.
|
||||
281
docs/internal/architecture/object-selection-integration.md
Normal file
281
docs/internal/architecture/object-selection-integration.md
Normal 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)
|
||||
405
docs/internal/architecture/object_selection_flow.md
Normal file
405
docs/internal/architecture/object_selection_flow.md
Normal 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
|
||||
450
docs/internal/architecture/object_selection_system.md
Normal file
450
docs/internal/architecture/object_selection_system.md
Normal 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
|
||||
63
docs/internal/architecture/overworld_editor_system.md
Normal file
63
docs/internal/architecture/overworld_editor_system.md
Normal 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.
|
||||
54
docs/internal/architecture/overworld_map_data.md
Normal file
54
docs/internal/architecture/overworld_map_data.md
Normal 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.
|
||||
278
docs/internal/architecture/rom_architecture.md
Normal file
278
docs/internal/architecture/rom_architecture.md
Normal 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 = ¤t_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
|
||||
72
docs/internal/architecture/room_data_persistence.md
Normal file
72
docs/internal/architecture/room_data_persistence.md
Normal 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.
|
||||
@@ -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
|
||||
49
docs/internal/architecture/ui-design-guidelines.md
Normal file
49
docs/internal/architecture/ui-design-guidelines.md
Normal 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"`).
|
||||
176
docs/internal/architecture/ui_layout_system.md
Normal file
176
docs/internal/architecture/ui_layout_system.md
Normal 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)
|
||||
58
docs/internal/architecture/undo_redo_system.md
Normal file
58
docs/internal/architecture/undo_redo_system.md
Normal 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`.
|
||||
133
docs/internal/architecture/zscustomoverworld_integration.md
Normal file
133
docs/internal/architecture/zscustomoverworld_integration.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user