257 lines
12 KiB
Markdown
257 lines
12 KiB
Markdown
# Phase 4: Dungeon Object Rendering - 0x80-0xFF Mapping Fixes
|
||
|
||
## Status: Steps 1, 2, 3, 4 Complete (2024-12-04)
|
||
|
||
### Completed
|
||
- **Step 1**: Quick mapping fixes (6 corrections using existing routines)
|
||
- **Step 2**: Simple variant routines (10 new routines, IDs 65-74)
|
||
- **Step 3**: Diagonal ceiling routines (4 new routines, IDs 75-78, covering 12 objects)
|
||
- **Step 4**: SuperSquare routines (9 new routines, IDs 56-64)
|
||
|
||
### Remaining
|
||
- **Step 5**: Special/logic-dependent routines (6 new routines)
|
||
|
||
### Test Updates Required
|
||
Some unit tests assert old (incorrect) mappings and need to be updated:
|
||
- `test/unit/zelda3/dungeon/draw_routine_mapping_test.cc`
|
||
- `test/unit/zelda3/dungeon/object_drawing_comprehensive_test.cc`
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
This document provides a handoff plan for completing Phase 4 of the dungeon object rendering system fixes. Phases 1-3 have been completed:
|
||
|
||
- **Phase 1**: Parser fixes (Type 2 index mask, routine pointer offset, removed fake mappings)
|
||
- **Phase 2**: Dimension calculation fixes (dual-nibble bug, diagonal wall off-by-one)
|
||
- **Phase 3**: Routine implementations for 0x40-0x7F range (13 new routines added)
|
||
|
||
## Phase 4 Scope
|
||
|
||
Fix object-to-routine mappings for the 0x80-0xFF range. The audit identified a **75.8% error rate** (97/128 objects with wrong mappings).
|
||
|
||
## Reference Files
|
||
|
||
- **Assembly Ground Truth**: `assets/asm/usdasm/bank_01.asm` (lines 397-516 for 0x80-0xF7)
|
||
- **Implementation File**: `src/zelda3/dungeon/object_drawer.cc` (lines 330-479)
|
||
- **Header File**: `src/zelda3/dungeon/object_drawer.h`
|
||
|
||
## Strategy
|
||
|
||
### Tier 1: Easy Fixes (Use Existing Routines)
|
||
|
||
These objects can be fixed by simply changing the routine ID to an existing routine:
|
||
|
||
| Object | Current | Correct Routine | Assembly Reference |
|
||
|--------|---------|-----------------|-------------------|
|
||
| 0x81-0x84 | 30 (4x3) | NEW: DownwardsDecor3x4spaced2 | `RoomDraw_DownwardsDecor3x4spaced2_1to16` |
|
||
| 0x88 | 30 (4x3) | NEW: DownwardsBigRail3x1_plus5 | `RoomDraw_DownwardsBigRail3x1_1to16plus5` |
|
||
| 0x89 | 11 | NEW: DownwardsBlock2x2spaced2 | `RoomDraw_DownwardsBlock2x2spaced2_1to16` |
|
||
| 0x8D-0x8E | 25 (1x1) | 13 (DownwardsEdge1x1) | `RoomDraw_DownwardsEdge1x1_1to16` |
|
||
| 0x90-0x91 | 8 | 8 (Downwards4x2_1to15or26) | Already correct! |
|
||
| 0x92-0x93 | 11 | 7 (Downwards2x2_1to15or32) | `RoomDraw_Downwards2x2_1to15or32` |
|
||
| 0x94 | 16 | 43 (DownwardsFloor4x4) | `RoomDraw_DownwardsFloor4x4_1to16` |
|
||
| 0xB0-0xB1 | 25 | NEW: RightwardsEdge1x1_plus7 | `RoomDraw_RightwardsEdge1x1_1to16plus7` |
|
||
| 0xB6-0xB7 | 8 | 1 (Rightwards2x4_1to15or26) | `RoomDraw_Rightwards2x4_1to15or26` |
|
||
| 0xB8-0xB9 | 11 | 0 (Rightwards2x2_1to15or32) | `RoomDraw_Rightwards2x2_1to15or32` |
|
||
| 0xBB | 11 | 55 (RightwardsBlock2x2spaced2) | `RoomDraw_RightwardsBlock2x2spaced2_1to16` |
|
||
|
||
### Tier 2: New Routine Implementations Required
|
||
|
||
These need new draw routines to be implemented:
|
||
|
||
#### 2A: Simple Variants (Low Effort)
|
||
|
||
| Routine Name | Objects | Pattern | Notes |
|
||
|-------------|---------|---------|-------|
|
||
| `DrawDownwardsDecor3x4spaced2_1to16` | 0x81-0x84 | 3x4 tiles, 6-row spacing | Similar to existing `DrawDownwardsDecor4x4spaced2_1to16` |
|
||
| `DrawDownwardsBigRail3x1_1to16plus5` | 0x88 | 3x1 tiles, +5 modifier | Vertical version of existing `DrawRightwardsBigRail1x3_1to16plus5` |
|
||
| `DrawDownwardsBlock2x2spaced2_1to16` | 0x89 | 2x2 tiles, 4-row spacing | Vertical version of existing `DrawRightwardsBlock2x2spaced2_1to16` |
|
||
| `DrawDownwardsCannonHole3x4_1to16` | 0x85-0x86 | 3x4 cannon hole | Vertical version of existing cannon hole |
|
||
| `DrawDownwardsBar2x5_1to16` | 0x8F | 2x5 bar pattern | New pattern |
|
||
| `DrawDownwardsPots2x2_1to16` | 0x95 | 2x2 pots | Interactive object |
|
||
| `DrawDownwardsHammerPegs2x2_1to16` | 0x96 | 2x2 hammer pegs | Interactive object |
|
||
| `DrawRightwardsEdge1x1_1to16plus7` | 0xB0-0xB1 | 1x1 edge, +7 modifier | Similar to existing edge routines |
|
||
| `DrawRightwardsPots2x2_1to16` | 0xBC | 2x2 pots | Interactive object |
|
||
| `DrawRightwardsHammerPegs2x2_1to16` | 0xBD | 2x2 hammer pegs | Interactive object |
|
||
|
||
#### 2B: Complex/Special Routines (Higher Effort)
|
||
|
||
| Routine Name | Objects | Pattern | Notes |
|
||
|-------------|---------|---------|-------|
|
||
| `DrawDiagonalCeilingTopLeftA` | 0xA0 | Diagonal ceiling variant A | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingBottomLeftA` | 0xA1 | Diagonal ceiling variant A | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingTopRightA` | 0xA2 | Diagonal ceiling variant A | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingBottomRightA` | 0xA3 | Diagonal ceiling variant A | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingTopLeftB` | 0xA5, 0xA9 | Diagonal ceiling variant B | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingBottomLeftB` | 0xA6, 0xAA | Diagonal ceiling variant B | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingTopRightB` | 0xA7, 0xAB | Diagonal ceiling variant B | Complex diagonal pattern |
|
||
| `DrawDiagonalCeilingBottomRightB` | 0xA8, 0xAC | Diagonal ceiling variant B | Complex diagonal pattern |
|
||
| `DrawBigHole4x4_1to16` | 0xA4 | 4x4 big hole | Special floor cutout |
|
||
| `Draw4x4BlocksIn4x4SuperSquare` | 0xC0, 0xC2 | 4x4 in 16x16 super square | Large composite object |
|
||
| `Draw3x3FloorIn4x4SuperSquare` | 0xC3, 0xD7 | 3x3 in 16x16 super square | Large composite object |
|
||
| `Draw4x4FloorIn4x4SuperSquare` | 0xC5-0xCA, 0xD1-0xD2, 0xD9, 0xDF-0xE8 | 4x4 floor pattern | Most common super square |
|
||
| `Draw4x4FloorOneIn4x4SuperSquare` | 0xC4 | Single 4x4 in super square | Variant |
|
||
| `Draw4x4FloorTwoIn4x4SuperSquare` | 0xDB | Two 4x4 in super square | Variant |
|
||
| `DrawClosedChestPlatform` | 0xC1 | Chest platform (closed) | 68-tile special object |
|
||
| `DrawOpenChestPlatform` | 0xDC | Chest platform (open) | State-dependent |
|
||
| `DrawMovingWallWest` | 0xCD | Moving wall (west) | Logic-dependent |
|
||
| `DrawMovingWallEast` | 0xCE | Moving wall (east) | Logic-dependent |
|
||
| `DrawCheckIfWallIsMoved` | 0xD3-0xD6 | Wall movement check | Logic-only, no tiles |
|
||
| `DrawWaterOverlayA8x8_1to16` | 0xD8 | Water overlay A | Semi-transparent overlay |
|
||
| `DrawWaterOverlayB8x8_1to16` | 0xDA | Water overlay B | Semi-transparent overlay |
|
||
| `DrawTableRock4x4_1to16` | 0xDD | Table rock pattern | 4x4 repeating |
|
||
| `DrawSpike2x2In4x4SuperSquare` | 0xDE | Spikes in super square | Hazard object |
|
||
|
||
## Implementation Approach
|
||
|
||
### Step 1: Quick Wins (30 min)
|
||
Fix the easy ID corrections that use existing routines:
|
||
- 0x8D-0x8E: Change from 25 to 13
|
||
- 0x92-0x93: Change from 11 to 7
|
||
- 0x94: Change from 16 to 43
|
||
- 0xB6-0xB7: Change from 8 to 1
|
||
- 0xB8-0xB9: Change from 11 to 0
|
||
- 0xBB: Change from 11 to 55
|
||
|
||
### Step 2: Add Simple Variant Routines (2-3 hours)
|
||
Create the Tier 2A routines by copying existing patterns and adjusting for vertical/horizontal orientation:
|
||
|
||
```cpp
|
||
// Example: DrawDownwardsBlock2x2spaced2_1to16 (mirror of existing horizontal routine)
|
||
void ObjectDrawer::DrawDownwardsBlock2x2spaced2_1to16(
|
||
const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||
std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
|
||
int size = obj.size_ & 0x0F;
|
||
int count = size + 1;
|
||
|
||
for (int s = 0; s < count; s++) {
|
||
if (tiles.size() >= 4) {
|
||
int base_y = obj.y_ + (s * 4); // 4-tile Y spacing
|
||
WriteTile8(bg, obj.x_, base_y, tiles[0]);
|
||
WriteTile8(bg, obj.x_ + 1, base_y, tiles[1]);
|
||
WriteTile8(bg, obj.x_, base_y + 1, tiles[2]);
|
||
WriteTile8(bg, obj.x_ + 1, base_y + 1, tiles[3]);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Step 3: SuperSquare Routines (4-6 hours)
|
||
The `4x4FloorIn4x4SuperSquare` pattern is used by ~20 objects. Understanding it unlocks many fixes:
|
||
|
||
```
|
||
SuperSquare = 16x16 tile area (128x128 pixels)
|
||
- Draws 4x4 tile patterns at each corner position
|
||
- Some variants fill differently (3x3, single, two, etc.)
|
||
```
|
||
|
||
Analyze the assembly at `RoomDraw_4x4FloorIn4x4SuperSquare` to understand the exact tile placement.
|
||
|
||
### Step 4: Diagonal Ceiling Routines (2-3 hours)
|
||
8 diagonal ceiling variants (A and B × 4 corners). These are similar to existing diagonal wall routines but for ceiling tiles.
|
||
|
||
### Step 5: Logic-Dependent Objects (1-2 hours)
|
||
- `DrawMovingWallWest/East`: Check wall state before drawing
|
||
- `DrawCheckIfWallIsMoved`: Logic-only, maps to "Nothing" for rendering
|
||
- `DrawClosedChestPlatform/OpenChestPlatform`: Check chest state
|
||
|
||
## Testing Strategy
|
||
|
||
1. **Visual Verification**: Load a ROM and visually compare dungeon rooms against screenshots or another editor (ZScream, Hyrule Magic)
|
||
|
||
2. **Target Rooms for Testing**:
|
||
- Hyrule Castle rooms (variety of floor patterns)
|
||
- Eastern Palace (diagonal ceilings at 0xA0-0xAC)
|
||
- Thieves' Town (moving walls at 0xCD-0xCE)
|
||
- Ice Palace (water overlays at 0xD8, 0xDA)
|
||
|
||
3. **Object Selection Test**: After rendering fixes, verify objects can be clicked/selected properly
|
||
|
||
## Current Routine Registry
|
||
|
||
As of Phase 4 Steps 1, 2, 3, 4 completion, we have 79 routines (0-78):
|
||
|
||
| ID Range | Description |
|
||
|----------|-------------|
|
||
| 0-6 | Rightwards basic patterns |
|
||
| 7-15 | Downwards basic patterns |
|
||
| 16 | Rightwards 4x4 |
|
||
| 17-18 | Diagonal BothBG |
|
||
| 19-39 | Various patterns (corners, edges, chests, etc.) |
|
||
| 40-42 | Rightwards 4x2, Decor4x2spaced8, CannonHole4x3 |
|
||
| 43-50 | New vertical routines (Phase 3.2) |
|
||
| 51-55 | New horizontal routines (Phase 3.3) |
|
||
| **56-64** | **Phase 4 SuperSquare routines** |
|
||
| **65-74** | **Phase 4 Step 2 simple variant routines** |
|
||
| **75-78** | **Phase 4 Step 3 diagonal ceiling routines** |
|
||
|
||
### Phase 4 New Routines (IDs 56-78)
|
||
|
||
**SuperSquare Routines (IDs 56-64)**:
|
||
|
||
| ID | Routine Name | Objects |
|
||
|----|--------------|---------|
|
||
| 56 | Draw4x4BlocksIn4x4SuperSquare | 0xC0, 0xC2 |
|
||
| 57 | Draw3x3FloorIn4x4SuperSquare | 0xC3, 0xD7 |
|
||
| 58 | Draw4x4FloorIn4x4SuperSquare | 0xC5-0xCA, 0xD1-0xD2, 0xD9, 0xDF-0xE8 |
|
||
| 59 | Draw4x4FloorOneIn4x4SuperSquare | 0xC4 |
|
||
| 60 | Draw4x4FloorTwoIn4x4SuperSquare | 0xDB |
|
||
| 61 | DrawBigHole4x4_1to16 | 0xA4 |
|
||
| 62 | DrawSpike2x2In4x4SuperSquare | 0xDE |
|
||
| 63 | DrawTableRock4x4_1to16 | 0xDD |
|
||
| 64 | DrawWaterOverlay8x8_1to16 | 0xD8, 0xDA |
|
||
|
||
**Step 2 Simple Variant Routines (IDs 65-74)**:
|
||
|
||
| ID | Routine Name | Objects |
|
||
|----|--------------|---------|
|
||
| 65 | DrawDownwardsDecor3x4spaced2_1to16 | 0x81-0x84 |
|
||
| 66 | DrawDownwardsBigRail3x1_1to16plus5 | 0x88 |
|
||
| 67 | DrawDownwardsBlock2x2spaced2_1to16 | 0x89 |
|
||
| 68 | DrawDownwardsCannonHole3x6_1to16 | 0x85-0x86 |
|
||
| 69 | DrawDownwardsBar2x3_1to16 | 0x8F |
|
||
| 70 | DrawDownwardsPots2x2_1to16 | 0x95 |
|
||
| 71 | DrawDownwardsHammerPegs2x2_1to16 | 0x96 |
|
||
| 72 | DrawRightwardsEdge1x1_1to16plus7 | 0xB0-0xB1 |
|
||
| 73 | DrawRightwardsPots2x2_1to16 | 0xBC |
|
||
| 74 | DrawRightwardsHammerPegs2x2_1to16 | 0xBD |
|
||
|
||
**Step 3 Diagonal Ceiling Routines (IDs 75-78)**:
|
||
|
||
| ID | Routine Name | Objects |
|
||
|----|--------------|---------|
|
||
| 75 | DrawDiagonalCeilingTopLeft | 0xA0, 0xA5, 0xA9 |
|
||
| 76 | DrawDiagonalCeilingBottomLeft | 0xA1, 0xA6, 0xAA |
|
||
| 77 | DrawDiagonalCeilingTopRight | 0xA2, 0xA7, 0xAB |
|
||
| 78 | DrawDiagonalCeilingBottomRight | 0xA3, 0xA8, 0xAC |
|
||
|
||
New routines should continue from ID 79.
|
||
|
||
## Files to Modify
|
||
|
||
1. **`src/zelda3/dungeon/object_drawer.h`**
|
||
- Add declarations for new draw routines
|
||
|
||
2. **`src/zelda3/dungeon/object_drawer.cc`**
|
||
- Update `object_to_routine_map_` entries (lines 330-479)
|
||
- Add new routine implementations
|
||
- Register new routines in `InitializeDrawRoutines()` (update reserve count)
|
||
|
||
3. **`src/zelda3/dungeon/object_dimensions.cc`** (optional)
|
||
- Update dimension calculations if needed for new patterns
|
||
|
||
## Priority Order
|
||
|
||
1. **Highest**: Fix existing routine ID mappings (Step 1)
|
||
2. **High**: Implement `Draw4x4FloorIn4x4SuperSquare` (unlocks ~20 objects)
|
||
3. **Medium**: Add simple variant routines (Step 2)
|
||
4. **Medium**: Diagonal ceiling routines (Step 4)
|
||
5. **Lower**: Logic-dependent and special objects (Step 5)
|
||
|
||
## Notes
|
||
|
||
- The audit found that objects 0x97-0x9F, 0xAD-0xAF, 0xBE-0xBF, 0xE9-0xF7 correctly map to `RoomDraw_Nothing_B` (routine 38)
|
||
- Some objects like 0xD3-0xD6 (`CheckIfWallIsMoved`) are logic-only and don't render tiles
|
||
- Interactive objects (pots, hammer pegs) may need additional state handling
|