# Dungeon Graphics Rendering Bug Report **Status**: CRITICAL - Objects not rendering **Affected System**: Dungeon Object Editor **Root Causes**: 4 critical bugs identified **Research By**: zelda3-hacking-expert + backend-infra-engineer agents --- ## Executive Summary Dungeon objects are not rendering correctly due to **incorrect ROM addresses** and **missing palette application**. Four critical bugs have been identified in the rendering pipeline. --- ## CRITICAL BUG #1: Wrong ROM Addresses in ObjectParser ⚠️ **Priority**: P0 - BLOCKER **File**: `src/zelda3/dungeon/object_parser.cc` (Lines 10-14) **Impact**: Objects read garbage data from ROM ### Current Code (WRONG) ```cpp static constexpr int kRoomObjectSubtype1 = 0x0A8000; // ❌ PLACEHOLDER static constexpr int kRoomObjectSubtype2 = 0x0A9000; // ❌ PLACEHOLDER static constexpr int kRoomObjectSubtype3 = 0x0AA000; // ❌ PLACEHOLDER static constexpr int kRoomObjectTileAddress = 0x0AB000; // ❌ PLACEHOLDER ``` **These addresses don't exist in ALTTP's ROM!** They are placeholders from early development. ### Fix (CORRECT) ```cpp // ALTTP US 1.0 ROM addresses (PC format) static constexpr int kRoomObjectSubtype1 = 0x0F8000; // SNES: $08:8000 static constexpr int kRoomObjectSubtype2 = 0x0F83F0; // SNES: $08:83F0 static constexpr int kRoomObjectSubtype3 = 0x0F84F0; // SNES: $08:84F0 static constexpr int kRoomObjectTileAddress = 0x091B52; // SNES: $09:1B52 ``` ### Explanation **How ALTTP Object Graphics Work**: ``` 1. Object ID (e.g., $10 = wall) → Subtype Table Lookup ├─ Read pointer from: kRoomObjectSubtype1 + (ID * 2) └─ Pointer is 16-bit offset from kRoomObjectTileAddress 2. Calculate Tile Data Address ├─ tile_data_addr = kRoomObjectTileAddress + offset └─ Each tile = 2 bytes (TileInfo word) 3. TileInfo Word Format (16-bit: vhopppcccccccccc) ├─ v (bit 15): Vertical flip ├─ h (bit 14): Horizontal flip ├─ o (bit 13): Priority/Over flag ├─ ppp (bits 10-12): Palette index (0-7) └─ cccccccccc (bits 0-9): CHR tile ID (0-1023) ``` **Example for Object $10 (Wall)**: ``` 1. Subtype 1 table: 0x0F8000 + ($10 * 2) = 0x0F8020 2. Read offset: [Low, High] = $0234 3. Tile data: 0x091B52 + $0234 = 0x091D86 4. Read TileInfo words (8 tiles = 16 bytes) ``` --- ## CRITICAL BUG #2: Missing Palette Application ⚠️ **Priority**: P0 - BLOCKER **File**: `src/zelda3/dungeon/object_drawer.cc` (Lines 76-104) **Impact**: Black screen or wrong colors ### The Problem `ObjectDrawer` writes palette index values (0-255) to the bitmap, but **never applies the dungeon palette** to the SDL surface. The bitmap has no color information! **Current Flow**: ``` ObjectDrawer writes index values → memcpy to SDL surface → Display ❌ ↑ No palette applied! ``` **Should Be**: ``` ObjectDrawer writes index values → Apply palette → memcpy to SDL → Display ✅ ``` ### Fix **Add to `ObjectDrawer::DrawObjectList()` after line 77**: ```cpp absl::Status ObjectDrawer::DrawObjectList( const std::vector& objects, gfx::BackgroundBuffer& bg1, gfx::BackgroundBuffer& bg2, const gfx::PaletteGroup& palette_group) { // Draw all objects for (const auto& object : objects) { RETURN_IF_ERROR(DrawObject(object, bg1, bg2, palette_group)); } // ✅ FIX: Apply dungeon palette to background buffers auto& bg1_bmp = bg1.bitmap(); auto& bg2_bmp = bg2.bitmap(); if (!palette_group.empty()) { const auto& dungeon_palette = palette_group[0]; // Main dungeon palette (90 colors) bg1_bmp.SetPalette(dungeon_palette); bg2_bmp.SetPalette(dungeon_palette); } // Sync bitmap data to SDL surfaces AFTER palette is applied if (bg1_bmp.modified() && bg1_bmp.surface() && !bg1_bmp.data().empty()) { SDL_LockSurface(bg1_bmp.surface()); memcpy(bg1_bmp.surface()->pixels, bg1_bmp.data().data(), bg1_bmp.data().size()); SDL_UnlockSurface(bg1_bmp.surface()); } if (bg2_bmp.modified() && bg2_bmp.surface() && !bg2_bmp.data().empty()) { SDL_LockSurface(bg2_bmp.surface()); memcpy(bg2_bmp.surface()->pixels, bg2_bmp.data().data(), bg2_bmp.data().size()); SDL_UnlockSurface(bg2_bmp.surface()); } return absl::OkStatus(); } ``` --- ## BUG #3: Incorrect Palette Offset Calculation **Priority**: P1 - HIGH **File**: `src/zelda3/dungeon/object_drawer.cc` (Line 900) **Impact**: Wrong colors for objects ### Current Code (WRONG) ```cpp // Line 899-900 uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8; ``` **Problem**: Uses 4 bits (`& 0x0F`) but dungeon graphics are 3BPP with only 3-bit palette indices! ### Fix ```cpp // Dungeon graphics are 3BPP (8 colors per palette) // Only use 3 bits for palette index (0-7) uint8_t palette_offset = (tile_info.palette_ & 0x07) * 8; ``` ### Dungeon Palette Structure From `snes_palette.cc` line 198: - Total: **90 colors** per dungeon palette - Colors 0-29: Main graphics (palettes 0-3) - Colors 30-59: Secondary graphics (palettes 4-7) - Colors 60-89: Sprite graphics (palettes 8-11) Each sub-palette has 8 colors (3BPP), arranged: - Palette 0: Colors 0-7 - Palette 1: Colors 8-15 - Palette 2: Colors 16-23 - Palette 3: Colors 24-29 (NOT 24-31!) --- ## BUG #4: Palette Metadata Not Initialized **Priority**: P2 - MEDIUM **File**: `src/app/gfx/render/background_buffer.cc` (constructor) **Impact**: `ApplyPaletteByMetadata()` may not work correctly ### Fix Ensure BackgroundBuffer initializes bitmap metadata: ```cpp BackgroundBuffer::BackgroundBuffer(int width, int height) : width_(width), height_(height) { buffer_.resize((width / 8) * (height / 8)); std::vector data(width * height, 0); // Create 8-bit indexed color bitmap bitmap_.Create(width, height, 8, data); // Set metadata for dungeon rendering auto& metadata = bitmap_.metadata(); metadata.source_bpp = 3; // 3BPP dungeon graphics metadata.palette_format = 0; // Full palette (90 colors) metadata.source_type = "dungeon_background"; metadata.palette_colors = 90; // Dungeon main palette size } ``` --- ## Complete Rendering Pipeline ### Correct Flow ``` 1. ROM Data (0x0F8000+) → ObjectParser ├─ Read subtype table ├─ Calculate tile data offset └─ Parse TileInfo words 2. TileInfo[] → ObjectDrawer ├─ For each tile: │ ├─ Calculate position in graphics sheet │ ├─ Read 8x8 indexed pixels (0-7) │ ├─ Apply palette offset: pixel + (palette * 8) │ └─ Write to BackgroundBuffer bitmap └─ Apply dungeon palette to bitmap (SetPalette) 3. BackgroundBuffer → SDL Surface ├─ memcpy indexed pixel data └─ SDL uses surface palette for display 4. SDL Surface → ImGui Texture → Screen ``` --- ## ROM Address Reference | Structure | SNES Address | PC Address | Purpose | |-----------|-------------|-----------|---------| | Subtype 1 Table | `$08:8000` | `0x0F8000` | Objects $00-$FF pointers (512 bytes) | | Subtype 2 Table | `$08:83F0` | `0x0F83F0` | Objects $100-$1FF pointers (256 bytes) | | Subtype 3 Table | `$08:84F0` | `0x0F84F0` | Objects $F00-$FFF pointers (256 bytes) | | Tile Data Base | `$09:1B52` | `0x091B52` | TileInfo word arrays (~8KB) | | Graphics Sheets | `$0C:8000+` | `0x0C8000+` | 4BPP compressed CHR data | --- ## Implementation Order 1. ✅ **Fix Bug #1** (ROM addresses) - 5 minutes 2. ✅ **Fix Bug #2** (palette application) - 15 minutes 3. ✅ **Fix Bug #3** (palette offset) - 5 minutes 4. ⚠️ **Fix Bug #4** (metadata) - 10 minutes (verify needed) **Total Time**: ~35 minutes to fix all critical bugs --- ## Testing Checklist After fixes: - [ ] Load dungeon room 0x01 (Eastern Palace entrance) - [ ] Verify gray stone walls render correctly - [ ] Check that objects have distinct colors - [ ] Verify no black/transparent artifacts - [ ] Test multiple rooms with different palettes - [ ] Verify BG1 and BG2 layers are distinct --- ## Debugging Commands Add these logs to verify the fix: ```cpp // In ObjectParser::ReadTileData() after reading first tile: if (i == 0) { printf("[ObjectParser] Object 0x%03X: tile_addr=0x%06X word=0x%04X → id=%03X pal=%d\n", object_id, tile_offset, tile_word, tile_info.id_, tile_info.palette_); } // In ObjectDrawer::DrawObjectList() after applying palette: if (!palette_group.empty()) { const auto& pal = palette_group[0]; printf("[ObjectDrawer] Applied palette: %zu colors, first=RGB(%d,%d,%d)\n", pal.size(), pal[0].rom_color().red, pal[0].rom_color().green, pal[0].rom_color().blue); } // In DrawTileToBitmap() after palette calculation: printf("[Tile] ID=0x%03X pal_idx=%d offset=%d pixel[0]=%d\n", tile_info.id_, tile_info.palette_, palette_offset, tiledata[0]); ``` --- ## References - **ALTTP Disassembly**: https://github.com/ spannerisms/ALTTPR-estrela - **ZScream Source**: DungeonObjectData.cs (C# implementation) - **yaze Graphics System**: CLAUDE.md Pattern 4 (Bitmap sync requirements) --- **Last Updated**: 2025-11-21 **Research By**: CLAUDE_CORE (zelda3-hacking-expert + backend-infra-engineer) **Status**: Ready for implementation