270 lines
12 KiB
Markdown
270 lines
12 KiB
Markdown
# 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 |
|