Files
yaze/docs/internal/DUNGEON_GRAPHICS_BUG_REPORT.md

304 lines
9.0 KiB
Markdown

# 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<RoomObject>& 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<uint8_t> 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