Files
yaze/docs/internal/archive/investigations/object-rendering-fixes.md

324 lines
13 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.
# Object Rendering Fixes - Action Plan
**Date:** 2025-11-26
**Based on:** ZScream comparison analysis
**Status:** Ready for implementation
---
## Problem Summary
After fixing the layout loading issue (walls now render correctly), some dungeon objects still render incorrectly. Analysis of ZScream's implementation reveals yaze loads incorrect tile counts per object.
**Root Cause:** yaze hardcodes 8 tiles per object, while ZScream loads 1-242 tiles based on object type.
---
## Fix 1: Object Tile Count Lookup Table (CRITICAL)
### Files to Modify
- `src/zelda3/dungeon/object_parser.h`
- `src/zelda3/dungeon/object_parser.cc`
### Implementation
**Step 1:** Add tile count lookup table in `object_parser.h`:
```cpp
// Object-specific tile counts (from ZScream's RoomObjectTileLister)
// These specify how many 16-bit tile words to read from ROM per object
static const std::unordered_map<int16_t, int> kObjectTileCounts = {
// Subtype 1 objects (0x000-0x0FF)
{0x000, 4}, {0x001, 8}, {0x002, 8}, {0x003, 8},
{0x004, 8}, {0x005, 8}, {0x006, 8}, {0x007, 8},
{0x008, 4}, {0x009, 5}, {0x00A, 5}, {0x00B, 5},
{0x00C, 5}, {0x00D, 5}, {0x00E, 5}, {0x00F, 5},
{0x010, 5}, {0x011, 5}, {0x012, 5}, {0x013, 5},
{0x014, 5}, {0x015, 5}, {0x016, 5}, {0x017, 5},
{0x018, 5}, {0x019, 5}, {0x01A, 5}, {0x01B, 5},
{0x01C, 5}, {0x01D, 5}, {0x01E, 5}, {0x01F, 5},
{0x020, 5}, {0x021, 9}, {0x022, 3}, {0x023, 3},
{0x024, 3}, {0x025, 3}, {0x026, 3}, {0x027, 3},
{0x028, 3}, {0x029, 3}, {0x02A, 3}, {0x02B, 3},
{0x02C, 3}, {0x02D, 3}, {0x02E, 3}, {0x02F, 6},
{0x030, 6}, {0x031, 0}, {0x032, 0}, {0x033, 16},
{0x034, 1}, {0x035, 1}, {0x036, 16}, {0x037, 16},
{0x038, 6}, {0x039, 8}, {0x03A, 12}, {0x03B, 12},
{0x03C, 4}, {0x03D, 8}, {0x03E, 4}, {0x03F, 3},
{0x040, 3}, {0x041, 3}, {0x042, 3}, {0x043, 3},
{0x044, 3}, {0x045, 3}, {0x046, 3}, {0x047, 0},
{0x048, 0}, {0x049, 8}, {0x04A, 8}, {0x04B, 4},
{0x04C, 9}, {0x04D, 16}, {0x04E, 16}, {0x04F, 16},
{0x050, 1}, {0x051, 18}, {0x052, 18}, {0x053, 4},
{0x054, 0}, {0x055, 8}, {0x056, 8}, {0x057, 0},
{0x058, 0}, {0x059, 0}, {0x05A, 0}, {0x05B, 18},
{0x05C, 18}, {0x05D, 15}, {0x05E, 4}, {0x05F, 3},
{0x060, 4}, {0x061, 8}, {0x062, 8}, {0x063, 8},
{0x064, 8}, {0x065, 8}, {0x066, 8}, {0x067, 4},
{0x068, 4}, {0x069, 3}, {0x06A, 1}, {0x06B, 1},
{0x06C, 6}, {0x06D, 6}, {0x06E, 0}, {0x06F, 0},
{0x070, 16}, {0x071, 1}, {0x072, 0}, {0x073, 16},
{0x074, 16}, {0x075, 8}, {0x076, 16}, {0x077, 16},
{0x078, 4}, {0x079, 1}, {0x07A, 1}, {0x07B, 4},
{0x07C, 1}, {0x07D, 4}, {0x07E, 0}, {0x07F, 8},
{0x080, 8}, {0x081, 12}, {0x082, 12}, {0x083, 12},
{0x084, 12}, {0x085, 18}, {0x086, 18}, {0x087, 8},
{0x088, 12}, {0x089, 4}, {0x08A, 3}, {0x08B, 3},
{0x08C, 3}, {0x08D, 1}, {0x08E, 1}, {0x08F, 6},
{0x090, 8}, {0x091, 8}, {0x092, 4}, {0x093, 4},
{0x094, 16}, {0x095, 4}, {0x096, 4}, {0x097, 0},
{0x098, 0}, {0x099, 0}, {0x09A, 0}, {0x09B, 0},
{0x09C, 0}, {0x09D, 0}, {0x09E, 0}, {0x09F, 0},
{0x0A0, 1}, {0x0A1, 1}, {0x0A2, 1}, {0x0A3, 1},
{0x0A4, 24}, {0x0A5, 1}, {0x0A6, 1}, {0x0A7, 1},
{0x0A8, 1}, {0x0A9, 1}, {0x0AA, 1}, {0x0AB, 1},
{0x0AC, 1}, {0x0AD, 0}, {0x0AE, 0}, {0x0AF, 0},
{0x0B0, 1}, {0x0B1, 1}, {0x0B2, 16}, {0x0B3, 3},
{0x0B4, 3}, {0x0B5, 8}, {0x0B6, 8}, {0x0B7, 8},
{0x0B8, 4}, {0x0B9, 4}, {0x0BA, 16}, {0x0BB, 4},
{0x0BC, 4}, {0x0BD, 4}, {0x0BE, 0}, {0x0BF, 0},
{0x0C0, 1}, {0x0C1, 68}, {0x0C2, 1}, {0x0C3, 1},
{0x0C4, 8}, {0x0C5, 8}, {0x0C6, 8}, {0x0C7, 8},
{0x0C8, 8}, {0x0C9, 8}, {0x0CA, 8}, {0x0CB, 0},
{0x0CC, 0}, {0x0CD, 28}, {0x0CE, 28}, {0x0CF, 0},
{0x0D0, 0}, {0x0D1, 8}, {0x0D2, 8}, {0x0D3, 0},
{0x0D4, 0}, {0x0D5, 0}, {0x0D6, 0}, {0x0D7, 1},
{0x0D8, 8}, {0x0D9, 8}, {0x0DA, 8}, {0x0DB, 8},
{0x0DC, 21}, {0x0DD, 16}, {0x0DE, 4}, {0x0DF, 8},
{0x0E0, 8}, {0x0E1, 8}, {0x0E2, 8}, {0x0E3, 8},
{0x0E4, 8}, {0x0E5, 8}, {0x0E6, 8}, {0x0E7, 8},
{0x0E8, 8}, {0x0E9, 0}, {0x0EA, 0}, {0x0EB, 0},
{0x0EC, 0}, {0x0ED, 0}, {0x0EE, 0}, {0x0EF, 0},
{0x0F0, 0}, {0x0F1, 0}, {0x0F2, 0}, {0x0F3, 0},
{0x0F4, 0}, {0x0F5, 0}, {0x0F6, 0}, {0x0F7, 0},
// Subtype 2 objects (0x100-0x13F) - all 16 tiles for corners
{0x100, 16}, {0x101, 16}, {0x102, 16}, {0x103, 16},
{0x104, 16}, {0x105, 16}, {0x106, 16}, {0x107, 16},
{0x108, 16}, {0x109, 16}, {0x10A, 16}, {0x10B, 16},
{0x10C, 16}, {0x10D, 16}, {0x10E, 16}, {0x10F, 16},
{0x110, 12}, {0x111, 12}, {0x112, 12}, {0x113, 12},
{0x114, 12}, {0x115, 12}, {0x116, 12}, {0x117, 12},
{0x118, 4}, {0x119, 4}, {0x11A, 4}, {0x11B, 4},
{0x11C, 16}, {0x11D, 6}, {0x11E, 4}, {0x11F, 4},
{0x120, 4}, {0x121, 6}, {0x122, 20}, {0x123, 12},
{0x124, 16}, {0x125, 16}, {0x126, 6}, {0x127, 4},
{0x128, 20}, {0x129, 16}, {0x12A, 8}, {0x12B, 4},
{0x12C, 18}, {0x12D, 16}, {0x12E, 16}, {0x12F, 16},
{0x130, 16}, {0x131, 16}, {0x132, 16}, {0x133, 16},
{0x134, 4}, {0x135, 8}, {0x136, 8}, {0x137, 40},
{0x138, 12}, {0x139, 12}, {0x13A, 12}, {0x13B, 12},
{0x13C, 24}, {0x13D, 12}, {0x13E, 18}, {0x13F, 56},
// Subtype 3 objects (0x200-0x27F)
{0x200, 12}, {0x201, 20}, {0x202, 28}, {0x203, 1},
{0x204, 1}, {0x205, 1}, {0x206, 1}, {0x207, 1},
{0x208, 1}, {0x209, 1}, {0x20A, 1}, {0x20B, 1},
{0x20C, 1}, {0x20D, 6}, {0x20E, 1}, {0x20F, 1},
{0x210, 4}, {0x211, 4}, {0x212, 4}, {0x213, 4},
{0x214, 12}, {0x215, 80}, {0x216, 4}, {0x217, 6},
{0x218, 4}, {0x219, 4}, {0x21A, 4}, {0x21B, 16},
{0x21C, 16}, {0x21D, 16}, {0x21E, 16}, {0x21F, 16},
{0x220, 16}, {0x221, 16}, {0x222, 4}, {0x223, 4},
{0x224, 4}, {0x225, 4}, {0x226, 16}, {0x227, 16},
{0x228, 16}, {0x229, 16}, {0x22A, 16}, {0x22B, 4},
{0x22C, 16}, {0x22D, 84}, {0x22E, 127}, {0x22F, 4},
{0x230, 4}, {0x231, 12}, {0x232, 12}, {0x233, 16},
{0x234, 6}, {0x235, 6}, {0x236, 18}, {0x237, 18},
{0x238, 18}, {0x239, 18}, {0x23A, 24}, {0x23B, 24},
{0x23C, 24}, {0x23D, 24}, {0x23E, 4}, {0x23F, 4},
{0x240, 4}, {0x241, 4}, {0x242, 4}, {0x243, 4},
{0x244, 4}, {0x245, 4}, {0x246, 4}, {0x247, 16},
{0x248, 16}, {0x249, 4}, {0x24A, 4}, {0x24B, 24},
{0x24C, 48}, {0x24D, 18}, {0x24E, 12}, {0x24F, 4},
{0x250, 4}, {0x251, 4}, {0x252, 4}, {0x253, 4},
{0x254, 26}, {0x255, 16}, {0x256, 4}, {0x257, 4},
{0x258, 6}, {0x259, 4}, {0x25A, 8}, {0x25B, 32},
{0x25C, 24}, {0x25D, 18}, {0x25E, 4}, {0x25F, 4},
{0x260, 18}, {0x261, 18}, {0x262, 242}, {0x263, 4},
{0x264, 4}, {0x265, 4}, {0x266, 16}, {0x267, 12},
{0x268, 12}, {0x269, 12}, {0x26A, 12}, {0x26B, 16},
{0x26C, 12}, {0x26D, 12}, {0x26E, 12}, {0x26F, 12},
{0x270, 32}, {0x271, 64}, {0x272, 80}, {0x273, 1},
{0x274, 64}, {0x275, 4}, {0x276, 64}, {0x277, 24},
{0x278, 32}, {0x279, 12}, {0x27A, 16}, {0x27B, 8},
{0x27C, 4}, {0x27D, 4}, {0x27E, 4},
};
// Helper function to get tile count for an object
inline int GetObjectTileCount(int16_t object_id) {
auto it = kObjectTileCounts.find(object_id);
return (it != kObjectTileCounts.end()) ? it->second : 8; // Default 8 if not found
}
```
**Step 2:** Update `object_parser.cc` to use the lookup table:
```cpp
// Replace lines 141, 160, 178 with:
int tile_count = GetObjectTileCount(object_id);
return ReadTileData(tile_data_ptr, tile_count);
```
**Before:**
```cpp
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(
int16_t object_id) {
// ...
return ReadTileData(tile_data_ptr, 8); // ❌ WRONG
}
```
**After:**
```cpp
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(
int16_t object_id) {
// ...
int tile_count = GetObjectTileCount(object_id);
return ReadTileData(tile_data_ptr, tile_count); // ✅ CORRECT
}
```
### Testing
Test with these specific objects after fix:
| Object | Tile Count | What It Is | Expected Result |
|--------|------------|-----------|-----------------|
| 0x033 | 16 | Carpet | Full 4×4 pattern visible |
| 0x0C1 | 68 | Chest platform (tall) | Complete platform structure |
| 0x215 | 80 | Kholdstare prison cell | Full prison bars |
| 0x22D | 84 | Agahnim's altar | Symmetrical 14-tile-wide altar |
| 0x22E | 127 | Agahnim's boss room | Complete room structure |
| 0x262 | 242 | Fortune teller room | Full room layout |
---
## Fix 2: Tile Transformation Support (MEDIUM PRIORITY)
### Problem
Objects with horizontal/vertical mirroring (like Agahnim's altar) render incorrectly because tile transformations aren't applied.
### Files to Modify
- `src/zelda3/dungeon/object_drawer.h`
- `src/zelda3/dungeon/object_drawer.cc`
### Implementation
**Update `WriteTile8()` signature:**
```cpp
// In object_drawer.h
void WriteTile8(gfx::BackgroundBuffer& bg, uint8_t x_grid, uint8_t y_grid,
const gfx::TileInfo& tile_info,
bool h_flip = false, bool v_flip = false);
// In object_drawer.cc
void ObjectDrawer::WriteTile8(gfx::BackgroundBuffer& bg, uint8_t x_grid,
uint8_t y_grid, const gfx::TileInfo& tile_info,
bool h_flip, bool v_flip) {
// ... existing code ...
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
// Apply transformations
int src_x = h_flip ? (7 - px) : px;
int src_y = v_flip ? (7 - py) : py;
int src_index = (src_y * 128) + src_x + tile_base_x + tile_base_y;
// ... rest of pixel drawing code ...
}
}
}
```
**Then update draw routines to use transformations from TileInfo:**
```cpp
WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0],
tiles[0].horizontal_mirror_, tiles[0].vertical_mirror_);
```
---
## Fix 3: Update Draw Routines (OPTIONAL - For Complex Objects)
Some objects may need custom draw routines beyond pattern-based drawing. Consider implementing for:
- **0x22D (Agahnim's Altar)**: Symmetrical mirrored structure
- **0x22E (Agahnim's Boss Room)**: Complex multi-tile layout
- **0x262 (Fortune Teller Room)**: Extremely large 242-tile object
These could use a `DrawInfo`-based approach like ZScream:
```cpp
struct DrawInfo {
int tile_index;
int x_offset; // In pixels
int y_offset; // In pixels
bool h_flip;
bool v_flip;
};
void DrawFromInstructions(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles,
const std::vector<DrawInfo>& instructions);
```
---
## Expected Outcomes
After implementing Fix 1:
- ✅ Carpets (0x33) render with full 4×4 tile pattern
- ✅ Chest platforms (0xC1) render with complete structure
- ✅ Large objects (0x215, 0x22D, 0x22E) appear (though possibly with wrong orientation)
After implementing Fix 2:
- ✅ Symmetrical objects render correctly with mirroring
- ✅ Agahnim's altar/room display properly
---
## Verification Steps
1. **Build and test:**
```bash
cmake --build build --target yaze -j4
./build/bin/Debug/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon
```
2. **Test rooms with specific objects:**
- Room 0x0C (Eastern Palace): Basic walls and carpets
- Room 0x20 (Agahnim's Tower): Agahnim's altar (0x22D)
- Room 0x00 (Sanctuary): Basic objects
3. **Compare with ZScream:**
- Open same room in ZScream
- Verify tile-by-tile rendering matches
4. **Log verification:**
```cpp
printf("[ObjectParser] Object %04X: Loading %d tiles\n", object_id, tile_count);
```
---
## Notes for Implementation
- The tile count lookup table comes directly from ZScream's `RoomObjectTileLister.cs:23-534`
- Each entry represents the number of **16-bit tile words** (2 bytes each) to read from ROM
- Objects with `0` tile count are empty placeholders or special objects (moving walls, etc.)
- Tile transformations (h_flip, v_flip) are stored in TileInfo from ROM data (bits in tile word)
- Some objects (0x0CD, 0x0CE) load tiles from multiple ROM addresses (not yet supported)
---
## References
- **ZScream Source:** `/Users/scawful/Code/ZScreamDungeon/ZeldaFullEditor/Data/Underworld/RoomObjectTileLister.cs`
- **Analysis Document:** `/Users/scawful/Code/yaze/docs/internal/zscream-comparison-object-rendering.md`
- **Rendering Analysis:** `/Users/scawful/Code/yaze/docs/internal/dungeon-rendering-analysis.md`