Files
yaze/docs/internal/archive/investigations/dungeon-rendering-analysis.md

270 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dungeon Rendering System Analysis
This document analyzes the dungeon object and background rendering pipeline, identifying potential issues with palette indexing, graphics buffer access, and memory safety.
## Graphics Pipeline Overview
```
ROM Data (3BPP compressed)
DecompressV2() → SnesTo8bppSheet()
graphics_buffer_ (8BPP linear, values 0-7)
Room::CopyRoomGraphicsToBuffer()
current_gfx16_ (room-specific graphics buffer)
ObjectDrawer::DrawTileToBitmap() / BackgroundBuffer::DrawTile()
Bitmap pixel data (indexed 8BPP)
SetPalette() → SDL Surface → Texture
```
## Palette Structure
### ROM Storage (kDungeonMainPalettes = 0xDD734)
- 20 dungeon palette sets
- 90 colors per set (180 bytes)
- Colors packed without transparent entries
### SNES Hardware Layout
The SNES expects 16-color rows with transparent at indices 0, 16, 32...
### Current yaze Implementation
- 90 colors loaded as linear array (indices 0-89)
- 6 groups of 15 colors each
- Palette stride: `* 15`
## Palette Offset Analysis
### Current Implementation
```cpp
// object_drawer.cc:916
uint8_t palette_offset = (tile_info.palette_ & 0x07) * 15;
// background_buffer.cc:64
uint8_t palette_offset = palette_idx * 15;
```
### The Math
For 3BPP graphics (pixel values 0-7):
- Pixel 0 = transparent (skipped)
- Pixels 1-7 → `(pixel - 1) + palette_offset`
With `* 15` stride:
| Palette | Pixel 1 | Pixel 7 | Colors Used |
|---------|---------|---------|-------------|
| 0 | 0 | 6 | 0-6 |
| 1 | 15 | 21 | 15-21 |
| 2 | 30 | 36 | 30-36 |
| 3 | 45 | 51 | 45-51 |
| 4 | 60 | 66 | 60-66 |
| 5 | 75 | 81 | 75-81 |
**Unused colors**: 7-14, 22-29, 37-44, 52-59, 67-74, 82-89 (8 colors per group)
### Verdict
The `* 15` stride is **correct** for the 90-color packed format. The "wasted" colors are an artifact of:
- ROM storing 15 colors per group (4BPP capacity)
- Graphics using only 8 values (3BPP)
## Current Status & Findings (2025-11-26)
### 1. 8BPP Conversion Mismatch (Fixed)
- **Issue:** `LoadAllGraphicsData` converted 3BPP to **8BPP linear** (1 byte/pixel), but `Room::CopyRoomGraphicsToBuffer` was treating it as 3BPP planar and trying to convert it to 4BPP packed. This caused double conversion and data corruption.
- **Fix:** Updated `CopyRoomGraphicsToBuffer` to copy 8BPP data directly (4096 bytes per sheet). Updated draw routines to read 1 byte per pixel.
### 2. Palette Stride (Fixed)
- **Issue:** Previous code used `* 16` stride, which skipped colors in the packed 90-color palette.
- **Fix:** Updated to `* 15` stride and `pixel - 1` indexing.
### 3. Buffer vs. Arena (Investigation)
- **Issue:** We attempted to switch to `gfx::Arena::Get().gfx_sheets()` for safer access, but this resulted in an empty frame (likely due to initialization order or empty Arena).
- **Status:** Reverted to `rom()->graphics_buffer()` but added strict 8BPP offset calculations (4096 bytes/sheet).
- **Artifacts:** We observed "number-like" tiles (5, 7, 8) instead of dungeon walls. This suggests we might be reading from a font sheet or an incorrect offset in the buffer.
- **Next Step:** Debug logging added to `CopyRoomGraphicsToBuffer` to print block IDs and raw bytes. This will confirm if we are reading valid graphics data or garbage.
### 4. LoadAnimatedGraphics sizeof vs size() (Fixed 2025-11-26)
- **Issue:** `room.cc:821,836` used `sizeof(current_gfx16_)` instead of `.size()` for bounds checking.
- **Context:** For `std::array<uint8_t, N>`, sizeof equals N (works), but this pattern is confusing and fragile.
- **Fix:** Updated to use `current_gfx16_.size()` for clarity and maintainability.
### 5. LoadRoomGraphics Entrance Blockset Condition (Not a Bug - 2025-11-26)
- **File:** `src/zelda3/dungeon/room.cc:352`
- **Observation:** Condition `if (i == 6)` applies entrance graphics only to block 6
- **Status:** This is intentional behavior. The misleading "3-6" comment was removed.
- **Note:** Changing to `i >= 3 && i <= 6` caused tiling artifacts - reverted.
### 7. Layout Not Being Loaded (Fixed 2025-11-26) - MAJOR BREAKTHROUGH
- **File:** `src/zelda3/dungeon/room.cc` - `LoadLayoutTilesToBuffer()`
- **Issue:** `layout_.LoadLayout(layout)` was never called, so `layout_.GetObjects()` always returned empty
- **Impact:** Only floor tiles were drawn, no layout tiles appeared
- **Fix:** Added `layout_.set_rom(rom_)` and `layout_.LoadLayout(layout)` call before accessing layout objects
- **Result:** **WALLS NOW RENDER CORRECTLY!** Left/right walls display properly.
- **Remaining:** Some objects still don't look right - needs further investigation
## Breakthrough Status (2025-11-26)
### What's Working Now
- ✅ Floor tiles render correctly
- ✅ Layout tiles load from ROM
- ✅ Left/right walls display correctly
- ✅ Basic room structure visible
### What Still Needs Work
- ⚠️ Some objects don't render correctly
- ⚠️ Need to verify object tile IDs and graphics lookup
- ⚠️ May be palette or graphics sheet issues for specific object types
- ⚠️ Floor rendering (screenshot shows grid, need to confirm if floor tiles are actually rendering or if the grid is obscuring them)
### Next Investigation Steps
1. **Verify Floor Rendering:** Check if the floor tiles are actually rendering underneath the grid or if they are missing.
2. **Check Object Types:** Identify which specific objects are rendering incorrectly (e.g., chests, pots, enemies).
3. **Verify Tile IDs:** Check `RoomObject::DecodeObjectFromBytes()` and `GetTile()` to ensure correct tile IDs are being calculated.
4. **Debug Logging:** Use the added logging to verify that the correct graphics sheets are being loaded for the objects.
## Detailed Context for Next Session
### Architecture Overview
The dungeon rendering has TWO separate tile systems:
1. **Layout Tiles** (`RoomLayout` class) - Pre-defined room templates (8 layouts total)
- Loaded from ROM via `kRoomLayoutPointers[]` in `dungeon_rom_addresses.h`
- Rendered by `LoadLayoutTilesToBuffer()``bg1_buffer_.SetTileAt()` / `bg2_buffer_.SetTileAt()`
- **NOW WORKING** after the LoadLayout fix
2. **Object Tiles** (`RoomObject` class) - Placed objects (walls, doors, decorations, etc.)
- Loaded from ROM via `LoadObjects()``ParseObjectsFromLocation()`
- Rendered by `RenderObjectsToBackground()``ObjectDrawer::DrawObject()`
- **PARTIALLY WORKING** - walls visible but some objects look wrong
### Key Files for Object Rendering
| File | Purpose |
|------|---------|
| `room.cc:LoadObjects()` | Parses object data from ROM |
| `room.cc:RenderObjectsToBackground()` | Iterates objects, calls ObjectDrawer |
| `object_drawer.cc` | Main object rendering logic |
| `object_drawer.cc:DrawTileToBitmap()` | Draws individual 8x8 tiles |
| `room_object.cc:DecodeObjectFromBytes()` | Decodes 3-byte object format |
| `room_object.cc:GetTile()` | Returns TileInfo for object tiles |
### Object Encoding Format (3 bytes)
```
Byte 1: YYYYY XXX (Y = tile Y position bits 4-0, X = tile X position bits 2-0)
Byte 2: S XXX YYYY (S = size bit, X = tile X position bits 5-3, Y = tile Y position bits 8-5)
Byte 3: OOOOOOOO (Object ID)
```
### Potential Object Rendering Issues to Investigate
1. **Tile ID Calculation**
- Objects use `GetTile(index)` to get TileInfo for each sub-tile
- The tile ID might be calculated incorrectly for some object types
- Check `RoomObject::EnsureTilesLoaded()` and tile lookup tables
2. **Graphics Sheet Selection**
- Objects should use tiles from `current_gfx16_` (room-specific buffer)
- Different object types may need tiles from different sheet ranges:
- Blocks 0-7: Main dungeon graphics
- Blocks 8-11: Static sprites (pots, fairies, etc.)
- Blocks 12-15: Enemy sprites
3. **Palette Assignment**
- Objects have a `palette_` field in TileInfo
- Dungeon palette has 6 groups × 15 colors = 90 colors
- Palette offset = `(palette_ & 0x07) * 15`
- Some objects might have wrong palette index
4. **Object Type Handlers**
- `ObjectDrawer` has different draw methods for different object sizes
- `DrawSingle()`, `Draw2x2()`, `DrawVertical()`, `DrawHorizontal()`, etc.
- Some handlers might have bugs in tile placement
### Debug Logging Currently Active
- `[CopyRoomGraphicsToBuffer]` - Logs block/sheet IDs and first bytes
- `[RenderRoomGraphics]` - Logs dirty flags and floor graphics
- `[LoadLayoutTilesToBuffer]` - Logs layout object count
- `[ObjectDrawer]` - Logs first 5 tile draws with position/palette info
### Files Modified in This Session
1. `src/zelda3/dungeon/room.cc:LoadLayoutTilesToBuffer()` - Added layout loading call
2. `src/zelda3/dungeon/room.cc:LoadRoomGraphics()` - Fixed comment, kept i==6 condition
3. `src/app/app.cmake` - Added z3ed WASM exports
4. `src/app/editor/ui/ui_coordinator.cc` - Fixed menu bar right panel positioning
5. `src/app/rom.cc` - Added debug logging for graphics loading
### Quick Test Commands
```bash
# Build
cmake --build build --target yaze -j4
# Run with dungeon editor
./build/bin/Debug/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon
```
### 6. 2BPP Placeholder Sheets (Verified 2025-11-26)
- **Sheets 113-114:** These are 2BPP font/title sheets loaded separately via Load2BppGraphics()
- **In graphics_buffer:** They contain 0xFF placeholder data (4096 bytes each)
- **Impact:** If blockset IDs accidentally point to 113-114, tiles render as solid color
- **Status:** This is expected behavior, not a bug
## Debugging the "Number-Like" Artifacts
The observed "5, 7, 8" number patterns could indicate:
1. **Font Sheet Access:** Blockset IDs pointing to font sheets (but sheets 113-114 have 0xFF, not font data)
2. **Debug Rendering:** Tile IDs or coordinates rendered as text (check for printf to canvas)
3. **Corrupted Offset:** Wrong src_index calculation causing read from arbitrary memory
4. **Uninitialized blocks_:** If LoadRoomGraphics() not called before CopyRoomGraphicsToBuffer()
### Debug Logging Added (room.cc)
```cpp
printf("[CopyRoomGraphicsToBuffer] Block %d (Sheet %d): Offset %d\n", block, sheet_id, src_sheet_offset);
printf(" Bytes: %02X %02X %02X %02X %02X %02X %02X %02X\n", ...);
```
### Next Steps
1. Run the application and check console output for:
- Sheet IDs for each block (should be 0-112 or 115-126 for valid dungeon graphics)
- First bytes of each sheet (should NOT be 0xFF for valid graphics)
2. If sheet IDs are valid but graphics are wrong, check:
- LoadGfxGroups() output for blockset 0 (verify main_blockset_ids)
- GetGraphicsAddress() returning correct ROM offsets
## Graphics Buffer Layout
### Per-Sheet (4096 bytes each)
- Width: 128 pixels (16 tiles × 8 pixels)
- Height: 32 pixels (4 tiles × 8 pixels)
- Format: 8BPP linear (1 byte per pixel)
- Values: 0-7 for 3BPP graphics
### Room Graphics Buffer (current_gfx16_)
- Size: 64KB (0x10000 bytes)
- Layout: 16 blocks × 4096 bytes
- Contains: Room-specific graphics from blocks_[0..15]
### Index Calculation
```cpp
int tile_col = tile_id % 16;
int tile_row = tile_id / 16;
int tile_base_x = tile_col * 8;
int tile_base_y = tile_row * 1024; // 8 rows * 128 bytes stride
int src_index = (py * 128) + px + tile_base_x + tile_base_y;
```
## Files Involved
| File | Function | Purpose |
|------|----------|---------|
| `rom.cc` | `LoadAllGraphicsData()` | Decompresses and converts 3BPP→8BPP |
| `room.cc` | `CopyRoomGraphicsToBuffer()` | Copies sheet data to room buffer |
| `room.cc` | `LoadAnimatedGraphics()` | Loads animated tile data |
| `room.cc` | `RenderRoomGraphics()` | Renders room with palette |
| `object_drawer.cc` | `DrawTileToBitmap()` | Draws object tiles |
| `background_buffer.cc` | `DrawTile()` | Draws background tiles |
| `snes_palette.cc` | `LoadDungeonMainPalettes()` | Loads 90-color palettes |
| `snes_tile.cc` | `SnesTo8bppSheet()` | Converts 3BPP→8BPP |