- Added detailed CI/CD reliability improvements, including retry mechanisms and enhanced error reporting for Windows vcpkg setup and dependency installation. - Updated Dungeon Editor status to EXPERIMENTAL, highlighting the need for thorough testing and outlining implemented features. - Introduced a comprehensive A Link to the Past ROM reference, detailing graphics systems, palette management, and memory mapping. - Improved changelog to reflect recent fixes and ongoing development status across various editors.
898 lines
25 KiB
Markdown
898 lines
25 KiB
Markdown
# Dungeon Editor Complete Guide
|
||
|
||
**Last Updated**: October 9, 2025
|
||
**Status**: EXPERIMENTAL - Core features implemented but requires thorough testing
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
- [Overview](#overview)
|
||
- [Architecture](#architecture)
|
||
- [Implemented Features](#implemented-features)
|
||
- [Technical Details](#technical-details)
|
||
- [Usage Guide](#usage-guide)
|
||
- [Troubleshooting](#troubleshooting)
|
||
- [Next Steps](#next-steps)
|
||
- [Reference](#reference)
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
The Dungeon Editor uses a modular card-based architecture for editing dungeon rooms in The Legend of Zelda: A Link to the Past.
|
||
|
||
**WARNING**: This editor is currently experimental. While core features are implemented, thorough testing is still required before production use.
|
||
|
||
### Key Capabilities
|
||
- Visual room editing with 512x512 canvas
|
||
- Object placement with pattern-based rendering
|
||
- Live palette editing with instant preview
|
||
- Independent dockable UI cards
|
||
- Multi-room editing support
|
||
- Automatic graphics loading
|
||
- Error recovery system
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Component Hierarchy
|
||
```
|
||
DungeonEditorV2 (Coordinator)
|
||
│
|
||
├── Toolbar (Toolset)
|
||
│ ├── Open Room
|
||
│ ├── Toggle Rooms List
|
||
│ ├── Toggle Room Matrix
|
||
│ ├── Toggle Entrances List
|
||
│ ├── Toggle Room Graphics
|
||
│ ├── Toggle Object Editor
|
||
│ └── Toggle Palette Editor
|
||
│
|
||
├── Independent Cards (all dockable)
|
||
│ ├── Rooms List Card
|
||
│ ├── Entrances List Card
|
||
│ ├── Room Matrix Card (16x19 grid)
|
||
│ ├── Room Graphics Card
|
||
│ ├── Object Editor Card
|
||
│ ├── Palette Editor Card
|
||
│ └── Room Cards (dynamic, auto-dock together)
|
||
│
|
||
└── Per-Room Rendering
|
||
└── Room
|
||
├── bg1_buffer_ (BackgroundBuffer)
|
||
├── bg2_buffer_ (BackgroundBuffer)
|
||
└── DungeonCanvasViewer
|
||
```
|
||
|
||
### Independent Card Architecture
|
||
|
||
**Key Principle**: Each card is a top-level ImGui window with NO table layout or window hierarchy inheritance.
|
||
|
||
```cpp
|
||
// Each card is completely independent
|
||
void DungeonEditorV2::DrawLayout() {
|
||
// Room Selector (persistent)
|
||
{
|
||
static bool show = true;
|
||
gui::EditorCard card("Room Selector", ICON_MD_LIST, &show);
|
||
if (card.Begin()) {
|
||
room_selector_.Draw();
|
||
}
|
||
card.End();
|
||
}
|
||
|
||
// Room Cards (closable, auto-dock)
|
||
for (int room_id : active_rooms_) {
|
||
bool open = true;
|
||
gui::EditorCard card(MakeCardTitle(room_id), ICON_MD_GRID_ON, &open);
|
||
if (card.Begin()) {
|
||
DrawRoomTab(room_id);
|
||
}
|
||
card.End();
|
||
|
||
if (!open) RemoveRoom(room_id);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Benefits**:
|
||
- ✅ Full freedom to drag, dock, resize
|
||
- ✅ No layout constraints or inheritance
|
||
- ✅ Can be arranged however user wants
|
||
- ✅ Session-aware card titles
|
||
- ✅ ImGui handles all docking logic
|
||
|
||
---
|
||
|
||
## Implemented Features
|
||
|
||
### 1. Rooms List Card
|
||
```cpp
|
||
Features:
|
||
- Filter/search functionality
|
||
- Format: [HEX_ID] Room Name
|
||
- Click to open room card
|
||
- Double-click for instant focus
|
||
- Shows all 296 rooms (0x00-0x127)
|
||
```
|
||
|
||
### 2. Entrances List Card (ZScream Parity)
|
||
```cpp
|
||
Configuration UI:
|
||
- Entrance ID, Room ID, Dungeon ID
|
||
- Blockset, Music, Floor
|
||
- Player Position (X, Y)
|
||
- Camera Trigger (X, Y)
|
||
- Scroll Position (X, Y)
|
||
- Exit value
|
||
- Camera Boundaries (quadrant & full room)
|
||
|
||
List Features:
|
||
- Format: [HEX_ID] Entrance Name -> Room Name
|
||
- Shows entrance-to-room relationship
|
||
- Click to select and open associated room
|
||
```
|
||
|
||
### 3. Room Matrix Card (16x19 Grid)
|
||
```cpp
|
||
Layout:
|
||
- 16 columns × 19 rows = 304 cells
|
||
- Displays all 296 rooms (0x00-0x127)
|
||
- 24px cells with 1px spacing (optimized)
|
||
- Window size: 440x520
|
||
|
||
Visual Features:
|
||
- Instant loading with deterministic HSV colors
|
||
- Color calculated from room ID (no palette loading)
|
||
- Light green outline: Currently selected room
|
||
- Green outline: Open rooms
|
||
- Gray outline: Inactive rooms
|
||
|
||
Interaction:
|
||
- Click to open room card
|
||
- Hover for tooltip (room name)
|
||
- Auto-focuses existing cards
|
||
```
|
||
|
||
**Performance**:
|
||
- Before: 2-4 seconds (lazy loading 296 rooms)
|
||
- After: < 50ms (pure math, no I/O)
|
||
|
||
### 4. Room Graphics Card
|
||
```cpp
|
||
Features:
|
||
- Shows blockset graphics for selected room
|
||
- 2-column grid layout
|
||
- Auto-loads when room changes
|
||
- Up to 16 graphics blocks
|
||
- Toggleable via toolbar
|
||
```
|
||
|
||
### 5. Object Editor Card (Unified)
|
||
```cpp
|
||
Improved UX:
|
||
- Mode controls at top: None | Place | Select | Delete
|
||
- Current object info always visible
|
||
- 2 tabs:
|
||
- Browser: Object selection with previews
|
||
- Preview: Emulator rendering with controls
|
||
|
||
Object Browser:
|
||
- Categorized objects (Floor/Wall/Special)
|
||
- 32x32 preview icons
|
||
- Filter/search functionality
|
||
- Shows object ID and type
|
||
```
|
||
|
||
### 6. Palette Editor Card
|
||
```cpp
|
||
Features:
|
||
- Palette selector dropdown (20 dungeon palettes)
|
||
- 90-color grid (15 per row)
|
||
- Visual selection with yellow border
|
||
- Tooltips: color index, SNES BGR555, RGB values
|
||
- HSV color wheel picker
|
||
- Live RGB display (0-255)
|
||
- SNES format display (15-bit BGR555)
|
||
- Reset button
|
||
|
||
Live Updates:
|
||
- Edit palette → all open rooms re-render automatically
|
||
- Callback system decouples palette editor from rooms
|
||
```
|
||
|
||
### 7. Room Cards (Auto-Loading)
|
||
```cpp
|
||
Improvements:
|
||
- Auto-loads graphics when properties change
|
||
- Simple status indicator (✓ Loaded / ⏳ Not Loaded)
|
||
- Auto-saves with main Save command
|
||
- Removed manual "Load Graphics" buttons
|
||
|
||
Docking Behavior:
|
||
- ImGuiWindowClass for automatic tab grouping
|
||
- New room cards auto-dock with existing rooms
|
||
- Can be undocked independently
|
||
- Maintains session state
|
||
```
|
||
|
||
### 8. Object Drawing System
|
||
```cpp
|
||
ObjectDrawer (Native C++ Rendering):
|
||
- Pattern-based tile placement
|
||
- Fast, no emulation overhead
|
||
- Centralized pattern logic
|
||
|
||
Supported Patterns:
|
||
- ✅ 1x1 Solid (0x34)
|
||
- ✅ Rightward 2x2 (0x00-0x08) - horizontal walls
|
||
- ✅ Downward 2x2 (0x60-0x68) - vertical walls
|
||
- ✅ Diagonal Acute (0x09-0x14) - / walls
|
||
- ✅ Diagonal Grave (0x15-0x20) - \ walls
|
||
- ✅ 4x4 Blocks (0x33, 0x70-0x71) - large structures
|
||
|
||
Integration:
|
||
// Simplified from 100+ lines to 3 lines
|
||
ObjectDrawer drawer(rom_);
|
||
drawer.DrawObjectList(tile_objects_, bg1_buffer_, bg2_buffer_);
|
||
```
|
||
|
||
### 9. Cross-Editor Navigation
|
||
```cpp
|
||
From Overworld Editor:
|
||
editor_manager->JumpToDungeonRoom(room_id);
|
||
|
||
From Dungeon Editor:
|
||
- Click in Rooms List → opens/focuses room card
|
||
- Click in Entrances List → opens associated room
|
||
- Click in Room Matrix → opens/focuses room card
|
||
|
||
EditorCard Focus System:
|
||
- Focus() method brings window to front
|
||
- Works with docked and floating windows
|
||
- Avoids duplicate cards
|
||
```
|
||
|
||
### 10. Error Handling & Recovery
|
||
```cpp
|
||
Custom ImGui Assertion Handler:
|
||
- Catches UI assertion failures
|
||
- Logs errors instead of crashing
|
||
- After 5 errors:
|
||
1. Backs up imgui.ini → imgui.ini.backup
|
||
2. Deletes imgui.ini (reset workspace)
|
||
3. Resets error counter
|
||
4. Application continues running
|
||
|
||
Benefits:
|
||
- No data loss from UI bugs
|
||
- Automatic recovery
|
||
- User-friendly error handling
|
||
```
|
||
|
||
---
|
||
|
||
## Technical Details
|
||
|
||
### Rendering Pipeline
|
||
|
||
```
|
||
1. Room::CopyRoomGraphicsToBuffer()
|
||
→ Loads tile graphics into current_gfx16_ [128×N indexed pixels]
|
||
|
||
2. BackgroundBuffer::DrawFloor()
|
||
→ Fills tilemap buffer with floor tile IDs
|
||
|
||
3. ObjectDrawer::DrawObjectList()
|
||
→ Writes wall/object tiles to BG1/BG2 buffers
|
||
|
||
4. BackgroundBuffer::DrawBackground()
|
||
→ For each tile in tilemap:
|
||
- Extract 8×8 pixels from gfx16_data
|
||
- Apply palette offset (palette_id * 8 for 3BPP)
|
||
- Copy to bitmap (512×512 indexed surface)
|
||
→ Sync: memcpy(surface->pixels, bitmap_data)
|
||
|
||
5. Bitmap::SetPalette()
|
||
→ Apply 90-color dungeon palette to SDL surface
|
||
|
||
6. Renderer::RenderBitmap()
|
||
→ Convert indexed surface → RGB texture
|
||
→ SDL_CreateTextureFromSurface() applies palette
|
||
|
||
7. DungeonCanvasViewer::RenderRoomBackgroundLayers()
|
||
→ Draw texture to canvas with ImGui::Image()
|
||
```
|
||
|
||
### SNES Graphics Format
|
||
|
||
**8-bit Indexed Color (3BPP for dungeons)**:
|
||
```cpp
|
||
// Each pixel is a palette index (0-7)
|
||
// RGB color comes from applying dungeon palette
|
||
bg1_bmp.SetPalette(dungeon_pal_group[palette_id]); // 90 colors
|
||
Renderer::Get().RenderBitmap(&bitmap); // indexed → RGB
|
||
```
|
||
|
||
**Color Format: 15-bit BGR555**
|
||
```
|
||
Bits: 0BBB BBGG GGGR RRRR
|
||
││││ ││││ ││││ ││││
|
||
│└──┴─┘└──┴─┘└──┴─┘
|
||
│ Blue Green Red
|
||
└─ Unused (always 0)
|
||
|
||
Each channel: 0-31 (5 bits)
|
||
Total colors: 32,768 (2^15)
|
||
```
|
||
|
||
**Palette Organization**:
|
||
- 20 total palettes (one per dungeon color scheme)
|
||
- 90 colors per palette (full SNES BG palette)
|
||
- ROM address: `kDungeonMainPalettes` (0xDD734)
|
||
|
||
### Critical Math Formulas
|
||
|
||
**Tile Position in Tilesheet (128px wide)**:
|
||
```cpp
|
||
int tile_x = (tile_id % 16) * 8;
|
||
int tile_y = (tile_id / 16) * 8;
|
||
int pixel_offset = (tile_y * 128) + tile_x;
|
||
```
|
||
|
||
**Tile Position in Canvas (512×512)**:
|
||
```cpp
|
||
int canvas_x = (tile_col * 8);
|
||
int canvas_y = (tile_row * 8);
|
||
int pixel_offset = (canvas_y * 512) + canvas_x;
|
||
|
||
// CRITICAL: For NxN tiles, advance by (tile_row * 8 * width)
|
||
int dest_offset = (yy * 8 * 512) + (xx * 8); // NOT just (yy * 512)!
|
||
```
|
||
|
||
**Palette Index Calculation**:
|
||
```cpp
|
||
// 3BPP: 8 colors per subpalette
|
||
int final_index = pixel_value + (palette_id * 8);
|
||
|
||
// NOT 4BPP (× 16)!
|
||
// int final_index = pixel_value + (palette_id << 4); // WRONG
|
||
```
|
||
|
||
### Per-Room Buffers (Critical for Multi-Room Editing)
|
||
|
||
**Old way (broken)**: Multiple rooms shared `gfx::Arena::Get().bg1()` and corrupted each other.
|
||
|
||
**New way (fixed)**: Each `Room` has its own buffers:
|
||
```cpp
|
||
// In room.h
|
||
gfx::BackgroundBuffer bg1_buffer_;
|
||
gfx::BackgroundBuffer bg2_buffer_;
|
||
|
||
// In room.cc
|
||
bg1_buffer_.DrawFloor(...);
|
||
bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
|
||
Renderer::Get().RenderBitmap(&bg1_buffer_.bitmap());
|
||
```
|
||
|
||
### Color Format Conversions
|
||
|
||
```cpp
|
||
// ImGui → SNES BGR555
|
||
int r_snes = (int)(imgui_r * 31.0f); // 0-1 → 0-31
|
||
int g_snes = (int)(imgui_g * 31.0f);
|
||
int b_snes = (int)(imgui_b * 31.0f);
|
||
uint16_t bgr555 = (b_snes << 10) | (g_snes << 5) | r_snes;
|
||
|
||
// SNES BGR555 → RGB (for display)
|
||
uint8_t r_rgb = (snes & 0x1F) * 255 / 31; // 0-31 → 0-255
|
||
uint8_t g_rgb = ((snes >> 5) & 0x1F) * 255 / 31;
|
||
uint8_t b_rgb = ((snes >> 10) & 0x1F) * 255 / 31;
|
||
```
|
||
|
||
---
|
||
|
||
## Usage Guide
|
||
|
||
### Opening Rooms
|
||
|
||
1. **Rooms List**: Search and click room
|
||
2. **Entrances List**: Click entrance to open associated room
|
||
3. **Room Matrix**: Visual navigation with color-coded grid
|
||
4. **Toolbar**: Use "Open Room" button
|
||
|
||
### Editing Objects
|
||
|
||
1. Toggle **Object Editor** card
|
||
2. Select mode: **Place** / **Select** / **Delete**
|
||
3. Browse objects in **Browser** tab
|
||
4. Click object to select
|
||
5. Click on canvas to place
|
||
6. Use **Select** mode for multi-select (Ctrl+drag)
|
||
|
||
### Editing Palettes
|
||
|
||
1. Toggle **Palette Editor** card
|
||
2. Select palette from dropdown (0-19)
|
||
3. Click color in 90-color grid
|
||
4. Adjust with HSV color wheel
|
||
5. See live updates in all open room cards
|
||
6. Reset color if needed
|
||
|
||
### Configuring Entrances
|
||
|
||
1. Toggle **Entrances List** card
|
||
2. Select entrance from list
|
||
3. Edit properties in configuration UI
|
||
4. All changes auto-save
|
||
5. Click entrance to jump to associated room
|
||
|
||
### Managing Layout
|
||
|
||
- All cards are dockable
|
||
- Room cards automatically tab together
|
||
- Save layout via imgui.ini
|
||
- If errors occur, layout auto-resets with backup
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Common Bugs & Fixes
|
||
|
||
#### Empty Palette (0 colors)
|
||
**Symptom**: Graphics render as solid color or invisible
|
||
**Cause**: Using `palette()` method (copy) instead of `operator[]` (reference)
|
||
```cpp
|
||
// WRONG:
|
||
auto pal = group.palette(id); // Copy, may be empty
|
||
|
||
// CORRECT:
|
||
auto pal = group[id]; // Reference
|
||
```
|
||
|
||
#### Bitmap Stretched/Corrupted
|
||
**Symptom**: Graphics only in top portion, repeated/stretched
|
||
**Cause**: Wrong offset in DrawBackground()
|
||
```cpp
|
||
// WRONG:
|
||
int offset = (yy * 512) + (xx * 8); // Only advances 512 per row
|
||
|
||
// CORRECT:
|
||
int offset = (yy * 8 * 512) + (xx * 8); // Advances 4096 per row
|
||
```
|
||
|
||
#### Black Canvas Despite Correct Data
|
||
**Symptom**: current_gfx16_ has data, palette loaded, but canvas black
|
||
**Cause**: Bitmap not synced to SDL surface
|
||
```cpp
|
||
// FIX: After DrawTile() loop
|
||
SDL_LockSurface(surface);
|
||
memcpy(surface->pixels, bitmap_data.data(), bitmap_data.size());
|
||
SDL_UnlockSurface(surface);
|
||
```
|
||
|
||
#### Wrong Colors
|
||
**Symptom**: Colors don't match expected palette
|
||
**Cause**: Using 4BPP offset for 3BPP graphics
|
||
```cpp
|
||
// WRONG (4BPP):
|
||
int offset = palette_id << 4; // × 16
|
||
|
||
// CORRECT (3BPP):
|
||
int offset = palette_id * 8; // × 8
|
||
```
|
||
|
||
#### Wrong Bitmap Depth
|
||
**Symptom**: Room graphics corrupted, only showing in small portion
|
||
**Cause**: Depth parameter wrong in CreateAndRenderBitmap
|
||
```cpp
|
||
// WRONG:
|
||
CreateAndRenderBitmap(0x200, 0x200, 0x200, data, bitmap, palette);
|
||
// ^^^^^ depth should be 8!
|
||
|
||
// CORRECT:
|
||
CreateAndRenderBitmap(0x200, 0x200, 8, data, bitmap, palette);
|
||
// ^ 8-bit indexed
|
||
```
|
||
|
||
#### Emulator Preview "ROM Not Loaded"
|
||
**Symptom**: Preview shows error despite ROM being loaded
|
||
**Cause**: Emulator initialized before ROM loaded
|
||
```cpp
|
||
// WRONG (in Initialize()):
|
||
object_emulator_preview_.Initialize(rom_); // Too early!
|
||
|
||
// CORRECT (in Load()):
|
||
if (!rom_ || !rom_->is_loaded()) return error;
|
||
object_emulator_preview_.Initialize(rom_); // After ROM confirmed
|
||
```
|
||
|
||
### Debugging Tips
|
||
|
||
If objects don't appear:
|
||
|
||
1. **Check console output**:
|
||
```
|
||
[ObjectDrawer] Drawing object $34 at (16,16)
|
||
[DungeonCanvas] Rendered BG1/BG2 to canvas
|
||
```
|
||
|
||
2. **Verify tiles loaded**:
|
||
- Object must have tiles (`EnsureTilesLoaded()`)
|
||
- Check `object.tiles().empty()`
|
||
|
||
3. **Check buffer writes**:
|
||
- Add logging in `WriteTile16()`
|
||
- Verify `IsValidTilePosition()` isn't rejecting writes
|
||
|
||
4. **Verify buffer rendering**:
|
||
- Check `RenderRoomBackgroundLayers()` renders BG1/BG2
|
||
- May need `bg1.DrawBackground(gfx16_data)` after writing
|
||
|
||
### Floor & Wall Rendering Debug Guide
|
||
|
||
**What Should Happen**:
|
||
1. `DrawFloor()` writes 4096 floor tile IDs to the buffer
|
||
2. `DrawBackground()` renders those tiles + any objects to the bitmap
|
||
3. Bitmap gets palette applied
|
||
4. SDL texture created from bitmap
|
||
|
||
**Debug Output to Check**:
|
||
|
||
When you open a room and load graphics, check console for:
|
||
|
||
```
|
||
[BG:DrawFloor] tile_address=0xXXXX, tile_address_floor=0xXXXX, floor_graphics=0xXX, f=0xXX
|
||
[BG:DrawFloor] Floor tile words: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
|
||
[BG:DrawFloor] Wrote 4096 floor tiles to buffer
|
||
[BG:DrawBackground] Using existing bitmap (preserving floor)
|
||
[BG:DrawBackground] gfx16_data size=XXXXX, first 32 bytes: XX XX XX XX...
|
||
```
|
||
|
||
**Common Floor/Wall Issues**:
|
||
|
||
#### Issue 1: Floor tiles are all 0x0000
|
||
**Symptom**: `Floor tile words: 0000 0000 0000 0000 0000 0000 0000 0000`
|
||
**Cause**: `tile_address` or `tile_address_floor` is wrong, or `floor_graphics` is wrong
|
||
**Fix**: Check that room data loaded correctly
|
||
|
||
#### Issue 2: gfx16_data is all 0x00
|
||
**Symptom**: `gfx16_data size=16384, first 32 bytes: 00 00 00 00 00 00 00 00...`
|
||
**Cause**: `CopyRoomGraphicsToBuffer()` failed to load graphics
|
||
**Fix**: Check `current_gfx16_` is populated in Room
|
||
|
||
#### Issue 3: "Creating new bitmap" instead of "Using existing bitmap"
|
||
**Symptom**: DrawBackground creates a new zero-filled bitmap
|
||
**Cause**: Bitmap wasn't active when DrawBackground was called
|
||
**Fix**: DrawBackground now checks `bitmap_.is_active()` before recreating
|
||
|
||
#### Issue 4: Buffer has tiles but bitmap is empty
|
||
**Symptom**: DrawFloor reports writing tiles, but canvas shows nothing
|
||
**Cause**: DrawBackground isn't actually rendering the buffer's tiles
|
||
**Fix**: Check that `buffer_[xx + yy * tiles_w]` has non-zero values
|
||
|
||
**Rendering Flow Diagram**:
|
||
|
||
```
|
||
Room::RenderRoomGraphics()
|
||
│
|
||
├─1. CopyRoomGraphicsToBuffer()
|
||
│ └─> Fills current_gfx16_[16384] with tile pixel data (3BPP)
|
||
│
|
||
├─2. bg1_buffer_.DrawFloor()
|
||
│ └─> Writes floor tile IDs to buffer_[4096]
|
||
│ └─> Example: buffer_[0] = 0x00EE (tile 238, palette 0)
|
||
│
|
||
├─3. RenderObjectsToBackground()
|
||
│ └─> ObjectDrawer writes wall/object tile IDs to buffer_[]
|
||
│ └─> Example: buffer_[100] = 0x0060 (wall tile, palette 0)
|
||
│
|
||
├─4. bg1_buffer_.DrawBackground(current_gfx16_)
|
||
│ └─> For each tile ID in buffer_[]:
|
||
│ ├─> Extract tile_id, palette from word
|
||
│ ├─> Read 8x8 pixels from current_gfx16_[128-pixel-wide sheet]
|
||
│ ├─> Apply palette offset (palette * 8 for 3BPP)
|
||
│ └─> Write to bitmap_.data()[512x512]
|
||
│
|
||
├─5. bitmap_.SetPalette(dungeon_palette[90 colors])
|
||
│ └─> Applies SNES BGR555 colors to SDL surface palette
|
||
│
|
||
└─6. Renderer::RenderBitmap(&bitmap_)
|
||
└─> Creates SDL_Texture from indexed surface + palette
|
||
└─> Result: RGB texture ready to display
|
||
```
|
||
|
||
**Expected Console Output (Working)**:
|
||
|
||
```
|
||
[BG:DrawFloor] tile_address=0x4D62, tile_address_floor=0x4D6A, floor_graphics=0x00, f=0x00
|
||
[BG:DrawFloor] Floor tile words: 00EE 00EF 01EE 01EF 02EE 02EF 03EE 03EF
|
||
[BG:DrawFloor] Wrote 4096 floor tiles to buffer
|
||
[ObjectDrawer] Drew 73 objects, skipped 0
|
||
[BG:DrawBackground] Using existing bitmap (preserving floor)
|
||
[BG:DrawBackground] gfx16_data size=16384, first 32 bytes: 3A 3A 3A 4C 4C 3A 3A 55 55 3A 3A 55 55 3A 3A...
|
||
```
|
||
|
||
✅ Floor tiles written ✅ Objects drawn ✅ Graphics data present ✅ Bitmap preserved
|
||
|
||
**Quick Diagnostic Tests**:
|
||
|
||
1. **Run App & Check Console**:
|
||
- Open Dungeon Editor
|
||
- Select a room (try room $00, $02, or $08)
|
||
- Load graphics
|
||
- Check console output
|
||
|
||
2. **Good Signs**:
|
||
- `[BG:DrawFloor] Wrote 4096 floor tiles` ← Floor data written
|
||
- `Floor tile words: 00EE 00EF...` ← Non-zero tile IDs
|
||
- `gfx16_data size=16384, first 32 bytes: 3A 3A...` ← Graphics data present
|
||
- `[ObjectDrawer] Drew XX objects` ← Objects rendered
|
||
|
||
3. **Bad Signs**:
|
||
- `Floor tile words: 0000 0000 0000 0000...` ← No floor tiles!
|
||
- `gfx16_data size=16384, first 32 bytes: 00 00 00...` ← No graphics!
|
||
- `[BG:DrawBackground] Creating new bitmap` ← Wiping out floor!
|
||
|
||
**If Console Shows Everything Working But Canvas Still Empty**:
|
||
|
||
The issue is in **canvas rendering**, not floor/wall drawing. Check:
|
||
1. Is texture being created? (Check `Renderer::RenderBitmap`)
|
||
2. Is canvas displaying texture? (Check `DungeonCanvasViewer::DrawDungeonCanvas`)
|
||
3. Is texture pointer valid? (Check `bitmap.texture() != nullptr`)
|
||
|
||
**Quick Visual Test**:
|
||
|
||
If you see **pink/brown rectangles** but no floor/walls:
|
||
- ✅ Canvas IS rendering primitives
|
||
- ❌ Canvas is NOT rendering the bitmap texture
|
||
|
||
This suggests the bitmap texture is either:
|
||
1. Not being created
|
||
2. Being created but not displayed
|
||
3. Being created with wrong data
|
||
|
||
---
|
||
|
||
## Next Steps
|
||
|
||
### Remaining Issues
|
||
|
||
#### Issue 1: Room Layout Not Rendering (COMPLETED ✅)
|
||
- **Solution**: ObjectDrawer integration complete
|
||
- Walls and floors now render properly
|
||
|
||
#### Issue 2: Entity Interaction
|
||
**Problem**: Can't click/drag dungeon entities like overworld
|
||
**Reference**: `overworld_entity_renderer.cc` lines 23-91
|
||
|
||
**Implementation Needed**:
|
||
```cpp
|
||
// 1. Detect hover
|
||
bool IsMouseHoveringOverEntity(const Entity& entity, canvas_p0, scrolling);
|
||
|
||
// 2. Handle dragging
|
||
void HandleEntityDragging(Entity* entity, ...);
|
||
|
||
// 3. Double-click to open
|
||
if (IsMouseHoveringOverEntity(entity) && IsMouseDoubleClicked()) {
|
||
// Open entity editor
|
||
}
|
||
|
||
// 4. Right-click for context menu
|
||
if (IsMouseHoveringOverEntity(entity) && IsMouseClicked(Right)) {
|
||
ImGui::OpenPopup("Entity Editor");
|
||
}
|
||
```
|
||
|
||
**Files to Create/Update**:
|
||
- `dungeon_entity_interaction.h/cc` (new)
|
||
- `dungeon_canvas_viewer.cc` (integrate)
|
||
|
||
#### Issue 3: Multi-Select for Objects
|
||
**Problem**: No group selection/movement
|
||
**Status**: Partially implemented in `DungeonObjectInteraction`
|
||
|
||
**What's Missing**:
|
||
1. Multi-object drag support
|
||
2. Group movement logic
|
||
3. Delete multiple objects
|
||
4. Copy/paste groups
|
||
|
||
**Implementation**:
|
||
```cpp
|
||
void MoveSelectedObjects(ImVec2 delta) {
|
||
for (size_t idx : selected_object_indices_) {
|
||
auto& obj = room.GetTileObjects()[idx];
|
||
obj.x_ += delta.x;
|
||
obj.y_ += delta.y;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Issue 4: Context Menu Not Dungeon-Aware
|
||
**Problem**: Generic canvas context menu
|
||
**Solution**: Use `Canvas::AddContextMenuItem()`:
|
||
|
||
```cpp
|
||
// Setup before DrawContextMenu()
|
||
canvas_.ClearContextMenuItems();
|
||
|
||
if (!selected_objects.empty()) {
|
||
canvas_.AddContextMenuItem({
|
||
ICON_MD_DELETE " Delete Selected",
|
||
[this]() { DeleteSelectedObjects(); },
|
||
"Del"
|
||
});
|
||
}
|
||
|
||
canvas_.AddContextMenuItem({
|
||
ICON_MD_ADD " Place Object",
|
||
[this]() { ShowObjectPlacementMenu(); }
|
||
});
|
||
```
|
||
|
||
### Future Enhancements
|
||
|
||
1. **Object Preview Thumbnails**
|
||
- Replace "?" placeholders with rendered thumbnails
|
||
- Use ObjectRenderer for 64×64 bitmaps
|
||
- Cache for performance
|
||
|
||
2. **Room Matrix Bitmap Preview**
|
||
- Hover shows actual room bitmap
|
||
- Small popup with preview
|
||
- Rendered on-demand
|
||
|
||
3. **Palette Presets**
|
||
- Save/load favorite combinations
|
||
- Import/export between dungeons
|
||
- Undo/redo for palette changes
|
||
|
||
4. **Custom Object Editor**
|
||
- Let users create new patterns
|
||
- Visual pattern editor
|
||
- Save to ROM
|
||
|
||
5. **Complete Object Coverage**
|
||
- All 256+ object types
|
||
- Special objects (stairs, chests, doors)
|
||
- Layer 3 effects
|
||
|
||
---
|
||
|
||
## Reference
|
||
|
||
### File Organization
|
||
|
||
**Core Rendering**:
|
||
- `src/app/zelda3/dungeon/room.{h,cc}` - Room state, buffers
|
||
- `src/app/gfx/background_buffer.{h,cc}` - Tile → bitmap drawing
|
||
- `src/app/core/renderer.cc` - Bitmap → texture conversion
|
||
- `src/app/editor/dungeon/dungeon_canvas_viewer.cc` - Canvas display
|
||
|
||
**Object Drawing**:
|
||
- `src/app/zelda3/dungeon/object_drawer.{h,cc}` - Native C++ patterns
|
||
- `src/app/gui/widgets/dungeon_object_emulator_preview.{h,cc}` - Research tool
|
||
|
||
**Editor UI**:
|
||
- `src/app/editor/dungeon/dungeon_editor_v2.{h,cc}` - Main coordinator
|
||
- `src/app/gui/widgets/editor_card.{h,cc}` - Independent card system
|
||
- `src/app/editor/dungeon/dungeon_object_interaction.{h,cc}` - Object selection
|
||
|
||
**Palette System**:
|
||
- `src/app/gfx/snes_palette.{h,cc}` - Palette loading
|
||
- `src/app/gui/widgets/palette_editor_widget.{h,cc}` - Visual editor
|
||
- `src/app/gfx/bitmap.cc` - SetPalette() implementation
|
||
|
||
### Quick Reference: Key Functions
|
||
|
||
```cpp
|
||
// Load dungeon palette
|
||
auto& pal_group = rom->palette_group().dungeon_main;
|
||
auto palette = pal_group[palette_id]; // NOT .palette(id)!
|
||
|
||
// Apply palette to bitmap
|
||
bitmap.SetPalette(palette); // NOT SetPaletteWithTransparent()!
|
||
|
||
// Create texture from indexed bitmap
|
||
Renderer::Get().RenderBitmap(&bitmap); // NOT UpdateBitmap()!
|
||
|
||
// Tile sheet offset (128px wide)
|
||
int offset = (tile_id / 16) * 8 * 128 + (tile_id % 16) * 8;
|
||
|
||
// Canvas offset (512px wide)
|
||
int offset = (tile_row * 8 * 512) + (tile_col * 8);
|
||
|
||
// Palette offset (3BPP)
|
||
int offset = palette_id * 8; // NOT << 4 !
|
||
```
|
||
|
||
### Performance Metrics
|
||
|
||
**Matrix Loading**:
|
||
- Load time: < 50ms (pure calculation, no I/O)
|
||
- Memory allocations: ~20 per matrix draw (cached colors)
|
||
- Frame drops: None
|
||
|
||
**Room Loading**:
|
||
- Lazy loading: Rooms loaded on-demand
|
||
- Graphics caching: Reused across room switches
|
||
- Texture batching: Up to 8 textures processed per frame
|
||
|
||
---
|
||
|
||
## Status Summary
|
||
|
||
### Implemented Features
|
||
|
||
**Rendering**:
|
||
- Floor rendering with tile graphics and palettes
|
||
- Object drawing via ObjectDrawer with pattern-based rendering
|
||
- Live palette editing with HSV picker
|
||
- Per-room background buffers (no shared state corruption)
|
||
|
||
**UI**:
|
||
- Independent dockable cards
|
||
- Room matrix for visual navigation
|
||
- Entrance configuration
|
||
- Cross-editor navigation (jump between overworld/dungeon)
|
||
- Error recovery system
|
||
|
||
### In Progress
|
||
|
||
**Interaction**:
|
||
- Entity click/drag for sprites and objects
|
||
- Multi-select drag for group movement
|
||
- Context-aware right-click menu
|
||
|
||
**Enhancement**:
|
||
- Object thumbnails in selector
|
||
- Room layout visual editor
|
||
- Auto-tile placement
|
||
- Object snapping grid
|
||
|
||
---
|
||
|
||
## Build Instructions
|
||
|
||
```bash
|
||
cd /Users/scawful/Code/yaze
|
||
cmake --build build_ai --target yaze -j12
|
||
./build_ai/bin/yaze.app/Contents/MacOS/yaze
|
||
```
|
||
|
||
---
|
||
|
||
**Status**: EXPERIMENTAL
|
||
|
||
The dungeon editor provides core editing capabilities but requires thorough testing before production use. Users should save backups before editing ROMs.
|
||
|
||
### Critical Rendering Pipeline Details
|
||
|
||
#### Bitmap Data Synchronization
|
||
When updating bitmap pixel data, two memory locations must stay synchronized:
|
||
1. `data_` - C++ std::vector<uint8_t>
|
||
2. `surface_->pixels` - SDL raw pixel buffer used for texture creation
|
||
|
||
**Always use**:
|
||
- `set_data()` for bulk updates (updates both vector AND surface via memcpy)
|
||
- `WriteToPixel()` for single pixel changes
|
||
- **Never** assign directly to `mutable_data()` for replacement operations
|
||
|
||
#### Texture Update Queue
|
||
Texture operations are queued and processed in batches for performance:
|
||
```cpp
|
||
// Queue texture operation
|
||
gfx::Arena::Get().QueueTextureCommand(
|
||
gfx::Arena::TextureCommandType::UPDATE, &bitmap);
|
||
|
||
// Process queue every frame (required!)
|
||
gfx::Arena::Get().ProcessTextureQueue(renderer_);
|
||
```
|
||
|
||
#### Graphics Sheet System
|
||
All 223 graphics sheets are managed centrally by `gfx::Arena`. When one editor modifies a sheet, use `Arena::NotifySheetModified(sheet_index)` to propagate changes to all editors.
|