Files
yaze/docs/internal/archive/completed_features/dungeon-object-rendering-fix-plan.md

7.8 KiB

Dungeon Object Rendering Fix Plan

Completed Phases

Phase 1: BG Layer Draw Order - COMPLETED (2025-11-26)

File: src/app/editor/dungeon/dungeon_canvas_viewer.cc:900-971 Fix: Swapped draw order - BG2 drawn first (background), then BG1 (foreground with objects) Result: Objects on BG1 no longer covered by BG2

Phase 2: Wall Rendering Investigation - COMPLETED (2025-11-26)

Finding: Bitmap initialization was the issue, not draw routines Root Cause: Test code was creating bitmap with {0} which only allocated 1 byte instead of 262144 Verification: Created ROM-dependent integration tests:

  • test/integration/zelda3/dungeon_graphics_transparency_test.cc
  • All 5 tests pass confirming:
    • Graphics buffer has 13% transparent pixels
    • Room graphics buffer has 31.9% transparent pixels
    • Wall objects load 8 tiles each correctly
    • BG1 has 24,000 non-zero pixels after object drawing

Wall tiles confirmed working: 0x090, 0x092, 0x093, 0x096, 0x098, 0x099, 0x0A2, 0x0A4, 0x0A5, 0x0AC, 0x0AD

Phase 3: Subtype1 Tile Count Lookup Table - COMPLETED (2025-11-26)

File: src/zelda3/dungeon/object_parser.cc:18-57 Fix: Added kSubtype1TileLengths[0xF8] lookup table from ZScream's DungeonObjectData.cs Changes:

  • Added 248-entry tile count lookup table for Subtype 1 objects
  • Modified GetSubtype1TileCount() helper function to use lookup table
  • Updated GetObjectSubtype() and ParseSubtype1() to use dynamic tile counts
  • Objects now load correct number of tiles (e.g., 0xC1 = 68 tiles, 0x33 = 16 tiles)

Source: ZScream DungeonObjectData.cs

All Critical Phases Complete

Root Cause Summary: Multiple issues - layer draw order (FIXED), bitmap sizing (FIXED), tile counts (FIXED).

Critical Findings

Finding 1: Hardcoded Tile Count (ROOT CAUSE)

  • Location: src/zelda3/dungeon/object_parser.cc:141,160,178
  • Issue: ReadTileData(tile_data_ptr, 8) always reads 8 tiles
  • Impact:
    • Simple objects (walls: 8 tiles) render correctly
    • Medium objects (carpets: 16 tiles) render incomplete
    • Complex objects (Agahnim's altar: 84 tiles) severely broken
  • Fix: Use ZScream's subtype1Lengths[] lookup table

Finding 2: Type 2/Type 3 Boundary Collision

  • Location: src/zelda3/dungeon/room_object.cc:184-190, 204-208
  • Issue: Type 2 objects with Y positions 3,7,11,...,63 encode to b3 >= 0xF8, triggering incorrect Type 3 decoding
  • Impact: 512 object placements affected

Finding 3: Type 2 Subtype Index Mask

  • Location: src/zelda3/dungeon/object_parser.cc:77, 147-148
  • Issue: Uses mask 0x7F for 256 IDs, causing IDs 0x180-0x1FF to alias to 0x100-0x17F
  • Fix: Use object_id & 0xFF or object_id - 0x100

Finding 4: Type 3 Subtype Heuristic

  • Location: src/zelda3/dungeon/room_object.cc:18-28, 74
  • Issue: GetSubtypeTable() uses id_ >= 0x200 but Type 3 IDs are 0xF00-0xFFF
  • Fix: Change to id_ >= 0xF00

Finding 5: Object ID Validation Range

  • Location: src/zelda3/dungeon/room.cc:966
  • Issue: Validates r.id_ <= 0x3FF but decoder can produce IDs up to 0xFFF
  • Fix: Change to r.id_ <= 0xFFF

Finding 6: tile_objects_ Not Cleared on Reload

  • Location: src/zelda3/dungeon/room.cc:908
  • Issue: Calling LoadObjects() twice causes object duplication
  • Fix: Add tile_objects_.clear() at start of LoadObjects()

Finding 7: Incomplete Draw Routine Registry

  • Location: src/zelda3/dungeon/object_drawer.cc:170
  • Issue: Reserves 35 routines but only initializes 17 (indices 0-16)
  • Impact: Object IDs mapping to routines 17-34 fallback to 1x1 drawing

Implementation Plan

Phase 1: Fix BG Layer Draw Order (CRITICAL - DO FIRST)

File: src/app/editor/dungeon/dungeon_canvas_viewer.cc Location: DrawRoomBackgroundLayers() (lines 900-968)

Problem: BG1 is drawn first, then BG2 is drawn ON TOP with 255 alpha, covering BG1 content.

Fix: Swap the draw order - draw BG2 first (background), then BG1 (foreground):

void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) {
  // ... validation code ...

  // Draw BG2 FIRST (background layer - underneath)
  if (layer_settings.bg2_visible && bg2_bitmap.is_active() ...) {
    // ... existing BG2 draw code ...
  }

  // Draw BG1 SECOND (foreground layer - on top)
  if (layer_settings.bg1_visible && bg1_bitmap.is_active() ...) {
    // ... existing BG1 draw code ...
  }
}

Phase 2: Investigate North/South Wall Draw Routines

Observation: Left/right walls (vertical, Downwards routines 0x60+) work, but up/down walls (horizontal, Rightwards routines 0x00-0x0B) don't.

Files to check:

  • src/zelda3/dungeon/object_drawer.cc - Rightwards draw routines
  • Object-to-routine mapping for wall IDs

Wall Object IDs:

  • 0x00: Ceiling (routine 0 - DrawRightwards2x2_1to15or32)
  • 0x01-0x02: North walls (routine 1 - DrawRightwards2x4_1to15or26)
  • 0x03-0x04: South walls (routine 2 - DrawRightwards2x4spaced4_1to16)
  • 0x60+: East/West walls (Downwards routines - WORKING)

Possible issues:

  1. Object tiles not being loaded for Subtype1 0x00-0x0B
  2. Draw routines have coordinate bugs
  3. Objects assigned to wrong layer (BG2 instead of BG1)

Phase 3: Subtype1 Tile Count Fix

Files to modify:

  • src/zelda3/dungeon/object_parser.cc

Add ZScream's tile count lookup table:

static constexpr uint8_t kSubtype1TileLengths[0xF8] = {
    04,08,08,08,08,08,08,04,04,05,05,05,05,05,05,05,
    05,05,05,05,05,05,05,05,05,05,05,05,05,05,05,05,
    05,09,03,03,03,03,03,03,03,03,03,03,03,03,03,06,
    06,01,01,16,01,01,16,16,06,08,12,12,04,08,04,03,
    03,03,03,03,03,03,03,00,00,08,08,04,09,16,16,16,
    01,18,18,04,01,08,08,01,01,01,01,18,18,15,04,03,
    04,08,08,08,08,08,08,04,04,03,01,01,06,06,01,01,
    16,01,01,16,16,08,16,16,04,01,01,04,01,04,01,08,
    08,12,12,12,12,18,18,08,12,04,03,03,03,01,01,06,
    08,08,04,04,16,04,04,01,01,01,01,01,01,01,01,01,
    01,01,01,01,24,01,01,01,01,01,01,01,01,01,01,01,
    01,01,16,03,03,08,08,08,04,04,16,04,04,04,01,01,
    01,68,01,01,08,08,08,08,08,08,08,01,01,28,28,01,
    01,08,08,00,00,00,00,01,08,08,08,08,21,16,04,08,
    08,08,08,08,08,08,08,08,08,01,01,01,01,01,01,01,
    01,01,01,01,01,01,01,01
};

Phase 4: Object Type Detection Fixes (Deferred)

  • Type 2/Type 3 boundary collision
  • Type 2 index mask (0x7F vs 0xFF)
  • Type 3 detection heuristic (0x200 vs 0xF00)

Phase 5: Validation & Lifecycle Fixes (Deferred)

  • Object ID validation range (0x3FF → 0xFFF)
  • tile_objects_ not cleared on reload

Phase 6: Draw Routine Completion (Deferred)

  • Complete draw routine registry (routines 17-34)

Testing Strategy

Test Objects by Complexity

Object ID Tiles Description Expected Result
0x000 4 Ceiling Works
0x001 8 Wall (north) Works
0x033 16 Carpet Should render fully
0x0C1 68 Chest platform Should render fully
0x215 80 Prison cell Should render fully

Rooms to Test

  • Room 0x00 (Simple walls)
  • Room with carpets
  • Agahnim's tower rooms
  • Fortune teller room (uses 242-tile objects)

Files to Read Before Implementation

  1. /Users/scawful/Code/yaze/src/zelda3/dungeon/object_parser.cc - PRIMARY - Find the hardcoded 8 in tile loading
  2. /Users/scawful/Code/ZScreamDungeon/ZeldaFullEditor/Data/DungeonObjectData.cs - Verify tile table values

Estimated Impact

  • Phase 1 alone should fix ~90% of broken Subtype1 objects (most common type)
  • Simple walls/floors already work (they use 4-8 tiles)
  • Carpets (16 tiles), chest platforms (68 tiles), and complex objects will now render fully

Risk Assessment

  • Low Risk: Adding a lookup table is additive, doesn't change existing logic flow
  • Mitigation: Compare visual output against ZScream for a few test rooms