# 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.
