# 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 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> ObjectParser::ParseSubtype1( int16_t object_id) { // ... return ReadTileData(tile_data_ptr, 8); // ❌ WRONG } ``` **After:** ```cpp absl::StatusOr> 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 tiles, const std::vector& 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`