yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
object_drawer.cc
Go to the documentation of this file.
1#include "object_drawer.h"
2
3#include <cstdio>
4#include <filesystem>
5
6#include "absl/strings/str_format.h"
8#include "rom/rom.h"
9#include "core/features.h"
10#include "rom/snes.h"
11#include "util/log.h"
16
17namespace yaze {
18namespace zelda3 {
19
20ObjectDrawer::ObjectDrawer(Rom* rom, int room_id, const uint8_t* room_gfx_buffer)
21 : rom_(rom), room_id_(room_id), room_gfx_buffer_(room_gfx_buffer) {
23}
24
25absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
28 const gfx::PaletteGroup& palette_group,
29 [[maybe_unused]] const DungeonState* state,
30 gfx::BackgroundBuffer* layout_bg1) {
31 if (!rom_ || !rom_->is_loaded()) {
32 return absl::FailedPreconditionError("ROM not loaded");
33 }
34
36 return absl::FailedPreconditionError("Draw routines not initialized");
37 }
38
39 // Ensure object has tiles loaded
40 auto mutable_obj = const_cast<RoomObject&>(object);
41 mutable_obj.SetRom(rom_);
42 mutable_obj.EnsureTilesLoaded();
43
44 // Select buffer based on layer
45 // Layer 0 (BG1): Main objects - drawn to BG1_Objects (on top of layout)
46 // Layer 1 (BG2): Overlay objects - drawn to BG2_Objects (behind layout)
47 // Layer 2 (BG3): Priority objects (torches) - drawn to BG1_Objects (on top)
48 bool use_bg2 = (object.layer_ == RoomObject::LayerType::BG2);
49 auto& target_bg = use_bg2 ? bg2 : bg1;
50
51 // Log buffer selection for debugging layer routing
52 LOG_DEBUG("ObjectDrawer", "Object 0x%03X layer=%d -> drawing to %s buffer",
53 object.id_, static_cast<int>(object.layer_),
54 use_bg2 ? "BG2 (behind layout)" : "BG1 (on top of layout)");
55
56 // Skip objects that don't have tiles loaded
57 if (mutable_obj.tiles().empty()) {
58 LOG_DEBUG("ObjectDrawer", "Object 0x%03X at (%d,%d) has NO TILES - skipping",
59 object.id_, object.x_, object.y_);
60 return absl::OkStatus();
61 }
62
63 // Check for custom object override first
64 // We check this BEFORE routine lookup to allow overriding vanilla objects
65 int subtype = object.size_ & 0x1F;
66 if (CustomObjectManager::Get().GetObjectInternal(object.id_, subtype).ok()) {
67 // Custom objects default to drawing on the target layer only, unless all_bgs_ is set
68 // Mask propagation is difficult without dimensions, so we rely on explicit transparency in the custom object tiles if needed
69
70 // Draw to target layer
71 DrawCustomObject(object, target_bg, mutable_obj.tiles(), state);
72
73 // If marked for both BGs, draw to the other layer too
74 if (object.all_bgs_) {
75 auto& other_bg = (object.layer_ == RoomObject::LayerType::BG2 ||
76 object.layer_ == RoomObject::LayerType::BG3) ? bg1 : bg2;
77 DrawCustomObject(object, other_bg, mutable_obj.tiles(), state);
78 }
79 // return absl::OkStatus();
80 }
81
82 // Look up draw routine for this object
83 int routine_id = GetDrawRoutineId(object.id_);
84
85 // Log draw routine lookup with tile info
86 LOG_DEBUG("ObjectDrawer",
87 "Object 0x%03X at (%d,%d) size=%d -> routine=%d tiles=%zu",
88 object.id_, object.x_, object.y_, object.size_, routine_id,
89 mutable_obj.tiles().size());
90
91 if (routine_id < 0 || routine_id >= static_cast<int>(draw_routines_.size())) {
92 LOG_DEBUG("ObjectDrawer",
93 "Object 0x%03X: NO ROUTINE (id=%d, max=%zu) - using fallback 1x1",
94 object.id_, routine_id, draw_routines_.size());
95 // Fallback to simple 1x1 drawing using first 8x8 tile
96 if (!mutable_obj.tiles().empty()) {
97 const auto& tile_info = mutable_obj.tiles()[0];
98 WriteTile8(target_bg, object.x_, object.y_, tile_info);
99 }
100 return absl::OkStatus();
101 }
102
103 // Check if this should draw to both BG layers
104 // IMPORTANT: BothBG only applies to Layer 0 (main) objects - structural elements.
105 // Layer 1 (BG2 overlay) objects should ONLY draw to BG2 so they appear BEHIND
106 // Layer 0 content. Layer 2 (priority) objects should ONLY draw to BG1 (on top).
107 bool is_both_bg = (object.layer_ == RoomObject::LayerType::BG1) &&
108 (object.all_bgs_ || RoutineDrawsToBothBGs(routine_id));
109
110 if (is_both_bg) {
111 // Draw to both background layers
112 draw_routines_[routine_id](this, object, bg1, mutable_obj.tiles(), state);
113 draw_routines_[routine_id](this, object, bg2, mutable_obj.tiles(), state);
114 } else {
115 // Execute the appropriate draw routine on target buffer only
116 draw_routines_[routine_id](this, object, target_bg, mutable_obj.tiles(), state);
117 }
118
119 // BG2 Mask Propagation: ONLY pit/mask objects should mark BG1 as transparent.
120 // Regular Layer 1 objects (walls, statues, stairs) should just draw to BG2
121 // and let normal compositing handle the layering (BG1 draws on top of BG2).
122 //
123 // FIX: Previously this marked ALL Layer 1 objects as creating BG1 transparency,
124 // which caused walls/statues to incorrectly show "above" the layout.
125 // Now we only call MarkBG1Transparent for actual pit/mask objects.
126 bool is_pit_or_mask = (object.id_ == 0xA4) || // BigHole4x4
127 (object.id_ >= 0x9B && object.id_ <= 0xA6) || // Pit edges
128 (object.id_ == 0xFE6) || // Type 3 pit
129 (object.id_ == 0xFBE || object.id_ == 0xFBF); // Layer 2 pit masks
130
131 if (is_pit_or_mask && object.layer_ == RoomObject::LayerType::BG2 && !is_both_bg) {
132 auto [pixel_width, pixel_height] = CalculateObjectDimensions(object);
133
134 // Log pit/mask transparency propagation
135 LOG_DEBUG("ObjectDrawer",
136 "Pit mask 0x%03X at (%d,%d) -> marking %dx%d pixels transparent in BG1",
137 object.id_, object.x_, object.y_, pixel_width, pixel_height);
138
139 // Mark the object buffer BG1 as transparent
140 MarkBG1Transparent(bg1, object.x_, object.y_, pixel_width, pixel_height);
141 // Also mark the layout buffer (floor tiles) as transparent if provided
142 if (layout_bg1 != nullptr) {
143 MarkBG1Transparent(*layout_bg1, object.x_, object.y_, pixel_width,
144 pixel_height);
145 }
146 }
147
148 return absl::OkStatus();
149}
150
152 const std::vector<RoomObject>& objects, gfx::BackgroundBuffer& bg1,
153 gfx::BackgroundBuffer& bg2, const gfx::PaletteGroup& palette_group,
154 [[maybe_unused]] const DungeonState* state,
155 gfx::BackgroundBuffer* layout_bg1) {
157 absl::Status status = absl::OkStatus();
158
159 // DEBUG: Count objects routed to each buffer
160 int to_bg1 = 0, to_bg2 = 0, both_bgs = 0;
161
162 for (const auto& object : objects) {
163 // Track buffer routing for summary
164 bool use_bg2 = (object.layer_ == RoomObject::LayerType::BG2);
165 bool is_layer0 = (object.layer_ == RoomObject::LayerType::BG1);
166 int routine_id = GetDrawRoutineId(object.id_);
167 bool is_both_bg = is_layer0 && (object.all_bgs_ || RoutineDrawsToBothBGs(routine_id));
168
169 if (is_both_bg) {
170 both_bgs++;
171 } else if (use_bg2) {
172 to_bg2++;
173 } else {
174 to_bg1++;
175 }
176
177 auto s = DrawObject(object, bg1, bg2, palette_group, state, layout_bg1);
178 if (!s.ok() && status.ok()) {
179 status = s;
180 }
181 }
182
183 LOG_DEBUG("ObjectDrawer", "Buffer routing: to_BG1=%d, to_BG2=%d, BothBGs=%d",
184 to_bg1, to_bg2, both_bgs);
185
186 // NOTE: Palette is already set in Room::RenderRoomGraphics() before calling
187 // this function. We just need to sync the pixel data to the SDL surface.
188 auto& bg1_bmp = bg1.bitmap();
189 auto& bg2_bmp = bg2.bitmap();
190
191 // Sync bitmap data to SDL surfaces (palette already applied)
192 if (bg1_bmp.modified() && bg1_bmp.surface() &&
193 bg1_bmp.mutable_data().size() > 0) {
194 SDL_LockSurface(bg1_bmp.surface());
195 // Safety check: ensure surface can hold the data
196 // Note: This assumes 8bpp surface where pitch >= width
197 size_t surface_size = bg1_bmp.surface()->h * bg1_bmp.surface()->pitch;
198 size_t buffer_size = bg1_bmp.mutable_data().size();
199
200 if (surface_size >= buffer_size) {
201 // TODO: Handle pitch mismatch properly (copy row by row)
202 // For now, just ensure we don't overflow
203 memcpy(bg1_bmp.surface()->pixels, bg1_bmp.mutable_data().data(),
204 buffer_size);
205 } else {
206 LOG_DEBUG("ObjectDrawer", "BG1 Surface too small: surf=%zu buf=%zu", surface_size, buffer_size);
207 }
208 SDL_UnlockSurface(bg1_bmp.surface());
209 }
210
211 if (bg2_bmp.modified() && bg2_bmp.surface() &&
212 bg2_bmp.mutable_data().size() > 0) {
213 SDL_LockSurface(bg2_bmp.surface());
214 size_t surface_size = bg2_bmp.surface()->h * bg2_bmp.surface()->pitch;
215 size_t buffer_size = bg2_bmp.mutable_data().size();
216
217 if (surface_size >= buffer_size) {
218 memcpy(bg2_bmp.surface()->pixels, bg2_bmp.mutable_data().data(),
219 buffer_size);
220 } else {
221 LOG_DEBUG("ObjectDrawer", "BG2 Surface too small: surf=%zu buf=%zu", surface_size, buffer_size);
222 }
223 SDL_UnlockSurface(bg2_bmp.surface());
224 }
225
226 return status;
227}
228
229// ============================================================================
230// Metadata-based BothBG Detection
231// ============================================================================
232
234 // Use the unified DrawRoutineRegistry for BothBG metadata.
235 // This ensures consistency between ObjectDrawer and ObjectGeometry.
236 //
237 // Fallback to hardcoded list for routines not yet in the registry,
238 // particularly layout structural objects that MUST draw to both BG layers.
239
240 // First check the unified registry
242 return true;
243 }
244
245 // Fallback: Layout structural objects that must draw to both BG layers
246 // even if not explicitly marked in registry metadata.
247 // These form the room structure (walls, corners, ceilings).
248 static constexpr int kBothBGRoutines[] = {
252 DrawRoutineIds::kCorner4x4, // 19: layout corners
255 DrawRoutineIds::kDiagonalAcute_1to16_BothBG, // 17: upper-left diagonal
256 DrawRoutineIds::kDiagonalGrave_1to16_BothBG, // 18: upper-right diagonal
257 DrawRoutineIds::kCorner4x4_BothBG, // 35: 4x4 corners
259 DrawRoutineIds::kWeirdCornerTop_BothBG, // 37: weird corners
260 };
261
262 for (int id : kBothBGRoutines) {
263 if (routine_id == id) return true;
264 }
265 return false;
266}
267
268// ============================================================================
269// Draw Routine Registry Initialization
270// ============================================================================
271
273 // This function maps object IDs to their corresponding draw routines.
274 // The mapping is based on ZScream's DungeonObjectData.cs and the game's
275 // assembly code. The order of functions in draw_routines_ MUST match the
276 // indices used here.
277 //
278 // ASM Reference (Bank 01):
279 // Subtype 1 Data Offset: $018000 (DrawObjects.type1_subtype_1_data_offset)
280 // Subtype 1 Routine Ptr: $018200 (DrawObjects.type1_subtype_1_routine)
281 // Subtype 2 Data Offset: $0183F0 (DrawObjects.type1_subtype_2_data_offset)
282 // Subtype 2 Routine Ptr: $018470 (DrawObjects.type1_subtype_2_routine)
283 // Subtype 3 Data Offset: $0184F0 (DrawObjects.type1_subtype_3_data_offset)
284 // Subtype 3 Routine Ptr: $0185F0 (DrawObjects.type1_subtype_3_routine)
285
287 draw_routines_.clear();
288
289 // Subtype 1 Object Mappings (Horizontal)
290 // ASM: Routines $00-$0B from table at $018200
291 object_to_routine_map_[0x00] = 0;
292 // Objects 0x01-0x02 use RoomDraw_Rightwards2x4_1to15or26 (routine 1)
293 // ASM: GetSize_1to15or26 - size defaults to 26 when 0
294 for (int id = 0x01; id <= 0x02; id++) {
296 }
297 // Objects 0x03-0x04 use RoomDraw_Rightwards2x4spaced4_1to16 (routine 2)
298 // ASM: GetSize_1to16 - count = size + 1
299 for (int id = 0x03; id <= 0x04; id++) {
301 }
302 for (int id = 0x05; id <= 0x06; id++) {
304 }
305 for (int id = 0x07; id <= 0x08; id++) {
307 }
308 object_to_routine_map_[0x09] = 5;
309 for (int id = 0x0A; id <= 0x0B; id++) {
311 }
312
313 // Diagonal walls (0x0C-0x20) - Verified against bank_01.asm
314 // Non-BothBG diagonals: 0x0C-0x14 (matching assembly lines 280-289)
315 // Acute Diagonals (/) - NON-BothBG
316 for (int id : {0x0C, 0x0D, 0x10, 0x11, 0x14}) {
317 object_to_routine_map_[id] = 5; // DiagonalAcute_1to16 (non-BothBG)
318 }
319 // Grave Diagonals (\‍) - NON-BothBG
320 for (int id : {0x0E, 0x0F, 0x12, 0x13}) {
321 object_to_routine_map_[id] = 6; // DiagonalGrave_1to16 (non-BothBG)
322 }
323 // BothBG diagonals start at 0x15 (matching assembly lines 289-300)
324 // Acute Diagonals (/) - BothBG
325 for (int id : {0x15, 0x18, 0x19, 0x1C, 0x1D, 0x20}) {
326 object_to_routine_map_[id] = 17; // DiagonalAcute_1to16_BothBG
327 }
328 // Grave Diagonals (\‍) - BothBG
329 for (int id : {0x16, 0x17, 0x1A, 0x1B, 0x1E, 0x1F}) {
330 object_to_routine_map_[id] = 18; // DiagonalGrave_1to16_BothBG
331 }
332
333 // Edge and Corner Objects (0x21-0x30) - Verified against bank_01.asm lines 302-317
334 object_to_routine_map_[0x21] = 20; // RoomDraw_Rightwards1x2_1to16_plus2
335 object_to_routine_map_[0x22] = 21; // RoomDraw_RightwardsHasEdge1x1_1to16_plus3
336 // 0x23-0x2E all use RoomDraw_RightwardsHasEdge1x1_1to16_plus2
337 for (int id = 0x23; id <= 0x2E; id++) {
338 object_to_routine_map_[id] = 22;
339 }
340 object_to_routine_map_[0x2F] = 23; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
341 object_to_routine_map_[0x30] = 24; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
342
343 // Custom Objects (0x31-0x32) - Oracle of Secrets minecart tracks and furniture
344 // USDASM marks these as RoomDraw_Nothing; only map to custom routines when enabled.
346 // These use external binary files instead of ROM tile data.
348 object_to_routine_map_[0x32] = DrawRoutineIds::kCustomObject; // Custom furniture
349 } else {
352 }
353 object_to_routine_map_[0x33] = 16; // 4x4 Block
354 object_to_routine_map_[0x34] = 25; // Solid 1x1
355 object_to_routine_map_[0x35] = 26; // Door Switcher
356 object_to_routine_map_[0x36] = 27; // Decor 4x4
357 object_to_routine_map_[0x37] = 27; // Decor 4x4
358 object_to_routine_map_[0x38] = 28; // Statue 2x3
359 object_to_routine_map_[0x39] = 29; // Pillar 2x4
360 object_to_routine_map_[0x3A] = 30; // Decor 4x3
361 object_to_routine_map_[0x3B] = 30; // Decor 4x3
362 object_to_routine_map_[0x3C] = 31; // Doubled 2x2
363 object_to_routine_map_[0x3D] = 29; // Pillar 2x4
364 object_to_routine_map_[0x3E] = 32; // Decor 2x2
365
366 // 0x3F-0x46 all use RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (verified bank_01.asm lines 332-338)
367 for (int id = 0x3F; id <= 0x46; id++) {
368 object_to_routine_map_[id] = 22;
369 }
370 // 0x47-0x48 Waterfalls - use dedicated waterfall routines
371 // ASM: RoomDraw_Waterfall47 and RoomDraw_Waterfall48
372 object_to_routine_map_[0x47] = 111; // Waterfall47 (1x5 columns)
373 object_to_routine_map_[0x48] = 112; // Waterfall48 (1x3 columns)
374 // 0x49-0x4A Floor Tile 4x2 - Verified against bank_01.asm RoomDraw_RightwardsFloorTile4x2_1to16
375 object_to_routine_map_[0x49] = 40; // Rightwards4x2 (4 cols × 2 rows, horizontal spacing)
376 object_to_routine_map_[0x4A] = 40;
377 // 0x4B Decor 2x2 spaced 12
378 object_to_routine_map_[0x4B] = 32;
379 // 0x4C: RoomDraw_RightwardsBar4x3_1to16 (bank_01.asm line 345)
380 object_to_routine_map_[0x4C] = 52; // Bar4x3
381 // 0x4D-0x4F: RoomDraw_RightwardsShelf4x4_1to16 (bank_01.asm lines 346-348)
382 object_to_routine_map_[0x4D] = 53; // Shelf4x4
383 object_to_routine_map_[0x4E] = 53;
384 object_to_routine_map_[0x4F] = 53;
385
386 // 0x50-0x5F Objects (verified against bank_01.asm routine table at $018200)
387 // 0x50: RoomDraw_RightwardsLine1x1_1to16plus1 (bank_01.asm line 349)
388 object_to_routine_map_[0x50] = 51; // Line1x1_plus1
389 // 0x51-0x52: RoomDraw_RightwardsCannonHole4x3_1to16 (18 tiles = 4×3 pattern + edges)
390 object_to_routine_map_[0x51] = 42; // CannonHole4x3
391 object_to_routine_map_[0x52] = 42;
392 // 0x53: RoomDraw_Rightwards2x2_1to16
393 object_to_routine_map_[0x53] = 4; // 2x2
394 // 0x54: RoomDraw_Nothing_B
395 object_to_routine_map_[0x54] = 38; // Nothing
396 // 0x55-0x56: RoomDraw_RightwardsDecor4x2spaced8_1to16 (8 tiles, 12-column spacing)
397 object_to_routine_map_[0x55] = 41; // Decor4x2spaced8
398 object_to_routine_map_[0x56] = 41;
399 // 0x57-0x5A: RoomDraw_Nothing_C
400 object_to_routine_map_[0x57] = 38; // Nothing
401 object_to_routine_map_[0x58] = 38;
402 object_to_routine_map_[0x59] = 38;
403 object_to_routine_map_[0x5A] = 38;
404 // 0x5B-0x5C: RoomDraw_RightwardsCannonHole4x3_1to16
405 object_to_routine_map_[0x5B] = 42; // CannonHole4x3
406 object_to_routine_map_[0x5C] = 42;
407 // 0x5D: RoomDraw_RightwardsBigRail1x3_1to16plus5 (bank_01.asm line 362)
408 object_to_routine_map_[0x5D] = 54; // BigRail1x3_plus5
409 // 0x5E: RoomDraw_RightwardsBlock2x2spaced2_1to16 (bank_01.asm line 363)
410 object_to_routine_map_[0x5E] = 55; // Block2x2spaced2
411 // 0x5F: RoomDraw_RightwardsHasEdge1x1_1to16_plus23
412 object_to_routine_map_[0x5F] = 22; // edge 1x1
413
414 // Subtype 1 Object Mappings (Vertical 0x60-0x6F)
415 object_to_routine_map_[0x60] = 7;
416 for (int id = 0x61; id <= 0x62; id++) {
418 }
419 for (int id = 0x63; id <= 0x64; id++) {
421 }
422 for (int id = 0x65; id <= 0x66; id++) {
423 object_to_routine_map_[id] = 10;
424 }
425 for (int id = 0x67; id <= 0x68; id++) {
426 object_to_routine_map_[id] = 11;
427 }
428 object_to_routine_map_[0x69] = 12;
429 for (int id = 0x6A; id <= 0x6B; id++) {
430 object_to_routine_map_[id] = 13;
431 }
432 object_to_routine_map_[0x6C] = 14;
433 object_to_routine_map_[0x6D] = 15;
434 // 0x6E-0x6F: 1 tile each
435 object_to_routine_map_[0x6E] = 38; // Nothing (Logic: RoomDraw_Nothing_A)
436 object_to_routine_map_[0x6F] = 38;
437
438 // 0x70-0x7F Objects (verified against bank_01.asm lines 381-396)
439 // 0x70: RoomDraw_DownwardsFloor4x4_1to16
440 object_to_routine_map_[0x70] = 43; // DownwardsFloor4x4
441 // 0x71: RoomDraw_Downwards1x1Solid_1to16_plus3
442 object_to_routine_map_[0x71] = 44; // Downwards1x1Solid_plus3
443 // 0x72: RoomDraw_Nothing_B
444 object_to_routine_map_[0x72] = 38; // Nothing
445 // 0x73-0x74: RoomDraw_DownwardsDecor4x4spaced2_1to16
446 object_to_routine_map_[0x73] = 45; // DownwardsDecor4x4spaced2
447 object_to_routine_map_[0x74] = 45;
448 // 0x75: RoomDraw_DownwardsPillar2x4spaced2_1to16
449 object_to_routine_map_[0x75] = 46; // DownwardsPillar2x4spaced2
450 // 0x76-0x77: RoomDraw_DownwardsDecor3x4spaced4_1to16
451 object_to_routine_map_[0x76] = 47; // DownwardsDecor3x4spaced4
452 object_to_routine_map_[0x77] = 47;
453 // 0x78: RoomDraw_DownwardsDecor2x2spaced12_1to16
454 object_to_routine_map_[0x78] = 48; // DownwardsDecor2x2spaced12
455 // 0x79-0x7A: RoomDraw_DownwardsEdge1x1_1to16 (bank_01.asm lines 390-391)
456 object_to_routine_map_[0x79] = 13; // DownwardsEdge1x1
457 object_to_routine_map_[0x7A] = 13;
458 // 0x7B: RoomDraw_DownwardsDecor2x2spaced12_1to16 (same as 0x78)
459 object_to_routine_map_[0x7B] = 48; // DownwardsDecor2x2spaced12
460 // 0x7C: RoomDraw_DownwardsLine1x1_1to16plus1 (bank_01.asm line 393)
461 object_to_routine_map_[0x7C] = 49; // DownwardsLine1x1_plus1
462 // 0x7D: RoomDraw_Downwards2x2_1to16 (bank_01.asm line 394)
463 object_to_routine_map_[0x7D] = 11; // Downwards2x2_1to16
464 // 0x7E: RoomDraw_Nothing_B
465 object_to_routine_map_[0x7E] = 38; // Nothing
466 // 0x7F: RoomDraw_DownwardsDecor2x4spaced8_1to16
467 object_to_routine_map_[0x7F] = 50; // DownwardsDecor2x4spaced8
468
469 // 0x80-0x8F Objects (verified against bank_01.asm lines 397-412)
470 // 0x80: RoomDraw_DownwardsDecor2x4spaced8_1to16 (same as 0x7F)
471 object_to_routine_map_[0x80] = 50; // DownwardsDecor2x4spaced8
472 // 0x81-0x84: RoomDraw_DownwardsDecor3x4spaced2_1to16 (12 tiles = 3x4)
473 object_to_routine_map_[0x81] = 65; // DownwardsDecor3x4spaced2_1to16
474 object_to_routine_map_[0x82] = 65;
475 object_to_routine_map_[0x83] = 65;
476 object_to_routine_map_[0x84] = 65;
477 // 0x85-0x86: RoomDraw_DownwardsCannonHole3x6_1to16 (18 tiles = 3x6)
478 object_to_routine_map_[0x85] = 68; // DownwardsCannonHole3x6_1to16
479 object_to_routine_map_[0x86] = 68;
480 // 0x87: RoomDraw_DownwardsPillar2x4spaced2_1to16 (same as 0x75)
481 object_to_routine_map_[0x87] = 46; // DownwardsPillar2x4spaced2
482 // 0x88: RoomDraw_DownwardsBigRail3x1_1to16plus5 (12 tiles)
483 object_to_routine_map_[0x88] = 66; // DownwardsBigRail3x1_1to16plus5
484 // 0x89: RoomDraw_DownwardsBlock2x2spaced2_1to16 (4 tiles)
485 object_to_routine_map_[0x89] = 67; // DownwardsBlock2x2spaced2_1to16
486 // 0x8A-0x8C: Vertical rails with CORNER+MIDDLE+END pattern (3 tiles each)
487 // ASM: RoomDraw_DownwardsHasEdge1x1_1to16_plus23 - matches horizontal 0x22
491 // 0x8D-0x8E: RoomDraw_DownwardsEdge1x1_1to16
492 object_to_routine_map_[0x8D] = 13; // DownwardsEdge1x1_1to16
493 object_to_routine_map_[0x8E] = 13;
494 // 0x8F: RoomDraw_DownwardsBar2x3_1to16 (6 tiles = 2x3)
495 object_to_routine_map_[0x8F] = 69; // DownwardsBar2x3_1to16
496
497 // 0x90-0x9F Objects (based on tile counts from kSubtype1TileLengths)
498 // 0x90-0x91: 8 tiles each
499 object_to_routine_map_[0x90] = 8; // 4x2
500 object_to_routine_map_[0x91] = 8;
501 // 0x92-0x93: RoomDraw_Downwards2x2_1to15or32
502 object_to_routine_map_[0x92] = 7; // Downwards2x2_1to15or32
503 object_to_routine_map_[0x93] = 7;
504 // 0x94: RoomDraw_DownwardsFloor4x4_1to16
505 object_to_routine_map_[0x94] = 43; // DownwardsFloor4x4_1to16
506 // 0x95: RoomDraw_DownwardsPots2x2_1to16 (4 tiles = 2x2 interactive)
507 object_to_routine_map_[0x95] = 70; // DownwardsPots2x2_1to16
508 // 0x96: RoomDraw_DownwardsHammerPegs2x2_1to16 (4 tiles = 2x2 interactive)
509 object_to_routine_map_[0x96] = 71; // DownwardsHammerPegs2x2_1to16
510 // 0x97-0x9F: 1 tile each
511 for (int id = 0x97; id <= 0x9F; id++) {
512 object_to_routine_map_[id] = 38; // Nothing (Logic: RoomDraw_Nothing_B)
513 }
514
515 // 0xA0-0xAF Objects (diagonal ceilings and big hole)
516 // Phase 4 Step 3: Diagonal ceiling routines implemented
517 // DiagonalCeilingTopLeft: 0xA0, 0xA5, 0xA9
518 object_to_routine_map_[0xA0] = 75; // DiagonalCeilingTopLeft (A variant)
519 object_to_routine_map_[0xA5] = 75; // DiagonalCeilingTopLeft (B variant)
520 object_to_routine_map_[0xA9] = 75; // DiagonalCeilingTopLeft (B variant)
521 // DiagonalCeilingBottomLeft: 0xA1, 0xA6, 0xAA
522 object_to_routine_map_[0xA1] = 76; // DiagonalCeilingBottomLeft (A variant)
523 object_to_routine_map_[0xA6] = 76; // DiagonalCeilingBottomLeft (B variant)
524 object_to_routine_map_[0xAA] = 76; // DiagonalCeilingBottomLeft (B variant)
525 // DiagonalCeilingTopRight: 0xA2, 0xA7, 0xAB
526 object_to_routine_map_[0xA2] = 77; // DiagonalCeilingTopRight (A variant)
527 object_to_routine_map_[0xA7] = 77; // DiagonalCeilingTopRight (B variant)
528 object_to_routine_map_[0xAB] = 77; // DiagonalCeilingTopRight (B variant)
529 // DiagonalCeilingBottomRight: 0xA3, 0xA8, 0xAC
530 object_to_routine_map_[0xA3] = 78; // DiagonalCeilingBottomRight (A variant)
531 object_to_routine_map_[0xA8] = 78; // DiagonalCeilingBottomRight (B variant)
532 object_to_routine_map_[0xAC] = 78; // DiagonalCeilingBottomRight (B variant)
533 // BigHole4x4: 0xA4
534 object_to_routine_map_[0xA4] = 61; // BigHole4x4_1to16
535 // Nothing: 0xAD-0xAF
536 object_to_routine_map_[0xAD] = 38; // Nothing (RoomDraw_Nothing_B)
537 object_to_routine_map_[0xAE] = 38; // Nothing (RoomDraw_Nothing_B)
538 object_to_routine_map_[0xAF] = 38; // Nothing (RoomDraw_Nothing_B)
539
540 // 0xB0-0xBF Objects (based on tile counts from kSubtype1TileLengths)
541 // 0xB0-0xB1: RoomDraw_RightwardsEdge1x1_1to16plus7 (1 tile with +7 offset)
542 object_to_routine_map_[0xB0] = 72; // RightwardsEdge1x1_1to16plus7
543 object_to_routine_map_[0xB1] = 72;
544 // 0xB2: 16 tiles
545 object_to_routine_map_[0xB2] = 16; // 4x4
546 // 0xB3-0xB4: 3 tiles each
547 object_to_routine_map_[0xB3] = 22; // edge 1x1
548 object_to_routine_map_[0xB4] = 22;
549 // 0xB5: Weird2x4_1to16
550 object_to_routine_map_[0xB5] = 8; // 4x2
551 // 0xB6-0xB7: RoomDraw_Rightwards2x4_1to15or26
552 object_to_routine_map_[0xB6] = 1; // Rightwards2x4_1to15or26
553 object_to_routine_map_[0xB7] = 1;
554 // 0xB8-0xB9: RoomDraw_Rightwards2x2_1to15or32
555 object_to_routine_map_[0xB8] = 0; // Rightwards2x2_1to15or32
556 object_to_routine_map_[0xB9] = 0;
557 // 0xBA: 16 tiles
558 object_to_routine_map_[0xBA] = 16; // 4x4
559 // 0xBB: RoomDraw_RightwardsBlock2x2spaced2_1to16
560 object_to_routine_map_[0xBB] = 55; // RightwardsBlock2x2spaced2_1to16
561 // 0xBC: RoomDraw_RightwardsPots2x2_1to16 (4 tiles = 2x2 interactive)
562 object_to_routine_map_[0xBC] = 73; // RightwardsPots2x2_1to16
563 // 0xBD: RoomDraw_RightwardsHammerPegs2x2_1to16 (4 tiles = 2x2 interactive)
564 object_to_routine_map_[0xBD] = 74; // RightwardsHammerPegs2x2_1to16
565 // 0xBE-0xBF: 1 tile each
566 object_to_routine_map_[0xBE] = 38; // Nothing (Logic: RoomDraw_Nothing_B)
567 object_to_routine_map_[0xBF] = 38;
568
569 // 0xC0-0xCF Objects (Phase 4: SuperSquare mappings)
570 // 0xC0: RoomDraw_4x4BlocksIn4x4SuperSquare
571 object_to_routine_map_[0xC0] = 56; // 4x4BlocksIn4x4SuperSquare
572 // 0xC1: ClosedChestPlatform (68 tiles)
573 object_to_routine_map_[0xC1] = 79; // ClosedChestPlatform
574 // 0xC2: RoomDraw_4x4BlocksIn4x4SuperSquare
575 object_to_routine_map_[0xC2] = 56; // 4x4BlocksIn4x4SuperSquare
576 // 0xC3: RoomDraw_3x3FloorIn4x4SuperSquare
577 object_to_routine_map_[0xC3] = 57; // 3x3FloorIn4x4SuperSquare
578 // 0xC4: RoomDraw_4x4FloorOneIn4x4SuperSquare
579 object_to_routine_map_[0xC4] = 59; // 4x4FloorOneIn4x4SuperSquare
580 // 0xC5-0xCA: RoomDraw_4x4FloorIn4x4SuperSquare
581 for (int id = 0xC5; id <= 0xCA; id++) {
582 object_to_routine_map_[id] = 58; // 4x4FloorIn4x4SuperSquare
583 }
584 // 0xCB-0xCC: Nothing (RoomDraw_Nothing_E)
585 object_to_routine_map_[0xCB] = 38;
586 object_to_routine_map_[0xCC] = 38;
587 // 0xCD-0xCE: Moving Walls
588 object_to_routine_map_[0xCD] = 80; // MovingWallWest
589 object_to_routine_map_[0xCE] = 81; // MovingWallEast
590 // 0xCF: Nothing (RoomDraw_Nothing_D)
591 object_to_routine_map_[0xCF] = 38;
592
593 // 0xD0-0xDF Objects (Phase 4: SuperSquare mappings)
594 // 0xD0: Nothing (RoomDraw_Nothing_D)
595 object_to_routine_map_[0xD0] = 38;
596 // 0xD1-0xD2: RoomDraw_4x4FloorIn4x4SuperSquare
597 object_to_routine_map_[0xD1] = 58; // 4x4FloorIn4x4SuperSquare
598 object_to_routine_map_[0xD2] = 58;
599 // 0xD3-0xD6: CheckIfWallIsMoved (Logic-only, no tiles)
600 object_to_routine_map_[0xD3] = 38; // Nothing
601 object_to_routine_map_[0xD4] = 38;
602 object_to_routine_map_[0xD5] = 38;
603 object_to_routine_map_[0xD6] = 38;
604 // 0xD7: RoomDraw_3x3FloorIn4x4SuperSquare
605 object_to_routine_map_[0xD7] = 57; // 3x3FloorIn4x4SuperSquare
606 // 0xD8: RoomDraw_WaterOverlayA8x8_1to16
607 object_to_routine_map_[0xD8] = 64; // WaterOverlay8x8_1to16
608 // 0xD9: RoomDraw_4x4FloorIn4x4SuperSquare
609 object_to_routine_map_[0xD9] = 58; // 4x4FloorIn4x4SuperSquare
610 // 0xDA: RoomDraw_WaterOverlayB8x8_1to16
611 object_to_routine_map_[0xDA] = 64; // WaterOverlay8x8_1to16
612 // 0xDB: RoomDraw_4x4FloorTwoIn4x4SuperSquare
613 object_to_routine_map_[0xDB] = 60; // 4x4FloorTwoIn4x4SuperSquare
614 // 0xDC: OpenChestPlatform (21 tiles)
615 object_to_routine_map_[0xDC] = 82; // OpenChestPlatform
616 // 0xDD: RoomDraw_TableRock4x4_1to16
617 object_to_routine_map_[0xDD] = 63; // TableRock4x4_1to16
618 // 0xDE: RoomDraw_Spike2x2In4x4SuperSquare
619 object_to_routine_map_[0xDE] = 62; // Spike2x2In4x4SuperSquare
620 // 0xDF: RoomDraw_4x4FloorIn4x4SuperSquare
621 object_to_routine_map_[0xDF] = 58; // 4x4FloorIn4x4SuperSquare
622
623 // 0xE0-0xEF Objects (Phase 4: SuperSquare mappings)
624 // 0xE0-0xE8: RoomDraw_4x4FloorIn4x4SuperSquare
625 for (int id = 0xE0; id <= 0xE8; id++) {
626 object_to_routine_map_[id] = 58; // 4x4FloorIn4x4SuperSquare
627 }
628 // 0xE9-0xEF: 1 tile each - Nothing (RoomDraw_Nothing_B)
629 for (int id = 0xE9; id <= 0xEF; id++) {
630 object_to_routine_map_[id] = 38;
631 }
632
633 // 0xF0-0xF7 Objects (all 1 tile) - Nothing (RoomDraw_Nothing_B)
634 for (int id = 0xF0; id <= 0xF7; id++) {
635 object_to_routine_map_[id] = 38;
636 }
637 // 0xF9-0xFD: Type 1 chests (small/big/map/compass)
638 for (int id = 0xF9; id <= 0xFD; id++) {
639 object_to_routine_map_[id] = 39; // Chest draw routine
640 }
641
642 // Subtype 2 Object Mappings (0x100-0x13F)
643 // ASM Reference: bank_01.asm .type1_subtype_2_routine ($018470)
644 // 0x100-0x107: RoomDraw_4x4
645 for (int id = 0x100; id <= 0x107; id++) {
646 object_to_routine_map_[id] = 16; // Rightwards 4x4
647 }
648 for (int id = 0x108; id <= 0x10F; id++) {
649 object_to_routine_map_[id] = 35; // 4x4 Corner BothBG
650 }
651 for (int id = 0x110; id <= 0x113; id++) {
652 object_to_routine_map_[id] = 36; // Weird Corner Bottom
653 }
654 for (int id = 0x114; id <= 0x117; id++) {
655 object_to_routine_map_[id] = 37; // Weird Corner Top
656 }
657 // 0x118-0x11B Rightwards 2x2
658 for (int id = 0x118; id <= 0x11B; id++) {
659 object_to_routine_map_[id] = 4; // Rightwards 2x2
660 }
661 // 0x11C 4x4
662 object_to_routine_map_[0x11C] = 16;
663 // 0x11D Single 2x3 Pillar -> Map to 2x3 Statue (28)
664 object_to_routine_map_[0x11D] = 28;
665 // 0x11E Single 2x2 -> Map to 2x2 (4)
666 object_to_routine_map_[0x11E] = 4;
667 // 0x11F Star Switch -> Map to 1x1 (25)
668 object_to_routine_map_[0x11F] = 25;
669 // 0x120 Torch -> Map to 1x1 (25)
670 object_to_routine_map_[0x120] = 25;
671 // 0x121 Single 2x3 Pillar -> Map to 2x3 Statue (28)
672 object_to_routine_map_[0x121] = 28;
673 // 0x122 Bed 4x5 -> Use dedicated Bed4x5 routine
674 object_to_routine_map_[0x122] = 98;
675 // 0x123 Table 4x3 -> Map to 4x3 (30)
676 object_to_routine_map_[0x123] = 30;
677 // 0x124-0x125 4x4
678 object_to_routine_map_[0x124] = 16;
679 object_to_routine_map_[0x125] = 16;
680 // 0x126 Single 2x3 -> Map to 2x3 (28)
681 object_to_routine_map_[0x126] = 28;
682 // 0x127 Rightwards 2x2 -> Map to 2x2 (4)
683 object_to_routine_map_[0x127] = 4;
684 // 0x128 Bed 4x5 -> Use dedicated Bed4x5 routine
685 object_to_routine_map_[0x128] = 98;
686 // 0x129 4x4
687 object_to_routine_map_[0x129] = 16;
688 // 0x12A Mario -> Map to 2x2 (4)
689 object_to_routine_map_[0x12A] = 4;
690 // 0x12B Rightwards 2x2 -> Map to 2x2 (4)
691 object_to_routine_map_[0x12B] = 4;
692 // 0x12C DrawRightwards3x6 -> Use dedicated 3x6 routine
693 object_to_routine_map_[0x12C] = 99;
694 // 0x12D-0x12F InterRoom Fat Stairs (ASM: $01A41B, $01A458, $01A486)
695 object_to_routine_map_[0x12D] = 83; // InterRoomFatStairsUp
696 object_to_routine_map_[0x12E] = 84; // InterRoomFatStairsDownA
697 object_to_routine_map_[0x12F] = 85; // InterRoomFatStairsDownB
698 // 0x130-0x133 Auto Stairs (ASM: RoomDraw_AutoStairs*)
699 for (int id = 0x130; id <= 0x133; id++) {
700 object_to_routine_map_[id] = 86; // AutoStairs
701 }
702 // 0x134 Rightwards 2x2 -> Map to 2x2 (4)
703 object_to_routine_map_[0x134] = 4;
704 // 0x135-0x136 Water Hop Stairs -> Map to 4x4 (16)
705 object_to_routine_map_[0x135] = 16;
706 object_to_routine_map_[0x136] = 16;
707 // 0x137 Dam Flood Gate -> Map to 4x4 (16)
708 object_to_routine_map_[0x137] = 16;
709 // 0x138-0x13B Spiral Stairs (ASM: RoomDraw_SpiralStairs*)
710 object_to_routine_map_[0x138] = 88; // SpiralStairsGoingUpUpper
711 object_to_routine_map_[0x139] = 89; // SpiralStairsGoingDownUpper
712 object_to_routine_map_[0x13A] = 90; // SpiralStairsGoingUpLower
713 object_to_routine_map_[0x13B] = 91; // SpiralStairsGoingDownLower
714 // 0x13C Sanctuary Wall -> Map to 4x4 (16)
715 object_to_routine_map_[0x13C] = 16;
716 // 0x13D Table 4x3 -> Map to 4x3 (30)
717 object_to_routine_map_[0x13D] = 30;
718 // 0x13E Utility 6x3 -> Use dedicated 6x3 routine
719 object_to_routine_map_[0x13E] = 100;
720 // 0x13F Magic Bat Altar -> Map to 4x4 (16)
721 object_to_routine_map_[0x13F] = 16;
722
723 // NOTE: Type 2 objects only range from 0x100-0x13F (64 objects)
724 // Objects 0x140-0x1FF do NOT exist in ROM - removed fake mappings
725
726 // Subtype 3 Object Mappings (0xF80-0xFFF)
727 // Type 3 object IDs are 0xF80-0xFFF (128 objects)
728 // Index = (object_id - 0xF80) & 0x7F maps to table indices 0-127
729 // Water Face variants (ASM: 0x200-0x202)
730 object_to_routine_map_[0xF80] = 94; // EmptyWaterFace
731 object_to_routine_map_[0xF81] = 95; // SpittingWaterFace
732 object_to_routine_map_[0xF82] = 96; // DrenchingWaterFace
733 // Somaria Line (0x203-0x20C)
734 for (int id = 0xF83; id <= 0xF89; id++) {
735 object_to_routine_map_[id] = 33; // Somaria Line
736 }
737 for (int id = 0xF8A; id <= 0xF8C; id++) {
738 object_to_routine_map_[id] = 33;
739 }
740 // 0xF8D (0x20D): PrisonCell - dual-BG drawing with horizontal flip symmetry
741 object_to_routine_map_[0xF8D] = 97; // Prison cell routine
742 object_to_routine_map_[0xF8E] = 33;
743 object_to_routine_map_[0xF8F] = 33;
744 // 0xF90-0xF93 (ASM 210-213): SINGLE 2x2 per bank_01.asm
745 // Note: 0xF92 (ASM 212) is RoomDraw_RupeeFloor - special 6x8 pattern
746 object_to_routine_map_[0xF90] = 110; // Single2x2
747 object_to_routine_map_[0xF91] = 110; // Single2x2
748 object_to_routine_map_[0xF92] = 115; // RupeeFloor (special 6x8 pattern)
749 object_to_routine_map_[0xF93] = 110; // Single2x2
750 // 0xF94 (ASM 214): RoomDraw_TableRock4x3
751 object_to_routine_map_[0xF94] = 30; // TableRock4x3 (Decor 4x3)
752 // 0xF95 (ASM 215): RoomDraw_KholdstareShell - boss shell routine
753 object_to_routine_map_[0xF95] = 106;
754 // 0xF96 (ASM 216): RoomDraw_HammerPegSingle - single peg, use 1x1
755 object_to_routine_map_[0xF96] = 25;
756 // 0xF97 (0x217): PrisonCell variant - dual-BG drawing
757 object_to_routine_map_[0xF97] = 97; // Prison cell routine
758 // 0xF98 (ASM 218): RoomDraw_BigKeyLock - uses dedicated routine
759 object_to_routine_map_[0xF98] = 92; // BigKeyLock
760 // 0xF99 (ASM 219): RoomDraw_Chest
761 object_to_routine_map_[0xF99] = 39; // DrawChest
762 // 0xF9A (ASM 21A): RoomDraw_OpenChest
763 object_to_routine_map_[0xF9A] = 39; // DrawChest (OpenChest variant)
764 // 0xF9B-0xF9D (ASM 21B-21D): AutoStairsSouthMultiLayer A/B/C
765 // These are stair objects that use 4x4 base patterns
766 for (int id = 0xF9B; id <= 0xF9D; id++) {
767 object_to_routine_map_[id] = 86; // AutoStairs (shared routine)
768 }
769 // 0xF9E-0xFA1 (ASM 21E-221): StraightInterroomStairs Upper variants
770 // These are stair objects that use the straight interroom stairs routine
771 for (int id = 0xF9E; id <= 0xFA1; id++) {
772 object_to_routine_map_[id] = 87; // StraightInterroomStairs
773 }
774 // 0xFA2-0xFA5 (ASM 222-225): Rightwards2x2 - SINGLE 2x2 (no repetition)
775 for (int id = 0xFA2; id <= 0xFA5; id++) {
776 object_to_routine_map_[id] = 110; // Single2x2
777 }
778 // 0xFA6-0xFA9 (ASM 226-229): StraightInterroomStairs Lower variants
779 for (int id = 0xFA6; id <= 0xFA9; id++) {
780 object_to_routine_map_[id] = 87; // StraightInterroomStairs
781 }
782 // 0xFAA (22A): RoomDraw_LampCones - use 4x4 base
783 object_to_routine_map_[0xFAA] = 16;
784 // 0xFAB (22B): RoomDraw_WeirdGloveRequiredPot - SINGLE 2x2 pot (no repetition)
785 object_to_routine_map_[0xFAB] = 110;
786 // 0xFAC (22C): RoomDraw_BigGrayRock - SINGLE 2x2 rock (no repetition)
787 object_to_routine_map_[0xFAC] = 110;
788 // 0xFAD (22D): RoomDraw_AgahnimsAltar - 4x4
789 object_to_routine_map_[0xFAD] = 16;
790 // 0xFAE (22E): RoomDraw_AgahnimsWindows - 4x4
791 object_to_routine_map_[0xFAE] = 16;
792 // 0xFAF (22F): RoomDraw_SinglePot - SINGLE 2x2 (no repetition)
793 object_to_routine_map_[0xFAF] = 110;
794 // 0xFB0 (230): RoomDraw_WeirdUglyPot - SINGLE 2x2 (no repetition)
795 object_to_routine_map_[0xFB0] = 110;
796 // 0xFB1 (231): RoomDraw_BigChest - SINGLE 4x3 (no repetition)
797 object_to_routine_map_[0xFB1] = 114; // Single4x3
798 // 0xFB2 (232): RoomDraw_OpenBigChest - SINGLE 4x3 (no repetition)
799 object_to_routine_map_[0xFB2] = 114; // Single4x3
800 // 0xFB3 (233): RoomDraw_AutoStairsSouthMergedLayer - shared AutoStairs routine
801 object_to_routine_map_[0xFB3] = 86;
802 // 0xFB4-0xFB9 (234-239): ChestPlatformVerticalWall and DrawRightwards3x6 variants
803 for (int id = 0xFB4; id <= 0xFB9; id++) {
804 object_to_routine_map_[id] = 16; // Use 4x4 as fallback
805 }
806 // 0xFBA-0xFBB (23A-23B): VerticalTurtleRockPipe
807 object_to_routine_map_[0xFBA] = 102; // Vertical Turtle Rock pipe
808 object_to_routine_map_[0xFBB] = 102;
809 // 0xFBC-0xFBD (23C-23D): HorizontalTurtleRockPipe
810 object_to_routine_map_[0xFBC] = 103; // Horizontal Turtle Rock pipe
811 object_to_routine_map_[0xFBD] = 103;
812 // 0xFBE-0xFC6 (23E-246): Various SINGLE 2x2 objects (no repetition)
813 for (int id = 0xFBE; id <= 0xFC6; id++) {
814 object_to_routine_map_[id] = 110; // Single2x2
815 }
816 // 0xFC7 (247): RoomDraw_BombableFloor
817 object_to_routine_map_[0xFC7] = 93; // BombableFloor
818 // 0xFC8 (248): RoomDraw_4x4
819 object_to_routine_map_[0xFC8] = 16;
820 // 0xFC9-0xFCA (249-24A): SINGLE 2x2 (no repetition)
821 object_to_routine_map_[0xFC9] = 110;
822 object_to_routine_map_[0xFCA] = 110;
823 // 0xFCB (24B): RoomDraw_BigWallDecor - 4x4
824 object_to_routine_map_[0xFCB] = 16;
825 // 0xFCC (24C): RoomDraw_SmithyFurnace - 4x4
826 object_to_routine_map_[0xFCC] = 16;
827 // 0xFCD (24D): RoomDraw_Utility6x3 - use dedicated 6x3 routine
828 object_to_routine_map_[0xFCD] = 100;
829 // 0xFCE (24E): RoomDraw_TableRock4x3
830 object_to_routine_map_[0xFCE] = 30;
831 // 0xFCF-0xFD3 (24F-253): SINGLE 2x2 (no repetition)
832 for (int id = 0xFCF; id <= 0xFD3; id++) {
833 object_to_routine_map_[id] = 110;
834 }
835 // 0xFD4 (254): RoomDraw_FortuneTellerRoom - complex, use 4x4
836 object_to_routine_map_[0xFD4] = 16;
837 // 0xFD5 (255): RoomDraw_Utility3x5 - use dedicated 3x5 routine
838 object_to_routine_map_[0xFD5] = 101;
839 // 0xFD6-0xFDA (256-25A): Various SINGLE 2x2 (no repetition)
840 for (int id = 0xFD6; id <= 0xFDA; id++) {
841 object_to_routine_map_[id] = 110;
842 }
843 // 0xFDB (25B): RoomDraw_Utility3x5 - use dedicated 3x5 routine
844 object_to_routine_map_[0xFDB] = 101;
845 // 0xFDC (25C): HorizontalTurtleRockPipe
846 object_to_routine_map_[0xFDC] = 103;
847 // 0xFDD (25D): RoomDraw_Utility6x3 - use dedicated 6x3 routine
848 object_to_routine_map_[0xFDD] = 100;
849 // 0xFDE-0xFDF (25E-25F): SINGLE 2x2 (no repetition)
850 object_to_routine_map_[0xFDE] = 110;
851 object_to_routine_map_[0xFDF] = 110;
852 // 0xFE0-0xFE1 (260-261): ArcheryGameTargetDoor - 3x6 door pattern
853 object_to_routine_map_[0xFE0] = 108;
854 object_to_routine_map_[0xFE1] = 108;
855 // 0xFE2 (262): VitreousGooGraphics - 4x4
856 object_to_routine_map_[0xFE2] = 16;
857 // 0xFE3-0xFE5 (263-265): SINGLE 2x2 (no repetition)
858 for (int id = 0xFE3; id <= 0xFE5; id++) {
859 object_to_routine_map_[id] = 110;
860 }
861 // 0xFE6 (266): RoomDraw_4x4 - actual 4x4 tile8 pattern (32x32 pixels)
862 object_to_routine_map_[0xFE6] = 116; // DrawActual4x4
863 // 0xFE7-0xFE8 (267-268): TableRock4x3
864 object_to_routine_map_[0xFE7] = 30;
865 object_to_routine_map_[0xFE8] = 30;
866 // 0xFE9-0xFEA (269-26A): SolidWallDecor3x4
867 object_to_routine_map_[0xFE9] = 107;
868 object_to_routine_map_[0xFEA] = 107;
869 // 0xFEB (26B): RoomDraw_4x4 - SINGLE 4x4 (no repetition)
870 object_to_routine_map_[0xFEB] = 113; // Single4x4
871 // 0xFEC-0xFED (26C-26D): RoomDraw_TableRock4x3 - SINGLE 4x3 (no repetition)
872 object_to_routine_map_[0xFEC] = 114; // Single4x3
873 object_to_routine_map_[0xFED] = 114; // Single4x3
874 // 0xFEE-0xFEF (26E-26F): SolidWallDecor3x4
875 object_to_routine_map_[0xFEE] = 107;
876 object_to_routine_map_[0xFEF] = 107;
877 // 0xFF0 (270): LightBeamOnFloor - dedicated light beam routine
878 object_to_routine_map_[0xFF0] = 104;
879 // 0xFF1 (271): BigLightBeamOnFloor - dedicated big light beam routine
880 object_to_routine_map_[0xFF1] = 105;
881 // 0xFF2 (272): TrinexxShell - boss shell routine
882 object_to_routine_map_[0xFF2] = 106;
883 // 0xFF3 (273): BG2MaskFull - Nothing
884 object_to_routine_map_[0xFF3] = 38;
885 // 0xFF4 (274): FloorLight - 4x4
886 object_to_routine_map_[0xFF4] = 16;
887 // 0xFF5 (275): SINGLE 2x2 (no repetition)
888 object_to_routine_map_[0xFF5] = 110;
889 // 0xFF6-0xFF7 (276-277): BigWallDecor - 4x4
890 object_to_routine_map_[0xFF6] = 16;
891 object_to_routine_map_[0xFF7] = 16;
892 // 0xFF8 (278): GanonTriforceFloorDecor - 4x8 Triforce floor pattern
893 object_to_routine_map_[0xFF8] = 109;
894 // 0xFF9 (279): TableRock4x3
895 object_to_routine_map_[0xFF9] = 30;
896 // 0xFFA (27A): RoomDraw_4x4
897 object_to_routine_map_[0xFFA] = 16;
898 // 0xFFB (27B): VitreousGooDamage - boss shell routine
899 object_to_routine_map_[0xFFB] = 106;
900 // 0xFFC-0xFFE (27C-27E): SINGLE 2x2 (no repetition)
901 for (int id = 0xFFC; id <= 0xFFE; id++) {
902 object_to_routine_map_[id] = 110;
903 }
904 // 0xFFF (27F): Nothing
905 object_to_routine_map_[0xFFF] = 38;
906
907 // Initialize draw routine function array in the correct order
908 // Routines 0-82 (existing), 80-98 (new special routines for stairs, locks, etc.)
909 draw_routines_.reserve(100);
910
911 // Routine 0
912 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
914 std::span<const gfx::TileInfo> tiles,
915 [[maybe_unused]] const DungeonState* state) {
916 self->DrawRightwards2x2_1to15or32(obj, bg, tiles);
917 });
918 // Routine 1
919 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
921 std::span<const gfx::TileInfo> tiles,
922 [[maybe_unused]] const DungeonState* state) {
923 self->DrawRightwards2x4_1to15or26(obj, bg, tiles);
924 });
925 // Routine 2 - 2x4 tiles with adjacent spacing (s * 2), count = size + 1
926 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
928 std::span<const gfx::TileInfo> tiles,
929 [[maybe_unused]] const DungeonState* state) {
930 self->DrawRightwards2x4_1to16(obj, bg, tiles);
931 });
932 // Routine 3 - Same as routine 2 but draws to both BG1 and BG2
933 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
935 std::span<const gfx::TileInfo> tiles,
936 [[maybe_unused]] const DungeonState* state) {
937 self->DrawRightwards2x4_1to16_BothBG(obj, bg, tiles);
938 });
939 // Routine 4
940 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
942 std::span<const gfx::TileInfo> tiles,
943 [[maybe_unused]] const DungeonState* state) {
944 self->DrawRightwards2x2_1to16(obj, bg, tiles);
945 });
946 // Routine 5
947 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
949 std::span<const gfx::TileInfo> tiles,
950 [[maybe_unused]] const DungeonState* state) {
951 self->DrawDiagonalAcute_1to16(obj, bg, tiles);
952 });
953 // Routine 6
954 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
956 std::span<const gfx::TileInfo> tiles,
957 [[maybe_unused]] const DungeonState* state) {
958 self->DrawDiagonalGrave_1to16(obj, bg, tiles);
959 });
960 // Routine 7
961 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
963 std::span<const gfx::TileInfo> tiles,
964 [[maybe_unused]] const DungeonState* state) {
965 self->DrawDownwards2x2_1to15or32(obj, bg, tiles);
966 });
967 // Routine 8
968 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
970 std::span<const gfx::TileInfo> tiles,
971 [[maybe_unused]] const DungeonState* state) {
972 self->DrawDownwards4x2_1to15or26(obj, bg, tiles);
973 });
974 // Routine 9
975 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
977 std::span<const gfx::TileInfo> tiles,
978 [[maybe_unused]] const DungeonState* state) {
979 self->DrawDownwards4x2_1to16_BothBG(obj, bg, tiles);
980 });
981 // Routine 10
982 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
984 std::span<const gfx::TileInfo> tiles,
985 [[maybe_unused]] const DungeonState* state) {
986 self->DrawDownwardsDecor4x2spaced4_1to16(obj, bg, tiles);
987 });
988 // Routine 11
989 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
991 std::span<const gfx::TileInfo> tiles,
992 [[maybe_unused]] const DungeonState* state) {
993 self->DrawDownwards2x2_1to16(obj, bg, tiles);
994 });
995 // Routine 12
996 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
998 std::span<const gfx::TileInfo> tiles,
999 [[maybe_unused]] const DungeonState* state) {
1000 self->DrawDownwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
1001 });
1002 // Routine 13
1003 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1005 std::span<const gfx::TileInfo> tiles,
1006 [[maybe_unused]] const DungeonState* state) {
1007 self->DrawDownwardsEdge1x1_1to16(obj, bg, tiles);
1008 });
1009 // Routine 14
1010 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1012 std::span<const gfx::TileInfo> tiles,
1013 [[maybe_unused]] const DungeonState* state) {
1014 self->DrawDownwardsLeftCorners2x1_1to16_plus12(obj, bg, tiles);
1015 });
1016 // Routine 15
1017 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1019 std::span<const gfx::TileInfo> tiles,
1020 [[maybe_unused]] const DungeonState* state) {
1021 self->DrawDownwardsRightCorners2x1_1to16_plus12(obj, bg, tiles);
1022 });
1023 // Routine 16
1024 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1026 std::span<const gfx::TileInfo> tiles,
1027 [[maybe_unused]] const DungeonState* state) {
1028 self->DrawRightwards4x4_1to16(obj, bg, tiles);
1029 });
1030 // Routine 17 - Diagonal Acute BothBG
1031 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1033 std::span<const gfx::TileInfo> tiles,
1034 [[maybe_unused]] const DungeonState* state) {
1035 self->DrawDiagonalAcute_1to16_BothBG(obj, bg, tiles);
1036 });
1037 // Routine 18 - Diagonal Grave BothBG
1038 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1040 std::span<const gfx::TileInfo> tiles,
1041 [[maybe_unused]] const DungeonState* state) {
1042 self->DrawDiagonalGrave_1to16_BothBG(obj, bg, tiles);
1043 });
1044 // Routine 19 - 4x4 Corner (Type 2 corners)
1045 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1047 std::span<const gfx::TileInfo> tiles,
1048 [[maybe_unused]] const DungeonState* state) {
1049 self->DrawCorner4x4(obj, bg, tiles);
1050 });
1051
1052 // Routine 20 - Edge objects 1x2 +2
1053 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1055 std::span<const gfx::TileInfo> tiles,
1056 [[maybe_unused]] const DungeonState* state) {
1057 self->DrawRightwards1x2_1to16_plus2(obj, bg, tiles);
1058 });
1059 // Routine 21 - Edge with perimeter 1x1 +3
1060 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1062 std::span<const gfx::TileInfo> tiles,
1063 [[maybe_unused]] const DungeonState* state) {
1064 self->DrawRightwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
1065 });
1066 // Routine 22 - Edge variant 1x1 +2
1067 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1069 std::span<const gfx::TileInfo> tiles,
1070 [[maybe_unused]] const DungeonState* state) {
1071 self->DrawRightwardsHasEdge1x1_1to16_plus2(obj, bg, tiles);
1072 });
1073 // Routine 23 - Top corners 1x2 +13
1074 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1076 std::span<const gfx::TileInfo> tiles,
1077 [[maybe_unused]] const DungeonState* state) {
1078 self->DrawRightwardsTopCorners1x2_1to16_plus13(obj, bg, tiles);
1079 });
1080 // Routine 24 - Bottom corners 1x2 +13
1081 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1083 std::span<const gfx::TileInfo> tiles,
1084 [[maybe_unused]] const DungeonState* state) {
1085 self->DrawRightwardsBottomCorners1x2_1to16_plus13(obj, bg, tiles);
1086 });
1087 // Routine 25 - Solid fill 1x1 +3 (floor patterns)
1088 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1090 std::span<const gfx::TileInfo> tiles,
1091 [[maybe_unused]] const DungeonState* state) {
1092 self->DrawRightwards1x1Solid_1to16_plus3(obj, bg, tiles);
1093 });
1094 // Routine 26 - Door switcherer
1095 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1097 std::span<const gfx::TileInfo> tiles,
1098 [[maybe_unused]] const DungeonState* state) {
1099 self->DrawDoorSwitcherer(obj, bg, tiles);
1100 });
1101 // Routine 27 - Decorations 4x4 spaced 2
1102 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1104 std::span<const gfx::TileInfo> tiles,
1105 [[maybe_unused]] const DungeonState* state) {
1106 self->DrawRightwardsDecor4x4spaced2_1to16(obj, bg, tiles);
1107 });
1108 // Routine 28 - Statues 2x3 spaced 2
1109 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1111 std::span<const gfx::TileInfo> tiles,
1112 [[maybe_unused]] const DungeonState* state) {
1113 self->DrawRightwardsStatue2x3spaced2_1to16(obj, bg, tiles);
1114 });
1115 // Routine 29 - Pillars 2x4 spaced 4
1116 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1118 std::span<const gfx::TileInfo> tiles,
1119 [[maybe_unused]] const DungeonState* state) {
1120 self->DrawRightwardsPillar2x4spaced4_1to16(obj, bg, tiles);
1121 });
1122 // Routine 30 - Decorations 4x3 spaced 4
1123 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1125 std::span<const gfx::TileInfo> tiles,
1126 [[maybe_unused]] const DungeonState* state) {
1127 self->DrawRightwardsDecor4x3spaced4_1to16(obj, bg, tiles);
1128 });
1129 // Routine 31 - Doubled 2x2 spaced 2
1130 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1132 std::span<const gfx::TileInfo> tiles,
1133 [[maybe_unused]] const DungeonState* state) {
1134 self->DrawRightwardsDoubled2x2spaced2_1to16(obj, bg, tiles);
1135 });
1136 // Routine 32 - Decorations 2x2 spaced 12
1137 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1139 std::span<const gfx::TileInfo> tiles,
1140 [[maybe_unused]] const DungeonState* state) {
1141 self->DrawRightwardsDecor2x2spaced12_1to16(obj, bg, tiles);
1142 });
1143 // Routine 33 - Somaria Line
1144 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1146 std::span<const gfx::TileInfo> tiles,
1147 [[maybe_unused]] const DungeonState* state) {
1148 self->DrawSomariaLine(obj, bg, tiles);
1149 });
1150 // Routine 34 - Water Face
1151 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1153 std::span<const gfx::TileInfo> tiles,
1154 [[maybe_unused]] const DungeonState* state) {
1155 self->DrawWaterFace(obj, bg, tiles);
1156 });
1157 // Routine 35 - 4x4 Corner BothBG
1158 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1160 std::span<const gfx::TileInfo> tiles,
1161 [[maybe_unused]] const DungeonState* state) {
1162 self->Draw4x4Corner_BothBG(obj, bg, tiles);
1163 });
1164 // Routine 36 - Weird Corner Bottom BothBG
1165 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1167 std::span<const gfx::TileInfo> tiles,
1168 [[maybe_unused]] const DungeonState* state) {
1169 self->DrawWeirdCornerBottom_BothBG(obj, bg, tiles);
1170 });
1171 // Routine 37 - Weird Corner Top BothBG
1172 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1174 std::span<const gfx::TileInfo> tiles,
1175 [[maybe_unused]] const DungeonState* state) {
1176 self->DrawWeirdCornerTop_BothBG(obj, bg, tiles);
1177 });
1178 // Routine 38 - Nothing
1179 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1181 std::span<const gfx::TileInfo> tiles,
1182 [[maybe_unused]] const DungeonState* state) {
1183 self->DrawNothing(obj, bg, tiles);
1184 });
1185 // Routine 39 - Chest rendering (small + big)
1186 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1188 std::span<const gfx::TileInfo> tiles,
1189 const DungeonState* state) {
1190 self->DrawChest(obj, bg, tiles, state);
1191 });
1192 // Routine 40 - Rightwards 4x2 (Floor Tile)
1193 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1195 std::span<const gfx::TileInfo> tiles,
1196 [[maybe_unused]] const DungeonState* state) {
1197 self->DrawRightwards4x2_1to16(obj, bg, tiles);
1198 });
1199 // Routine 41 - Rightwards Decor 4x2 spaced 8 (12-column spacing)
1200 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1202 std::span<const gfx::TileInfo> tiles,
1203 [[maybe_unused]] const DungeonState* state) {
1204 self->DrawRightwardsDecor4x2spaced8_1to16(obj, bg, tiles);
1205 });
1206 // Routine 42 - Rightwards Cannon Hole 4x3
1207 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1209 std::span<const gfx::TileInfo> tiles,
1210 [[maybe_unused]] const DungeonState* state) {
1211 self->DrawRightwardsCannonHole4x3_1to16(obj, bg, tiles);
1212 });
1213 // Routine 43 - Downwards Floor 4x4 (object 0x70)
1214 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1216 std::span<const gfx::TileInfo> tiles,
1217 [[maybe_unused]] const DungeonState* state) {
1218 self->DrawDownwardsFloor4x4_1to16(obj, bg, tiles);
1219 });
1220 // Routine 44 - Downwards 1x1 Solid +3 (object 0x71)
1221 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1223 std::span<const gfx::TileInfo> tiles,
1224 [[maybe_unused]] const DungeonState* state) {
1225 self->DrawDownwards1x1Solid_1to16_plus3(obj, bg, tiles);
1226 });
1227 // Routine 45 - Downwards Decor 4x4 spaced 2 (objects 0x73-0x74)
1228 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1230 std::span<const gfx::TileInfo> tiles,
1231 [[maybe_unused]] const DungeonState* state) {
1232 self->DrawDownwardsDecor4x4spaced2_1to16(obj, bg, tiles);
1233 });
1234 // Routine 46 - Downwards Pillar 2x4 spaced 2 (objects 0x75, 0x87)
1235 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1237 std::span<const gfx::TileInfo> tiles,
1238 [[maybe_unused]] const DungeonState* state) {
1239 self->DrawDownwardsPillar2x4spaced2_1to16(obj, bg, tiles);
1240 });
1241 // Routine 47 - Downwards Decor 3x4 spaced 4 (objects 0x76-0x77)
1242 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1244 std::span<const gfx::TileInfo> tiles,
1245 [[maybe_unused]] const DungeonState* state) {
1246 self->DrawDownwardsDecor3x4spaced4_1to16(obj, bg, tiles);
1247 });
1248 // Routine 48 - Downwards Decor 2x2 spaced 12 (objects 0x78, 0x7B)
1249 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1251 std::span<const gfx::TileInfo> tiles,
1252 [[maybe_unused]] const DungeonState* state) {
1253 self->DrawDownwardsDecor2x2spaced12_1to16(obj, bg, tiles);
1254 });
1255 // Routine 49 - Downwards Line 1x1 +1 (object 0x7C)
1256 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1258 std::span<const gfx::TileInfo> tiles,
1259 [[maybe_unused]] const DungeonState* state) {
1260 self->DrawDownwardsLine1x1_1to16plus1(obj, bg, tiles);
1261 });
1262 // Routine 50 - Downwards Decor 2x4 spaced 8 (objects 0x7F, 0x80)
1263 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1265 std::span<const gfx::TileInfo> tiles,
1266 [[maybe_unused]] const DungeonState* state) {
1267 self->DrawDownwardsDecor2x4spaced8_1to16(obj, bg, tiles);
1268 });
1269 // Routine 51 - Rightwards Line 1x1 +1 (object 0x50)
1270 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1272 std::span<const gfx::TileInfo> tiles,
1273 [[maybe_unused]] const DungeonState* state) {
1274 self->DrawRightwardsLine1x1_1to16plus1(obj, bg, tiles);
1275 });
1276 // Routine 52 - Rightwards Bar 4x3 (object 0x4C)
1277 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1279 std::span<const gfx::TileInfo> tiles,
1280 [[maybe_unused]] const DungeonState* state) {
1281 self->DrawRightwardsBar4x3_1to16(obj, bg, tiles);
1282 });
1283 // Routine 53 - Rightwards Shelf 4x4 (objects 0x4D-0x4F)
1284 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1286 std::span<const gfx::TileInfo> tiles,
1287 [[maybe_unused]] const DungeonState* state) {
1288 self->DrawRightwardsShelf4x4_1to16(obj, bg, tiles);
1289 });
1290 // Routine 54 - Rightwards Big Rail 1x3 +5 (object 0x5D)
1291 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1293 std::span<const gfx::TileInfo> tiles,
1294 [[maybe_unused]] const DungeonState* state) {
1295 self->DrawRightwardsBigRail1x3_1to16plus5(obj, bg, tiles);
1296 });
1297 // Routine 55 - Rightwards Block 2x2 spaced 2 (object 0x5E)
1298 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1300 std::span<const gfx::TileInfo> tiles,
1301 [[maybe_unused]] const DungeonState* state) {
1302 self->DrawRightwardsBlock2x2spaced2_1to16(obj, bg, tiles);
1303 });
1304
1305 // ============================================================================
1306 // Phase 4: SuperSquare Routines (routines 56-64)
1307 // ============================================================================
1308
1309 // Routine 56 - 4x4 Blocks in 4x4 SuperSquare (objects 0xC0, 0xC2)
1310 draw_routines_.push_back(
1311 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1312 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1313 [[maybe_unused]] const DungeonState* state) {
1314 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1316 });
1317
1318 // Routine 57 - 3x3 Floor in 4x4 SuperSquare (objects 0xC3, 0xD7)
1319 draw_routines_.push_back(
1320 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1321 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1322 [[maybe_unused]] const DungeonState* state) {
1323 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1325 });
1326
1327 // Routine 58 - 4x4 Floor in 4x4 SuperSquare (objects 0xC5-0xCA, 0xD1-0xD2,
1328 // 0xD9, 0xDF-0xE8)
1329 draw_routines_.push_back(
1330 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1331 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1332 [[maybe_unused]] const DungeonState* state) {
1333 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1335 });
1336
1337 // Routine 59 - 4x4 Floor One in 4x4 SuperSquare (object 0xC4)
1338 draw_routines_.push_back(
1339 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1340 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1341 [[maybe_unused]] const DungeonState* state) {
1342 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1344 });
1345
1346 // Routine 60 - 4x4 Floor Two in 4x4 SuperSquare (object 0xDB)
1347 draw_routines_.push_back(
1348 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1349 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1350 [[maybe_unused]] const DungeonState* state) {
1351 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1353 });
1354
1355 // Routine 61 - Big Hole 4x4 (object 0xA4)
1356 draw_routines_.push_back(
1357 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1358 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1359 [[maybe_unused]] const DungeonState* state) {
1360 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1362 });
1363
1364 // Routine 62 - Spike 2x2 in 4x4 SuperSquare (object 0xDE)
1365 draw_routines_.push_back(
1366 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1367 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1368 [[maybe_unused]] const DungeonState* state) {
1369 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1371 });
1372
1373 // Routine 63 - Table Rock 4x4 (object 0xDD)
1374 draw_routines_.push_back(
1375 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1376 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1377 [[maybe_unused]] const DungeonState* state) {
1378 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1380 });
1381
1382 // Routine 64 - Water Overlay 8x8 (objects 0xD8, 0xDA)
1383 draw_routines_.push_back(
1384 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1385 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1386 [[maybe_unused]] const DungeonState* state) {
1387 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1389 });
1390
1391 // ============================================================================
1392 // Phase 4 Step 2: Simple Variant Routines (routines 65-74)
1393 // ============================================================================
1394
1395 // Routine 65 - Downwards Decor 3x4 spaced 2 (objects 0x81-0x84)
1396 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1398 std::span<const gfx::TileInfo> tiles,
1399 [[maybe_unused]] const DungeonState* state) {
1400 self->DrawDownwardsDecor3x4spaced2_1to16(obj, bg, tiles);
1401 });
1402
1403 // Routine 66 - Downwards Big Rail 3x1 plus 5 (object 0x88)
1404 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1406 std::span<const gfx::TileInfo> tiles,
1407 [[maybe_unused]] const DungeonState* state) {
1408 self->DrawDownwardsBigRail3x1_1to16plus5(obj, bg, tiles);
1409 });
1410
1411 // Routine 67 - Downwards Block 2x2 spaced 2 (object 0x89)
1412 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1414 std::span<const gfx::TileInfo> tiles,
1415 [[maybe_unused]] const DungeonState* state) {
1416 self->DrawDownwardsBlock2x2spaced2_1to16(obj, bg, tiles);
1417 });
1418
1419 // Routine 68 - Downwards Cannon Hole 3x6 (objects 0x85-0x86)
1420 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1422 std::span<const gfx::TileInfo> tiles,
1423 [[maybe_unused]] const DungeonState* state) {
1424 self->DrawDownwardsCannonHole3x6_1to16(obj, bg, tiles);
1425 });
1426
1427 // Routine 69 - Downwards Bar 2x3 (object 0x8F)
1428 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1430 std::span<const gfx::TileInfo> tiles,
1431 [[maybe_unused]] const DungeonState* state) {
1432 self->DrawDownwardsBar2x3_1to16(obj, bg, tiles);
1433 });
1434
1435 // Routine 70 - Downwards Pots 2x2 (object 0x95)
1436 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1438 std::span<const gfx::TileInfo> tiles,
1439 [[maybe_unused]] const DungeonState* state) {
1440 self->DrawDownwardsPots2x2_1to16(obj, bg, tiles);
1441 });
1442
1443 // Routine 71 - Downwards Hammer Pegs 2x2 (object 0x96)
1444 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1446 std::span<const gfx::TileInfo> tiles,
1447 [[maybe_unused]] const DungeonState* state) {
1448 self->DrawDownwardsHammerPegs2x2_1to16(obj, bg, tiles);
1449 });
1450
1451 // Routine 72 - Rightwards Edge 1x1 plus 7 (objects 0xB0-0xB1)
1452 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1454 std::span<const gfx::TileInfo> tiles,
1455 [[maybe_unused]] const DungeonState* state) {
1456 self->DrawRightwardsEdge1x1_1to16plus7(obj, bg, tiles);
1457 });
1458
1459 // Routine 73 - Rightwards Pots 2x2 (object 0xBC)
1460 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1462 std::span<const gfx::TileInfo> tiles,
1463 [[maybe_unused]] const DungeonState* state) {
1464 self->DrawRightwardsPots2x2_1to16(obj, bg, tiles);
1465 });
1466
1467 // Routine 74 - Rightwards Hammer Pegs 2x2 (object 0xBD)
1468 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1470 std::span<const gfx::TileInfo> tiles,
1471 [[maybe_unused]] const DungeonState* state) {
1472 self->DrawRightwardsHammerPegs2x2_1to16(obj, bg, tiles);
1473 });
1474
1475 // ============================================================================
1476 // Phase 4 Step 3: Diagonal Ceiling Routines (routines 75-78)
1477 // ============================================================================
1478
1479 // Routine 75 - Diagonal Ceiling Top Left (objects 0xA0, 0xA5, 0xA9)
1480 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1482 std::span<const gfx::TileInfo> tiles,
1483 [[maybe_unused]] const DungeonState* state) {
1484 self->DrawDiagonalCeilingTopLeft(obj, bg, tiles);
1485 });
1486
1487 // Routine 76 - Diagonal Ceiling Bottom Left (objects 0xA1, 0xA6, 0xAA)
1488 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1490 std::span<const gfx::TileInfo> tiles,
1491 [[maybe_unused]] const DungeonState* state) {
1492 self->DrawDiagonalCeilingBottomLeft(obj, bg, tiles);
1493 });
1494
1495 // Routine 77 - Diagonal Ceiling Top Right (objects 0xA2, 0xA7, 0xAB)
1496 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1498 std::span<const gfx::TileInfo> tiles,
1499 [[maybe_unused]] const DungeonState* state) {
1500 self->DrawDiagonalCeilingTopRight(obj, bg, tiles);
1501 });
1502
1503 // Routine 78 - Diagonal Ceiling Bottom Right (objects 0xA3, 0xA8, 0xAC)
1504 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1506 std::span<const gfx::TileInfo> tiles,
1507 [[maybe_unused]] const DungeonState* state) {
1508 self->DrawDiagonalCeilingBottomRight(obj, bg, tiles);
1509 });
1510
1511 // ============================================================================
1512 // Phase 4 Step 5: Special Routines (routines 79-82)
1513 // ============================================================================
1514
1515 // Routine 79 - Closed Chest Platform (object 0xC1, 68 tiles)
1516 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1518 std::span<const gfx::TileInfo> tiles,
1519 [[maybe_unused]] const DungeonState* state) {
1520 self->DrawClosedChestPlatform(obj, bg, tiles);
1521 });
1522
1523 // Routine 80 - Moving Wall West (object 0xCD, 28 tiles)
1524 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1526 std::span<const gfx::TileInfo> tiles,
1527 [[maybe_unused]] const DungeonState* state) {
1528 self->DrawMovingWallWest(obj, bg, tiles);
1529 });
1530
1531 // Routine 81 - Moving Wall East (object 0xCE, 28 tiles)
1532 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1534 std::span<const gfx::TileInfo> tiles,
1535 [[maybe_unused]] const DungeonState* state) {
1536 self->DrawMovingWallEast(obj, bg, tiles);
1537 });
1538
1539 // Routine 82 - Open Chest Platform (object 0xDC, 21 tiles)
1540 draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj,
1542 std::span<const gfx::TileInfo> tiles,
1543 [[maybe_unused]] const DungeonState* state) {
1544 self->DrawOpenChestPlatform(obj, bg, tiles);
1545 });
1546
1547 // ============================================================================
1548 // New Special Routines (Phase 5) - Stairs, Locks, Interactive Objects
1549 // ============================================================================
1550
1551 // Routine 83 - InterRoom Fat Stairs Up (object 0x12D)
1552 draw_routines_.push_back(
1553 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1554 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1555 [[maybe_unused]] const DungeonState* state) {
1556 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1558 });
1559
1560 // Routine 84 - InterRoom Fat Stairs Down A (object 0x12E)
1561 draw_routines_.push_back(
1562 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1563 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1564 [[maybe_unused]] const DungeonState* state) {
1565 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1567 });
1568
1569 // Routine 85 - InterRoom Fat Stairs Down B (object 0x12F)
1570 draw_routines_.push_back(
1571 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1572 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1573 [[maybe_unused]] const DungeonState* state) {
1574 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1576 });
1577
1578 // Routine 86 - Auto Stairs (objects 0x130-0x133)
1579 draw_routines_.push_back(
1580 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1581 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1582 [[maybe_unused]] const DungeonState* state) {
1583 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1585 });
1586
1587 // Routine 87 - Straight InterRoom Stairs (Type 3 objects 0x21E-0x229)
1588 draw_routines_.push_back(
1589 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1590 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1591 [[maybe_unused]] const DungeonState* state) {
1592 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1594 });
1595
1596 // Routine 88 - Spiral Stairs Going Up Upper (object 0x138)
1597 draw_routines_.push_back(
1598 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1599 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1600 [[maybe_unused]] const DungeonState* state) {
1601 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1602 draw_routines::DrawSpiralStairs(ctx, true, true);
1603 });
1604
1605 // Routine 89 - Spiral Stairs Going Down Upper (object 0x139)
1606 draw_routines_.push_back(
1607 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1608 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1609 [[maybe_unused]] const DungeonState* state) {
1610 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1611 draw_routines::DrawSpiralStairs(ctx, false, true);
1612 });
1613
1614 // Routine 90 - Spiral Stairs Going Up Lower (object 0x13A)
1615 draw_routines_.push_back(
1616 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1617 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1618 [[maybe_unused]] const DungeonState* state) {
1619 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1620 draw_routines::DrawSpiralStairs(ctx, true, false);
1621 });
1622
1623 // Routine 91 - Spiral Stairs Going Down Lower (object 0x13B)
1624 draw_routines_.push_back(
1625 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1626 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1627 [[maybe_unused]] const DungeonState* state) {
1628 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1629 draw_routines::DrawSpiralStairs(ctx, false, false);
1630 });
1631
1632 // Routine 92 - Big Key Lock (Type 3 object 0x218)
1633 draw_routines_.push_back(
1634 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1635 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1636 [[maybe_unused]] const DungeonState* state) {
1637 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1639 });
1640
1641 // Routine 93 - Bombable Floor (Type 3 object 0x247)
1642 draw_routines_.push_back(
1643 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1644 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1645 [[maybe_unused]] const DungeonState* state) {
1646 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1648 });
1649
1650 // Routine 94 - Empty Water Face (Type 3 object 0x200)
1651 draw_routines_.push_back(
1652 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1653 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1654 [[maybe_unused]] const DungeonState* state) {
1655 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1657 });
1658
1659 // Routine 95 - Spitting Water Face (Type 3 object 0x201)
1660 draw_routines_.push_back(
1661 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1662 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1663 [[maybe_unused]] const DungeonState* state) {
1664 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr};
1666 });
1667
1668 // Routine 96 - Drenching Water Face (Type 3 object 0x202)
1669 draw_routines_.push_back(
1670 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1671 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1672 [[maybe_unused]] const DungeonState* state) {
1673 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr, nullptr};
1675 });
1676
1677 // Routine 97 - Prison Cell (Type 3 objects 0x20D, 0x217)
1678 // This routine draws to both BG1 and BG2 with horizontal flip symmetry
1679 // Note: secondary_bg is set in DrawObject() for dual-BG objects
1680 draw_routines_.push_back(
1681 []([[maybe_unused]] ObjectDrawer* self, const RoomObject& obj,
1682 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1683 [[maybe_unused]] const DungeonState* state) {
1684 DrawContext ctx{bg, obj, tiles, state, nullptr, 0, nullptr, nullptr};
1686 });
1687
1688 // Routine 98 - Bed 4x5 (Type 2 objects 0x122, 0x128)
1689 draw_routines_.push_back(
1690 [](ObjectDrawer* self, const RoomObject& obj,
1691 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1692 const DungeonState* state) {
1693 self->DrawBed4x5(obj, bg, tiles, state);
1694 });
1695
1696 // Routine 99 - Rightwards 3x6 (Type 2 object 0x12C, Type 3 0x236-0x237)
1697 draw_routines_.push_back(
1698 [](ObjectDrawer* self, const RoomObject& obj,
1699 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1700 const DungeonState* state) {
1701 self->DrawRightwards3x6(obj, bg, tiles, state);
1702 });
1703
1704 // Routine 100 - Utility 6x3 (Type 2 object 0x13E, Type 3 0x24D, 0x25D)
1705 draw_routines_.push_back(
1706 [](ObjectDrawer* self, const RoomObject& obj,
1707 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1708 const DungeonState* state) {
1709 self->DrawUtility6x3(obj, bg, tiles, state);
1710 });
1711
1712 // Routine 101 - Utility 3x5 (Type 3 objects 0x255, 0x25B)
1713 draw_routines_.push_back(
1714 [](ObjectDrawer* self, const RoomObject& obj,
1715 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1716 const DungeonState* state) {
1717 self->DrawUtility3x5(obj, bg, tiles, state);
1718 });
1719
1720 // Routine 102 - Vertical Turtle Rock Pipe (Type 3 objects 0x23A, 0x23B)
1721 draw_routines_.push_back(
1722 [](ObjectDrawer* self, const RoomObject& obj,
1723 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1724 const DungeonState* state) {
1725 self->DrawVerticalTurtleRockPipe(obj, bg, tiles, state);
1726 });
1727
1728 // Routine 103 - Horizontal Turtle Rock Pipe (Type 3 objects 0x23C, 0x23D, 0x25C)
1729 draw_routines_.push_back(
1730 [](ObjectDrawer* self, const RoomObject& obj,
1731 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1732 const DungeonState* state) {
1733 self->DrawHorizontalTurtleRockPipe(obj, bg, tiles, state);
1734 });
1735
1736 // Routine 104 - Light Beam on Floor (Type 3 object 0x270)
1737 draw_routines_.push_back(
1738 [](ObjectDrawer* self, const RoomObject& obj,
1739 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1740 const DungeonState* state) {
1741 self->DrawLightBeam(obj, bg, tiles, state);
1742 });
1743
1744 // Routine 105 - Big Light Beam on Floor (Type 3 object 0x271)
1745 draw_routines_.push_back(
1746 [](ObjectDrawer* self, const RoomObject& obj,
1747 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1748 const DungeonState* state) {
1749 self->DrawBigLightBeam(obj, bg, tiles, state);
1750 });
1751
1752 // Routine 106 - Boss Shell 4x4 (Type 3 objects 0x272, 0x27B, 0xF95)
1753 draw_routines_.push_back(
1754 [](ObjectDrawer* self, const RoomObject& obj,
1755 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1756 const DungeonState* state) {
1757 self->DrawBossShell4x4(obj, bg, tiles, state);
1758 });
1759
1760 // Routine 107 - Solid Wall Decor 3x4 (Type 3 objects 0x269-0x26A, 0x26E-0x26F)
1761 draw_routines_.push_back(
1762 [](ObjectDrawer* self, const RoomObject& obj,
1763 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1764 const DungeonState* state) {
1765 self->DrawSolidWallDecor3x4(obj, bg, tiles, state);
1766 });
1767
1768 // Routine 108 - Archery Game Target Door (Type 3 objects 0x260-0x261)
1769 draw_routines_.push_back(
1770 [](ObjectDrawer* self, const RoomObject& obj,
1771 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1772 const DungeonState* state) {
1773 self->DrawArcheryGameTargetDoor(obj, bg, tiles, state);
1774 });
1775
1776 // Routine 109 - Ganon Triforce Floor Decor (Type 3 object 0x278)
1777 draw_routines_.push_back(
1778 [](ObjectDrawer* self, const RoomObject& obj,
1779 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1780 const DungeonState* state) {
1781 self->DrawGanonTriforceFloorDecor(obj, bg, tiles, state);
1782 });
1783
1784 // Routine 110 - Single 2x2 (pots, statues, single-instance 2x2 objects)
1785 draw_routines_.push_back(
1786 [](ObjectDrawer* self, const RoomObject& obj,
1787 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1788 const DungeonState* state) {
1789 self->DrawSingle2x2(obj, bg, tiles, state);
1790 });
1791
1792 // Routine 111 - Waterfall47 (object 0x47)
1793 // ASM: RoomDraw_Waterfall47 - draws 1x5 columns, count = (size+1)*2
1794 draw_routines_.push_back(
1795 [](ObjectDrawer* self, const RoomObject& obj,
1796 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1797 [[maybe_unused]] const DungeonState* state) {
1798 int size = obj.size_ & 0x0F;
1799 int count = (size + 1) * 2; // ASM: ASL $B2
1800
1801 if (tiles.size() < 5) return;
1802
1803 // Draw first 1x5 column
1804 for (int row = 0; row < 5; row++) {
1805 self->WriteTile8(bg, obj.x_, obj.y_ + row, tiles[row]);
1806 }
1807
1808 // Draw middle columns
1809 for (int s = 0; s < count; s++) {
1810 int col_x = obj.x_ + 1 + s;
1811 for (int row = 0; row < 5; row++) {
1812 // Use tiles at offset 10 for middle columns
1813 size_t tile_idx = std::min(size_t(5 + row), tiles.size() - 1);
1814 self->WriteTile8(bg, col_x, obj.y_ + row, tiles[tile_idx]);
1815 }
1816 }
1817
1818 // Draw last 1x5 column
1819 int last_x = obj.x_ + 1 + count;
1820 for (int row = 0; row < 5; row++) {
1821 size_t tile_idx = std::min(size_t(10 + row), tiles.size() - 1);
1822 self->WriteTile8(bg, last_x, obj.y_ + row, tiles[tile_idx]);
1823 }
1824 });
1825
1826 // Routine 112 - Waterfall48 (object 0x48)
1827 // ASM: RoomDraw_Waterfall48 - draws 1x3 columns, count = (size+1)*2
1828 draw_routines_.push_back(
1829 [](ObjectDrawer* self, const RoomObject& obj,
1830 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1831 [[maybe_unused]] const DungeonState* state) {
1832 int size = obj.size_ & 0x0F;
1833 int count = (size + 1) * 2; // ASM: ASL $B2
1834
1835 if (tiles.size() < 3) return;
1836
1837 // Draw first 1x3 column
1838 for (int row = 0; row < 3; row++) {
1839 self->WriteTile8(bg, obj.x_, obj.y_ + row, tiles[row]);
1840 }
1841
1842 // Draw middle columns
1843 for (int s = 0; s < count; s++) {
1844 int col_x = obj.x_ + 1 + s;
1845 for (int row = 0; row < 3; row++) {
1846 self->WriteTile8(bg, col_x, obj.y_ + row, tiles[row]);
1847 }
1848 }
1849
1850 // Draw last 1x3 column using different tiles
1851 int last_x = obj.x_ + 1 + count;
1852 for (int row = 0; row < 3; row++) {
1853 size_t tile_idx = std::min(size_t(3 + row), tiles.size() - 1);
1854 self->WriteTile8(bg, last_x, obj.y_ + row, tiles[tile_idx]);
1855 }
1856 });
1857
1858 // Routine 113 - Single 4x4 (NO repetition)
1859 // ASM: RoomDraw_4x4 - draws a single 4x4 pattern (16 tiles)
1860 // Used for: 0xFEB (large decor), and other single 4x4 objects
1861 draw_routines_.push_back(
1862 [](ObjectDrawer* self, const RoomObject& obj,
1863 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1864 [[maybe_unused]] const DungeonState* state) {
1865 self->DrawSingle4x4(obj, bg, tiles, state);
1866 });
1867
1868 // Routine 114 - Single 4x3 (NO repetition)
1869 // ASM: RoomDraw_TableRock4x3 - draws a single 4x3 pattern (12 tiles)
1870 // Used for: 0xFED (water grate), 0xFB1 (big chest), etc.
1871 draw_routines_.push_back(
1872 [](ObjectDrawer* self, const RoomObject& obj,
1873 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1874 [[maybe_unused]] const DungeonState* state) {
1875 self->DrawSingle4x3(obj, bg, tiles, state);
1876 });
1877
1878 // Routine 115 - RupeeFloor (special pattern for 0xF92)
1879 // ASM: RoomDraw_RupeeFloor - draws 3 columns of 2-tile pairs at 3 Y positions
1880 // Pattern: 6 tiles wide, 8 rows tall with gaps (rows 2 and 5 are empty)
1881 draw_routines_.push_back(
1882 [](ObjectDrawer* self, const RoomObject& obj,
1883 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1884 [[maybe_unused]] const DungeonState* state) {
1885 self->DrawRupeeFloor(obj, bg, tiles, state);
1886 });
1887
1888 // Routine 116 - Actual 4x4 tile8 pattern (32x32 pixels, NO repetition)
1889 // ASM: RoomDraw_4x4 - draws exactly 4 columns x 4 rows = 16 tiles
1890 // Used for: 0xFE6 (pit)
1891 draw_routines_.push_back(
1892 [](ObjectDrawer* self, const RoomObject& obj,
1893 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1894 [[maybe_unused]] const DungeonState* state) {
1895 self->DrawActual4x4(obj, bg, tiles, state);
1896 });
1897
1898 // Fill gaps between 116 and 130 with Nothing routines
1899 // This ensures the routine vector is large enough for custom object ID 130
1900 while (draw_routines_.size() < 130) {
1901 draw_routines_.push_back(
1902 [](ObjectDrawer* self, const RoomObject& obj,
1903 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1904 [[maybe_unused]] const DungeonState* state) {
1905 self->DrawNothing(obj, bg, tiles, state);
1906 });
1907 }
1908
1909 // Routine 117 - Vertical rails with CORNER+MIDDLE+END pattern (0x8A-0x8C)
1910 // ASM: RoomDraw_DownwardsHasEdge1x1_1to16_plus23 - matches horizontal 0x22
1911 while (draw_routines_.size() < 117) {
1912 draw_routines_.push_back(
1913 [](ObjectDrawer* self, const RoomObject& obj,
1914 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1915 [[maybe_unused]] const DungeonState* state) {
1916 self->DrawNothing(obj, bg, tiles, state);
1917 });
1918 }
1919 draw_routines_.push_back(
1920 [](ObjectDrawer* self, const RoomObject& obj,
1921 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1922 [[maybe_unused]] const DungeonState* state) {
1923 self->DrawDownwardsHasEdge1x1_1to16_plus23(obj, bg, tiles, state);
1924 });
1925
1926 // Fill to routine 130
1927 while (draw_routines_.size() < 130) {
1928 draw_routines_.push_back(
1929 [](ObjectDrawer* self, const RoomObject& obj,
1930 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1931 [[maybe_unused]] const DungeonState* state) {
1932 self->DrawNothing(obj, bg, tiles, state);
1933 });
1934 }
1935
1936 // Routine 130 - Custom Object (Oracle of Secrets 0x31, 0x32)
1937 // Uses external binary files instead of ROM tile data.
1938 // Requires CustomObjectManager initialization and enable_custom_objects flag.
1939 draw_routines_.push_back(
1940 [](ObjectDrawer* self, const RoomObject& obj,
1941 gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles,
1942 [[maybe_unused]] const DungeonState* state) {
1943 self->DrawCustomObject(obj, bg, tiles, state);
1944 });
1945
1946 routines_initialized_ = true;
1947}
1948
1949int ObjectDrawer::GetDrawRoutineId(int16_t object_id) const {
1950 auto it = object_to_routine_map_.find(object_id);
1951 if (it != object_to_routine_map_.end()) {
1952 return it->second;
1953 }
1954
1955 // Default to simple 1x1 solid for unmapped objects
1956 return -1;
1957}
1958
1959// ============================================================================
1960// Draw Routine Implementations (Based on ZScream patterns)
1961// ============================================================================
1962
1963void ObjectDrawer::DrawDoor(const DoorDef& door, int door_index,
1966 [[maybe_unused]] const DungeonState* state) {
1967 // Door rendering based on ZELDA3_DUNGEON_SPEC.md Section 5 and disassembly
1968 // Uses DoorType and DoorDirection enums for type safety
1969 // Position calculations via DoorPositionManager
1970
1971 LOG_DEBUG("ObjectDrawer", "DrawDoor: idx=%d type=%d dir=%d pos=%d",
1972 door_index, static_cast<int>(door.type),
1973 static_cast<int>(door.direction), door.position);
1974
1975 if (!rom_ || !rom_->is_loaded() || !room_gfx_buffer_) {
1976 LOG_DEBUG("ObjectDrawer", "DrawDoor: SKIPPED - rom=%p loaded=%d gfx=%p",
1977 (void*)rom_, rom_ ? rom_->is_loaded() : 0, (void*)room_gfx_buffer_);
1978 return;
1979 }
1980
1981 auto& bitmap = bg1.bitmap();
1982 if (!bitmap.is_active() || bitmap.width() == 0) {
1983 LOG_DEBUG("ObjectDrawer", "DrawDoor: SKIPPED - bitmap not active or zero width");
1984 return;
1985 }
1986
1987 // Get door position from DoorPositionManager
1988 auto [tile_x, tile_y] = door.GetTileCoords();
1989 auto dims = door.GetDimensions();
1990 int door_width = dims.width_tiles;
1991 int door_height = dims.height_tiles;
1992
1993 LOG_DEBUG("ObjectDrawer", "DrawDoor: tile_pos=(%d,%d) dims=%dx%d",
1994 tile_x, tile_y, door_width, door_height);
1995
1996 // Door graphics use an indirect addressing scheme:
1997 // 1. kDoorGfxUp/Down/Left/Right point to offset tables (DoorGFXDataOffset_*)
1998 // 2. Each table entry is a 16-bit offset into RoomDrawObjectData
1999 // 3. RoomDrawObjectData base is at PC 0x1B52 (SNES $00:9B52)
2000 // 4. Actual tile data = 0x1B52 + offset_from_table
2001
2002 // Select offset table based on direction
2003 int offset_table_addr = 0;
2004 switch (door.direction) {
2005 case DoorDirection::North: offset_table_addr = kDoorGfxUp; break; // 0x4D9E
2006 case DoorDirection::South: offset_table_addr = kDoorGfxDown; break; // 0x4E06
2007 case DoorDirection::West: offset_table_addr = kDoorGfxLeft; break; // 0x4E66
2008 case DoorDirection::East: offset_table_addr = kDoorGfxRight; break; // 0x4EC6
2009 }
2010
2011 // Calculate door type index (door types step by 2: 0x00, 0x02, 0x04, ...)
2012 int type_value = static_cast<int>(door.type);
2013 int type_index = type_value / 2;
2014
2015 // Read offset from table (each entry is 2 bytes)
2016 int table_entry_addr = offset_table_addr + (type_index * 2);
2017 if (table_entry_addr + 1 >= static_cast<int>(rom_->size())) {
2018 DrawDoorIndicator(bitmap, tile_x, tile_y, door_width, door_height,
2019 door.type, door.direction);
2020 return;
2021 }
2022
2023 const auto& rom_data = rom_->data();
2024 uint16_t tile_offset = rom_data[table_entry_addr] |
2025 (rom_data[table_entry_addr + 1] << 8);
2026
2027 // RoomDrawObjectData base address (PC offset)
2028 constexpr int kRoomDrawObjectDataBase = 0x1B52;
2029 int tile_data_addr = kRoomDrawObjectDataBase + tile_offset;
2030
2031 LOG_DEBUG("ObjectDrawer",
2032 "DrawDoor: offset_table=0x%X type_idx=%d tile_offset=0x%X tile_addr=0x%X",
2033 offset_table_addr, type_index, tile_offset, tile_data_addr);
2034
2035 // Validate address range (12 tiles * 2 bytes = 24 bytes)
2036 int tiles_per_door = door_width * door_height; // 12 tiles (4x3 or 3x4)
2037 int data_size = tiles_per_door * 2;
2038 if (tile_data_addr < 0 || tile_data_addr + data_size > static_cast<int>(rom_->size())) {
2039 LOG_DEBUG("ObjectDrawer", "DrawDoor: INVALID ADDRESS - falling back to indicator");
2040 DrawDoorIndicator(bitmap, tile_x, tile_y, door_width, door_height,
2041 door.type, door.direction);
2042 return;
2043 }
2044
2045 // Read and render door tiles
2046 // All directions use column-major tile order (matching ASM draw routines)
2047 // The ROM stores tiles in column-major order for all door directions
2048 LOG_DEBUG("ObjectDrawer", "DrawDoor: Reading %d tiles from 0x%X",
2049 tiles_per_door, tile_data_addr);
2050 int tile_idx = 0;
2051 auto& priority_buffer = bg1.mutable_priority_data();
2052 int bitmap_width = bitmap.width();
2053
2054 for (int dx = 0; dx < door_width; dx++) {
2055 for (int dy = 0; dy < door_height; dy++) {
2056 int addr = tile_data_addr + (tile_idx * 2);
2057 uint16_t tile_word = rom_data[addr] | (rom_data[addr + 1] << 8);
2058
2059 auto tile_info = gfx::WordToTileInfo(tile_word);
2060 int pixel_x = (tile_x + dx) * 8;
2061 int pixel_y = (tile_y + dy) * 8;
2062
2063 if (tile_idx < 4) {
2064 LOG_DEBUG("ObjectDrawer", "DrawDoor: tile[%d] word=0x%04X id=%d pal=%d pixel=(%d,%d)",
2065 tile_idx, tile_word, tile_info.id_, tile_info.palette_, pixel_x, pixel_y);
2066 }
2067
2068 DrawTileToBitmap(bitmap, tile_info, pixel_x, pixel_y, room_gfx_buffer_);
2069
2070 // Update priority buffer for this tile
2071 uint8_t priority = tile_info.over_ ? 1 : 0;
2072 const auto& bitmap_data = bitmap.vector();
2073 for (int py = 0; py < 8; py++) {
2074 int dest_y = pixel_y + py;
2075 if (dest_y < 0 || dest_y >= bitmap.height()) continue;
2076 for (int px = 0; px < 8; px++) {
2077 int dest_x = pixel_x + px;
2078 if (dest_x < 0 || dest_x >= bitmap_width) continue;
2079 int dest_index = dest_y * bitmap_width + dest_x;
2080 if (dest_index < static_cast<int>(bitmap_data.size()) &&
2081 bitmap_data[dest_index] != 255) {
2082 priority_buffer[dest_index] = priority;
2083 }
2084 }
2085 }
2086
2087 tile_idx++;
2088 }
2089 }
2090
2091 LOG_DEBUG("ObjectDrawer",
2092 "DrawDoor: type=%s dir=%s pos=%d at tile(%d,%d) size=%dx%d "
2093 "offset_table=0x%X tile_offset=0x%X tile_addr=0x%X",
2094 std::string(GetDoorTypeName(door.type)).c_str(),
2095 std::string(GetDoorDirectionName(door.direction)).c_str(),
2096 door.position, tile_x, tile_y, door_width, door_height,
2097 offset_table_addr, tile_offset, tile_data_addr);
2098}
2099
2100void ObjectDrawer::DrawDoorIndicator(gfx::Bitmap& bitmap, int tile_x, int tile_y,
2101 int width, int height, DoorType type,
2102 DoorDirection direction) {
2103 // Draw a simple colored rectangle as door indicator when graphics unavailable
2104 // Different colors for different door types using DoorType enum
2105
2106 uint8_t color_idx;
2107 switch (type) {
2110 color_idx = 45; // Standard door color (brown)
2111 break;
2112
2118 color_idx = 60; // Key door - yellowish
2119 break;
2120
2123 color_idx = 58; // Big key - golden
2124 break;
2125
2129 case DoorType::DashWall:
2130 color_idx = 15; // Bombable/destructible - brownish/cracked
2131 break;
2132
2139 color_idx = 30; // Shutter - greenish
2140 break;
2141
2143 color_idx = 42; // Eye watch - lighter brown
2144 break;
2145
2147 color_idx = 35; // Curtain - special
2148 break;
2149
2150 case DoorType::CaveExit:
2155 color_idx = 25; // Cave/dungeon exit - dark
2156 break;
2157
2161 color_idx = 5; // Markers - very faint
2162 break;
2163
2164 default:
2165 color_idx = 50; // Default door color
2166 break;
2167 }
2168
2169 int pixel_x = tile_x * 8;
2170 int pixel_y = tile_y * 8;
2171 int pixel_width = width * 8;
2172 int pixel_height = height * 8;
2173
2174 int bitmap_width = bitmap.width();
2175 int bitmap_height = bitmap.height();
2176
2177 // Draw filled rectangle with border
2178 for (int py = 0; py < pixel_height; py++) {
2179 for (int px = 0; px < pixel_width; px++) {
2180 int dest_x = pixel_x + px;
2181 int dest_y = pixel_y + py;
2182
2183 if (dest_x >= 0 && dest_x < bitmap_width &&
2184 dest_y >= 0 && dest_y < bitmap_height) {
2185 // Draw border (2 pixel thick) or fill
2186 bool is_border = (px < 2 || px >= pixel_width - 2 ||
2187 py < 2 || py >= pixel_height - 2);
2188 uint8_t final_color = is_border ? (color_idx + 5) : color_idx;
2189
2190 int offset = (dest_y * bitmap_width) + dest_x;
2191 bitmap.WriteToPixel(offset, final_color);
2192 }
2193 }
2194 }
2195}
2196
2198 std::span<const gfx::TileInfo> tiles,
2199 [[maybe_unused]] const DungeonState* state) {
2200 // ASM: RoomDraw_Chest - draws a SINGLE chest (2x2) without size-based repetition
2201 // 0xF99 = closed chest, 0xF9A = open chest
2202 // The size byte is NOT used for repetition
2203
2204 // Determine if chest is open
2205 bool is_open = false;
2206 if (state) {
2207 is_open = state->IsChestOpen(room_id_, current_chest_index_);
2208 }
2209
2210 // Increment index for next chest
2212
2213 // Draw SINGLE chest - no repetition based on size
2214 // Standard chests are 2x2 (4 tiles)
2215 // If we have extra tiles loaded, the second 4 are for open state
2216
2217 if (is_open && tiles.size() >= 8) {
2218 // Small chest open tiles (indices 4-7) - SINGLE 2x2 draw
2219 if (tiles.size() >= 8) {
2220 WriteTile8(bg, obj.x_, obj.y_, tiles[4]); // top-left
2221 WriteTile8(bg, obj.x_, obj.y_ + 1, tiles[5]); // bottom-left
2222 WriteTile8(bg, obj.x_ + 1, obj.y_, tiles[6]); // top-right
2223 WriteTile8(bg, obj.x_ + 1, obj.y_ + 1, tiles[7]); // bottom-right
2224 }
2225 return;
2226 }
2227
2228 // Draw closed chest - SINGLE 2x2 pattern (column-major order)
2229 if (tiles.size() >= 4) {
2230 WriteTile8(bg, obj.x_, obj.y_, tiles[0]); // top-left
2231 WriteTile8(bg, obj.x_, obj.y_ + 1, tiles[1]); // bottom-left
2232 WriteTile8(bg, obj.x_ + 1, obj.y_, tiles[2]); // top-right
2233 WriteTile8(bg, obj.x_ + 1, obj.y_ + 1, tiles[3]); // bottom-right
2234 }
2235}
2236
2238 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2239 // Intentionally empty - represents invisible logic objects or placeholders
2240 // ASM: RoomDraw_Nothing_A ($0190F2), RoomDraw_Nothing_B ($01932E), etc.
2241 // These routines typically just RTS.
2242 LOG_DEBUG("ObjectDrawer", "DrawNothing for object 0x%02X (logic/invisible)", obj.id_);
2243}
2244
2246 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2247 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2248 // Pattern: Draws 2x2 tiles rightward (object 0x00 = ceiling, 0xB8-0xB9)
2249 // Size byte determines how many times to repeat (1-15 or 32)
2250 // ROM tile order is COLUMN-MAJOR: [col0_row0, col0_row1, col1_row0, col1_row1]
2251 int size = obj.size_;
2252 if (size == 0)
2253 size = 32; // Special case for object 0x00
2254
2255 // Debug: Log ceiling objects (0x00, 0xB8, 0xB9)
2256 bool is_ceiling = (obj.id_ == 0x00 || obj.id_ == 0xB8 || obj.id_ == 0xB9);
2257 if (is_ceiling && tiles.size() >= 4) {
2258 LOG_DEBUG("ObjectDrawer", "Ceiling Draw: obj=0x%02X pos=(%d,%d) size=%d tiles=%zu",
2259 obj.id_, obj.x_, obj.y_, size, tiles.size());
2260 LOG_DEBUG("ObjectDrawer", " Tile IDs: [%d, %d, %d, %d]",
2261 tiles[0].id_, tiles[1].id_, tiles[2].id_, tiles[3].id_);
2262 LOG_DEBUG("ObjectDrawer", " Palettes: [%d, %d, %d, %d]",
2263 tiles[0].palette_, tiles[1].palette_, tiles[2].palette_, tiles[3].palette_);
2264 }
2265
2266 LOG_DEBUG("ObjectDrawer",
2267 "DrawRightwards2x2: obj=%04X pos=(%d,%d) size=%d tiles=%zu",
2268 obj.id_, obj.x_, obj.y_, size, tiles.size());
2269
2270 for (int s = 0; s < size; s++) {
2271 if (tiles.size() >= 4) {
2272 // Draw 2x2 pattern in COLUMN-MAJOR order (matching assembly)
2273 // tiles[0] → $BF → (col 0, row 0) = top-left
2274 // tiles[1] → $CB → (col 0, row 1) = bottom-left
2275 // tiles[2] → $C2 → (col 1, row 0) = top-right
2276 // tiles[3] → $CE → (col 1, row 1) = bottom-right
2277 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // col 0, row 0
2278 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]); // col 0, row 1
2279 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[2]); // col 1, row 0
2280 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1,
2281 tiles[3]); // col 1, row 1
2282 } else {
2283 LOG_DEBUG("ObjectDrawer",
2284 "DrawRightwards2x2: SKIPPING - tiles.size()=%zu < 4",
2285 tiles.size());
2286 }
2287 }
2288}
2289
2291 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2292 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2293 // Pattern: Draws 2x4 tiles rightward (objects 0x01-0x02)
2294 // Uses RoomDraw_Nx4 with N=2, tiles are COLUMN-MAJOR:
2295 // [col0_row0, col0_row1, col0_row2, col0_row3, col1_row0, col1_row1, col1_row2, col1_row3]
2296 int size = obj.size_;
2297 if (size == 0)
2298 size = 26; // Special case
2299
2300 LOG_DEBUG("ObjectDrawer", "Wall Draw 2x4: obj=0x%03X pos=(%d,%d) size=%d tiles=%zu",
2301 obj.id_, obj.x_, obj.y_, size, tiles.size());
2302 LOG_DEBUG("ObjectDrawer",
2303 "DrawRightwards2x4: obj=%04X pos=(%d,%d) size=%d tiles=%zu",
2304 obj.id_, obj.x_, obj.y_, size, tiles.size());
2305
2306 for (int s = 0; s < size; s++) {
2307 if (tiles.size() >= 8) {
2308 // Draw 2x4 pattern in COLUMN-MAJOR order (matching RoomDraw_Nx4)
2309 // Column 0 (tiles 0-3)
2310 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // col 0, row 0
2311 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]); // col 0, row 1
2312 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[2]); // col 0, row 2
2313 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[3]); // col 0, row 3
2314 // Column 1 (tiles 4-7)
2315 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[4]); // col 1, row 0
2316 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[5]); // col 1, row 1
2317 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tiles[6]); // col 1, row 2
2318 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tiles[7]); // col 1, row 3
2319 } else if (tiles.size() >= 4) {
2320 // Fallback: with 4 tiles we can only draw 1 column (1x4 pattern)
2321 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]);
2322 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]);
2323 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[2]);
2324 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[3]);
2325 }
2326 }
2327}
2328
2330 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2331 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2332 // Pattern: Draws 2x4 tiles rightward with adjacent spacing (objects 0x03-0x04)
2333 // Uses RoomDraw_Nx4 with N=2, tiles are COLUMN-MAJOR
2334 // ASM: GetSize_1to16 means count = size + 1
2335 int size = obj.size_ & 0x0F;
2336 int count = size + 1;
2337
2338 LOG_DEBUG("ObjectDrawer",
2339 "DrawRightwards2x4_1to16: obj=%04X pos=(%d,%d) size=%d count=%d tiles=%zu",
2340 obj.id_, obj.x_, obj.y_, size, count, tiles.size());
2341
2342 for (int s = 0; s < count; s++) {
2343 if (tiles.size() >= 8) {
2344 // Draw 2x4 pattern in COLUMN-MAJOR order with adjacent spacing (s * 2)
2345 // Column 0 (tiles 0-3)
2346 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // col 0, row 0
2347 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]); // col 0, row 1
2348 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[2]); // col 0, row 2
2349 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[3]); // col 0, row 3
2350 // Column 1 (tiles 4-7)
2351 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[4]); // col 1, row 0
2352 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[5]); // col 1, row 1
2353 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tiles[6]); // col 1, row 2
2354 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tiles[7]); // col 1, row 3
2355 } else if (tiles.size() >= 4) {
2356 // Fallback: with 4 tiles we can only draw 1 column (1x4 pattern)
2357 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]);
2358 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]);
2359 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[2]);
2360 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[3]);
2361 }
2362 }
2363}
2364
2366 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2367 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2368 // Pattern: Same as DrawRightwards2x4_1to16 but draws to both BG1 and BG2 (objects 0x05-0x06)
2369 DrawRightwards2x4_1to16(obj, bg, tiles);
2370 // Note: BothBG would require access to both buffers - simplified for now
2371}
2372
2374 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2375 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2376 // Pattern: Draws 2x2 tiles rightward (objects 0x07-0x08)
2377 // ROM tile order is COLUMN-MAJOR: [col0_row0, col0_row1, col1_row0, col1_row1]
2378 int size = obj.size_ & 0x0F;
2379
2380 // Assembly: JSR RoomDraw_GetSize_1to16
2381 // GetSize_1to16: count = size + 1
2382 int count = size + 1;
2383
2384 for (int s = 0; s < count; s++) {
2385 if (tiles.size() >= 4) {
2386 // Draw 2x2 pattern in COLUMN-MAJOR order (matching assembly)
2387 WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // col 0, row 0
2388 WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[1]); // col 0, row 1
2389 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[2]); // col 1, row 0
2390 WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1,
2391 tiles[3]); // col 1, row 1
2392 }
2393 }
2394}
2395
2397 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2398 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2399 // Pattern: Diagonal acute (/) - draws 5 tiles vertically, moves up-right
2400 // Based on bank_01.asm RoomDraw_DiagonalAcute_1to16 at $018C58
2401 // Uses RoomDraw_2x2and1 to draw 5 tiles at rows 0-4, then moves Y -= $7E
2402 int size = obj.size_ & 0x0F;
2403
2404 // Assembly: LDA #$0007; JSR RoomDraw_GetSize_1to16_timesA
2405 // GetSize_1to16_timesA formula: ((B2 << 2) | B4) + A
2406 // Since size_ already contains ((B2 << 2) | B4), count = size + 7
2407 int count = size + 7;
2408
2409 if (tiles.size() < 5) return;
2410
2411 for (int s = 0; s < count; s++) {
2412 // Draw 5 tiles in a vertical column (RoomDraw_2x2and1 pattern)
2413 // Assembly stores to [$BF],Y, [$CB],Y, [$D7],Y, [$DA],Y, [$DD],Y
2414 // These are rows 0, 1, 2, 3, 4 at the same X position
2415 int tile_x = obj.x_ + s; // Move right each iteration
2416 int tile_y = obj.y_ - s; // Move up each iteration (acute = /)
2417
2418 WriteTile8(bg, tile_x, tile_y + 0, tiles[0]);
2419 WriteTile8(bg, tile_x, tile_y + 1, tiles[1]);
2420 WriteTile8(bg, tile_x, tile_y + 2, tiles[2]);
2421 WriteTile8(bg, tile_x, tile_y + 3, tiles[3]);
2422 WriteTile8(bg, tile_x, tile_y + 4, tiles[4]);
2423 }
2424}
2425
2427 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2428 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2429 // Pattern: Diagonal grave (\‍) - draws 5 tiles vertically, moves down-right
2430 // Based on bank_01.asm RoomDraw_DiagonalGrave_1to16 at $018C61
2431 // Uses RoomDraw_2x2and1 to draw 5 tiles at rows 0-4, then moves Y += $82
2432 int size = obj.size_ & 0x0F;
2433
2434 // Assembly: LDA #$0007; JSR RoomDraw_GetSize_1to16_timesA
2435 // GetSize_1to16_timesA formula: ((B2 << 2) | B4) + A
2436 // Since size_ already contains ((B2 << 2) | B4), count = size + 7
2437 int count = size + 7;
2438
2439 if (tiles.size() < 5) return;
2440
2441 for (int s = 0; s < count; s++) {
2442 // Draw 5 tiles in a vertical column (RoomDraw_2x2and1 pattern)
2443 // Assembly stores to [$BF],Y, [$CB],Y, [$D7],Y, [$DA],Y, [$DD],Y
2444 // These are rows 0, 1, 2, 3, 4 at the same X position
2445 int tile_x = obj.x_ + s; // Move right each iteration
2446 int tile_y = obj.y_ + s; // Move down each iteration (grave = \‍)
2447
2448 WriteTile8(bg, tile_x, tile_y + 0, tiles[0]);
2449 WriteTile8(bg, tile_x, tile_y + 1, tiles[1]);
2450 WriteTile8(bg, tile_x, tile_y + 2, tiles[2]);
2451 WriteTile8(bg, tile_x, tile_y + 3, tiles[3]);
2452 WriteTile8(bg, tile_x, tile_y + 4, tiles[4]);
2453 }
2454}
2455
2457 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2458 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2459 // Pattern: Diagonal acute (/) for both BG layers (objects 0x15, 0x18-0x1D, 0x20)
2460 // Based on bank_01.asm RoomDraw_DiagonalAcute_1to16_BothBG at $018C6A
2461 // Assembly: LDA #$0006; JSR RoomDraw_GetSize_1to16_timesA
2462 // GetSize_1to16_timesA formula: ((B2 << 2) | B4) + A
2463 // Since size_ already contains ((B2 << 2) | B4), count = size + 6
2464 int size = obj.size_ & 0x0F;
2465 int count = size + 6;
2466
2467 if (tiles.size() < 5) return;
2468
2469 for (int s = 0; s < count; s++) {
2470 int tile_x = obj.x_ + s;
2471 int tile_y = obj.y_ - s;
2472
2473 WriteTile8(bg, tile_x, tile_y + 0, tiles[0]);
2474 WriteTile8(bg, tile_x, tile_y + 1, tiles[1]);
2475 WriteTile8(bg, tile_x, tile_y + 2, tiles[2]);
2476 WriteTile8(bg, tile_x, tile_y + 3, tiles[3]);
2477 WriteTile8(bg, tile_x, tile_y + 4, tiles[4]);
2478 }
2479 // Note: BothBG should write to both BG1 and BG2 - handled by DrawObject caller
2480}
2481
2483 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2484 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2485 // Pattern: Diagonal grave (\‍) for both BG layers (objects 0x16-0x17, 0x1A-0x1B, 0x1E-0x1F)
2486 // Based on bank_01.asm RoomDraw_DiagonalGrave_1to16_BothBG at $018CB9
2487 // Assembly: LDA #$0006; JSR RoomDraw_GetSize_1to16_timesA
2488 // GetSize_1to16_timesA formula: ((B2 << 2) | B4) + A
2489 // Since size_ already contains ((B2 << 2) | B4), count = size + 6
2490 int size = obj.size_ & 0x0F;
2491 int count = size + 6;
2492
2493 if (tiles.size() < 5) return;
2494
2495 for (int s = 0; s < count; s++) {
2496 int tile_x = obj.x_ + s;
2497 int tile_y = obj.y_ + s;
2498
2499 WriteTile8(bg, tile_x, tile_y + 0, tiles[0]);
2500 WriteTile8(bg, tile_x, tile_y + 1, tiles[1]);
2501 WriteTile8(bg, tile_x, tile_y + 2, tiles[2]);
2502 WriteTile8(bg, tile_x, tile_y + 3, tiles[3]);
2503 WriteTile8(bg, tile_x, tile_y + 4, tiles[4]);
2504 }
2505 // Note: BothBG should write to both BG1 and BG2 - handled by DrawObject caller
2506}
2507
2510 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2511 // Pattern: 4x4 grid corner for Type 2 objects
2512 // LAYOUT CORNERS: 0x100-0x103 (upper-left, lower-left, upper-right, lower-right)
2513 // - These are the concave corners used in room layouts
2514 // - GetSubtype2TileCount returns 16 tiles for these (32 bytes of tile data)
2515 // OTHER CORNERS: 0x108-0x10F (BothBG variants handled by routine 35)
2516 // Type 2 objects may have 8 or 16 tiles depending on the specific object
2517
2518 if (tiles.size() >= 16) {
2519 // Full 4x4 pattern - Column-major ordering per ZScream
2520 int tid = 0;
2521 for (int xx = 0; xx < 4; xx++) {
2522 for (int yy = 0; yy < 4; yy++) {
2523 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
2524 }
2525 }
2526 } else if (tiles.size() >= 8) {
2527 // Type 2 objects: 8 tiles arranged in 2x4 column-major pattern
2528 // This is the standard Type 2 tile layout
2529 int tid = 0;
2530 for (int xx = 0; xx < 2; xx++) {
2531 for (int yy = 0; yy < 4; yy++) {
2532 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
2533 }
2534 }
2535 } else if (tiles.size() >= 4) {
2536 // Fallback: 2x2 pattern
2537 int tid = 0;
2538 for (int xx = 0; xx < 2; xx++) {
2539 for (int yy = 0; yy < 2; yy++) {
2540 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
2541 }
2542 }
2543 }
2544}
2545
2547 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2548 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2549 // Pattern: 1x2 tiles rightward with +2 offset (object 0x21)
2550 int size = obj.size_ & 0x0F;
2551
2552 // Assembly: (size << 1) + 1 = (size * 2) + 1
2553 int count = (size * 2) + 1;
2554
2555 for (int s = 0; s < count; s++) {
2556 if (tiles.size() >= 2) {
2557 // Use first tile span for 1x2 pattern
2558 WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]);
2559 WriteTile8(bg, obj.x_ + s + 2, obj.y_ + 1, tiles[1]);
2560 }
2561 }
2562}
2563
2565 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2566 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2567 // Pattern: Rail with corner check (object 0x22 small rails)
2568 // ASM: RoomDraw_RightwardsHasEdge1x1_1to16_plus3
2569 // Uses GetSize_1to16_timesA(2), count = (size & 0x0F + 1) * 2
2570 // Structure: [CORNER if needed] -> [MIDDLE * count] -> [END]
2571 int size = obj.size_ & 0x0F;
2572 int count = (size + 1) * 2; // timesA with A=2
2573
2574 if (tiles.size() < 3) return;
2575
2576 int x = obj.x_;
2577
2578 // Draw corner tile (tile 0) - in editor we always draw it
2579 WriteTile8(bg, x, obj.y_, tiles[0]);
2580 x++;
2581
2582 // Draw middle tiles (tile 1) repeated
2583 for (int s = 0; s < count; s++) {
2584 WriteTile8(bg, x, obj.y_, tiles[1]);
2585 x++;
2586 }
2587
2588 // Draw end tile (tile 2)
2589 WriteTile8(bg, x, obj.y_, tiles[2]);
2590}
2591
2593 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2594 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2595 // Pattern: Rail with corner check (objects 0x23-0x2E carpet trim, 0x3F-0x46)
2596 // ASM: RoomDraw_RightwardsHasEdge1x1_1to16_plus2
2597 // Uses GetSize_1to16, count = size + 1
2598 // Structure: [CORNER if needed] -> [MIDDLE * count] -> [END]
2599 int size = obj.size_ & 0x0F;
2600 int count = size + 1;
2601
2602 if (tiles.size() < 3) return;
2603
2604 int x = obj.x_;
2605
2606 // Draw corner tile (tile 0) - in editor we always draw it
2607 WriteTile8(bg, x, obj.y_, tiles[0]);
2608 x++;
2609
2610 // Draw middle tiles (tile 1) repeated
2611 for (int s = 0; s < count; s++) {
2612 WriteTile8(bg, x, obj.y_, tiles[1]);
2613 x++;
2614 }
2615
2616 // Draw end tile (tile 2)
2617 WriteTile8(bg, x, obj.y_, tiles[2]);
2618}
2619
2621 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2622 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2623 // Pattern: Top corner 1x2 tiles with +13 offset (object 0x2F)
2624 int size = obj.size_ & 0x0F;
2625
2626 // Assembly: GetSize_1to16_timesA(0x0A), so count = size + 10
2627 int count = size + 10;
2628
2629 for (int s = 0; s < count; s++) {
2630 if (tiles.size() >= 2) {
2631 // Use first tile span for 1x2 pattern
2632 WriteTile8(bg, obj.x_ + s + 13, obj.y_, tiles[0]);
2633 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[1]);
2634 }
2635 }
2636}
2637
2639 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2640 const std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2641 // Pattern: Bottom corner 1x2 tiles with +13 offset (object 0x30)
2642 int size = obj.size_ & 0x0F;
2643
2644 // Assembly: GetSize_1to16_timesA(0x0A), so count = size + 10
2645 int count = size + 10;
2646
2647 for (int s = 0; s < count; s++) {
2648 if (tiles.size() >= 2) {
2649 // Use first tile span for 1x2 pattern
2650 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[0]);
2651 WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 2, tiles[1]);
2652 }
2653 }
2654}
2655
2657 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2658 // Pattern: Custom draw routine (objects 0x31-0x32)
2659 // For now, fall back to simple 1x1
2660 if (tiles.size() >= 1) {
2661 // Use first 8x8 tile from span
2662 WriteTile8(bg, obj.x_, obj.y_, tiles[0]);
2663 }
2664}
2665
2667 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2668 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2669 // Pattern: 4x4 block rightward (objects 0x33, 0xBA = large ceiling, etc.)
2670 int size = obj.size_ & 0x0F;
2671
2672 // Assembly: GetSize_1to16, so count = size + 1
2673 int count = size + 1;
2674
2675 // Debug: Log large ceiling objects (0xBA)
2676 if (obj.id_ == 0xBA && tiles.size() >= 16) {
2677 LOG_DEBUG("ObjectDrawer", "Large Ceiling Draw: obj=0x%02X pos=(%d,%d) size=%d tiles=%zu",
2678 obj.id_, obj.x_, obj.y_, size, tiles.size());
2679 LOG_DEBUG("ObjectDrawer", " First 4 Tile IDs: [%d, %d, %d, %d]",
2680 tiles[0].id_, tiles[1].id_, tiles[2].id_, tiles[3].id_);
2681 LOG_DEBUG("ObjectDrawer", " First 4 Palettes: [%d, %d, %d, %d]",
2682 tiles[0].palette_, tiles[1].palette_, tiles[2].palette_, tiles[3].palette_);
2683 }
2684
2685 for (int s = 0; s < count; s++) {
2686 if (tiles.size() >= 16) {
2687 // Draw 4x4 pattern in COLUMN-MAJOR order (matching assembly)
2688 // Iterate columns (x) first, then rows (y) within each column
2689 for (int x = 0; x < 4; ++x) {
2690 for (int y = 0; y < 4; ++y) {
2691 WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[x * 4 + y]);
2692 }
2693 }
2694 }
2695 }
2696}
2697
2699 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2700 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2701 // Pattern: 1x1 solid tiles +3 offset (object 0x34)
2702 int size = obj.size_ & 0x0F;
2703
2704 // Assembly: GetSize_1to16_timesA(4), so count = size + 4
2705 int count = size + 4;
2706
2707 for (int s = 0; s < count; s++) {
2708 if (tiles.size() >= 1) {
2709 // Use first 8x8 tile from span
2710 WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]);
2711 }
2712 }
2713}
2714
2717 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2718 // Pattern: Door switcher (object 0x35)
2719 // Special door logic
2720 // Check state to decide graphic
2721 int tile_index = 0;
2722 if (state && state->IsDoorSwitchActive(room_id_)) {
2723 // Use active tile if available (assuming 2nd tile is active state)
2724 if (tiles.size() >= 2) {
2725 tile_index = 1;
2726 }
2727 }
2728
2729 if (tiles.size() > tile_index) {
2730 WriteTile8(bg, obj.x_, obj.y_, tiles[tile_index]);
2731 }
2732}
2733
2735 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2736 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2737 // Pattern: 4x4 decoration with spacing (objects 0x36-0x37)
2738 int size = obj.size_ & 0x0F;
2739
2740 // Assembly: GetSize_1to16, so count = size + 1
2741 int count = size + 1;
2742
2743 for (int s = 0; s < count; s++) {
2744 if (tiles.size() >= 16) {
2745 // Draw 4x4 pattern with spacing in COLUMN-MAJOR order (matching assembly)
2746 for (int x = 0; x < 4; ++x) {
2747 for (int y = 0; y < 4; ++y) {
2748 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[x * 4 + y]);
2749 }
2750 }
2751 }
2752 }
2753}
2754
2756 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2757 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2758 // Pattern: 2x3 statue with spacing (object 0x38)
2759 // 2 columns × 3 rows = 6 tiles in COLUMN-MAJOR order
2760 int size = obj.size_ & 0x0F;
2761
2762 // Assembly: GetSize_1to16, so count = size + 1
2763 int count = size + 1;
2764
2765 for (int s = 0; s < count; s++) {
2766 if (tiles.size() >= 6) {
2767 // Draw 2x3 pattern in COLUMN-MAJOR order (matching assembly)
2768 for (int x = 0; x < 2; ++x) {
2769 for (int y = 0; y < 3; ++y) {
2770 WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[x * 3 + y]);
2771 }
2772 }
2773 }
2774 }
2775}
2776
2778 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2779 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2780 // Pattern: 2x4 pillar with spacing (objects 0x39, 0x3D)
2781 // 2 columns × 4 rows = 8 tiles in COLUMN-MAJOR order
2782 // ASM: ADC #$0008 = 8 bytes = 4 tiles spacing between starts
2783 // Object is 2 tiles wide, so gap is 2 tiles
2784 int size = obj.size_ & 0x0F;
2785
2786 // Assembly: GetSize_1to16, so count = size + 1
2787 int count = size + 1;
2788
2789 for (int s = 0; s < count; s++) {
2790 if (tiles.size() >= 8) {
2791 // Draw 2x4 pattern in COLUMN-MAJOR order (matching assembly)
2792 // Spacing: 4 tiles between starts (object width 2 + gap 2) per ASM ADC #$0008
2793 for (int x = 0; x < 2; ++x) {
2794 for (int y = 0; y < 4; ++y) {
2795 WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[x * 4 + y]);
2796 }
2797 }
2798 }
2799 }
2800}
2801
2803 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2804 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2805 // Pattern: 4x3 decoration with spacing (objects 0x3A-0x3B)
2806 // 4 columns × 3 rows = 12 tiles in COLUMN-MAJOR order
2807 // ASM: ADC #$0008 to Y = 8-byte advance = 4 tiles per iteration
2808 // Total spacing: 4 (object width) + 4 (gap) = 8 tiles between starts
2809 int size = obj.size_ & 0x0F;
2810
2811 // Assembly: GetSize_1to16, so count = size + 1
2812 int count = size + 1;
2813
2814 for (int s = 0; s < count; s++) {
2815 if (tiles.size() >= 12) {
2816 // Draw 4x3 pattern in COLUMN-MAJOR order (matching assembly)
2817 // Spacing: 8 tiles (4 object + 4 gap) per ASM ADC #$0008
2818 for (int x = 0; x < 4; ++x) {
2819 for (int y = 0; y < 3; ++y) {
2820 WriteTile8(bg, obj.x_ + (s * 8) + x, obj.y_ + y, tiles[x * 3 + y]);
2821 }
2822 }
2823 }
2824 }
2825}
2826
2828 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2829 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2830 // Pattern: Doubled 2x2 with spacing (object 0x3C)
2831 // 4 columns × 2 rows = 8 tiles in COLUMN-MAJOR order
2832 int size = obj.size_ & 0x0F;
2833
2834 // Assembly: GetSize_1to16, so count = size + 1
2835 int count = size + 1;
2836
2837 for (int s = 0; s < count; s++) {
2838 if (tiles.size() >= 8) {
2839 // Draw doubled 2x2 pattern in COLUMN-MAJOR order (matching assembly)
2840 for (int x = 0; x < 4; ++x) {
2841 for (int y = 0; y < 2; ++y) {
2842 WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[x * 2 + y]);
2843 }
2844 }
2845 }
2846 }
2847}
2848
2850 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2851 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2852 // Pattern: 2x2 decoration with large spacing (object 0x3E)
2853 int size = obj.size_ & 0x0F;
2854
2855 // Assembly: GetSize_1to16, so count = size + 1
2856 int count = size + 1;
2857
2858 for (int s = 0; s < count; s++) {
2859 if (tiles.size() >= 4) {
2860 // Draw 2x2 pattern in COLUMN-MAJOR order (matching assembly)
2861 // tiles[0] → col 0, row 0 = top-left
2862 // tiles[1] → col 0, row 1 = bottom-left
2863 // tiles[2] → col 1, row 0 = top-right
2864 // tiles[3] → col 1, row 1 = bottom-right
2865 WriteTile8(bg, obj.x_ + (s * 14), obj.y_, tiles[0]); // col 0, row 0
2866 WriteTile8(bg, obj.x_ + (s * 14), obj.y_ + 1, tiles[1]); // col 0, row 1
2867 WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_, tiles[2]); // col 1, row 0
2868 WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_ + 1, tiles[3]); // col 1, row 1
2869 }
2870 }
2871}
2872
2874 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2875 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2876 // Pattern: Draws 4x2 tiles rightward (objects 0x49-0x4A: Floor Tile 4x2)
2877 // Assembly: RoomDraw_RightwardsFloorTile4x2_1to16 -> RoomDraw_Downwards4x2VariableSpacing
2878 // This is 4 columns × 2 rows = 8 tiles in ROW-MAJOR order with horizontal spacing
2879 // Spacing of $0008 = 4 tile columns = 32 pixels
2880 int size = obj.size_ & 0x0F;
2881 int count = size + 1; // GetSize_1to16
2882
2883 for (int s = 0; s < count; s++) {
2884 if (tiles.size() >= 8) {
2885 // Draw 4x2 pattern in ROW-MAJOR order (matching assembly RoomDraw_Downwards4x2VariableSpacing)
2886 // Row 0: tiles 0, 1, 2, 3 at x+0, x+1, x+2, x+3
2887 WriteTile8(bg, obj.x_ + (s * 4), obj.y_, tiles[0]);
2888 WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_, tiles[1]);
2889 WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_, tiles[2]);
2890 WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_, tiles[3]);
2891 // Row 1: tiles 4, 5, 6, 7 at x+0, x+1, x+2, x+3
2892 WriteTile8(bg, obj.x_ + (s * 4), obj.y_ + 1, tiles[4]);
2893 WriteTile8(bg, obj.x_ + (s * 4) + 1, obj.y_ + 1, tiles[5]);
2894 WriteTile8(bg, obj.x_ + (s * 4) + 2, obj.y_ + 1, tiles[6]);
2895 WriteTile8(bg, obj.x_ + (s * 4) + 3, obj.y_ + 1, tiles[7]);
2896 }
2897 }
2898}
2899
2901 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2902 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2903 // Pattern: Draws 1x8 column tiles rightward with spacing (objects 0x55-0x56 wall torches)
2904 // Assembly: RoomDraw_RightwardsDecor4x2spaced8_1to16 -> RoomDraw_Downwards4x2VariableSpacing
2905 // ASM writes to 8 consecutive row buffers ([$BF] through [$D4]) = 1 column × 8 rows
2906 // Spacing = 0x18 = 24 bytes = 12 tile columns between repetitions
2907 int size = obj.size_ & 0x0F;
2908 int count = size + 1; // GetSize_1to16
2909
2910 if (tiles.size() < 8) return;
2911
2912 for (int s = 0; s < count; s++) {
2913 // Draw 1x8 column pattern with 12-tile horizontal spacing
2914 int base_x = obj.x_ + (s * 12); // spacing of 12 columns
2915 for (int row = 0; row < 8; row++) {
2916 WriteTile8(bg, base_x, obj.y_ + row, tiles[row]);
2917 }
2918 }
2919}
2920
2922 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2923 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2924 // Pattern: Draws 4x3 tiles (objects 0x51-0x52, 0x5B-0x5C: Cannon Hole)
2925 // Assembly: RoomDraw_RightwardsCannonHole4x3_1to16
2926 // This is 4 columns × 3 rows = 12 tiles in ROW-MAJOR order
2927 int size = obj.size_ & 0x0F;
2928 int count = size + 1; // GetSize_1to16
2929
2930 for (int s = 0; s < count; s++) {
2931 if (tiles.size() >= 12) {
2932 int base_x = obj.x_ + (s * 4);
2933 // Row 0
2934 WriteTile8(bg, base_x, obj.y_, tiles[0]);
2935 WriteTile8(bg, base_x + 1, obj.y_, tiles[1]);
2936 WriteTile8(bg, base_x + 2, obj.y_, tiles[2]);
2937 WriteTile8(bg, base_x + 3, obj.y_, tiles[3]);
2938 // Row 1
2939 WriteTile8(bg, base_x, obj.y_ + 1, tiles[4]);
2940 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[5]);
2941 WriteTile8(bg, base_x + 2, obj.y_ + 1, tiles[6]);
2942 WriteTile8(bg, base_x + 3, obj.y_ + 1, tiles[7]);
2943 // Row 2
2944 WriteTile8(bg, base_x, obj.y_ + 2, tiles[8]);
2945 WriteTile8(bg, base_x + 1, obj.y_ + 2, tiles[9]);
2946 WriteTile8(bg, base_x + 2, obj.y_ + 2, tiles[10]);
2947 WriteTile8(bg, base_x + 3, obj.y_ + 2, tiles[11]);
2948 } else if (tiles.size() >= 8) {
2949 // Fallback: use 4x2 if we don't have enough tiles
2950 int base_x = obj.x_ + (s * 4);
2951 WriteTile8(bg, base_x, obj.y_, tiles[0]);
2952 WriteTile8(bg, base_x + 1, obj.y_, tiles[1]);
2953 WriteTile8(bg, base_x + 2, obj.y_, tiles[2]);
2954 WriteTile8(bg, base_x + 3, obj.y_, tiles[3]);
2955 WriteTile8(bg, base_x, obj.y_ + 1, tiles[4]);
2956 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[5]);
2957 WriteTile8(bg, base_x + 2, obj.y_ + 1, tiles[6]);
2958 WriteTile8(bg, base_x + 3, obj.y_ + 1, tiles[7]);
2959 }
2960 }
2961}
2962
2963// Additional Rightwards draw routines (0x47-0x5E range)
2964
2966 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2967 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2968 // Pattern: 1x1 line rightward with +1 modifier (object 0x50)
2969 // Assembly: RoomDraw_RightwardsLine1x1_1to16plus1
2970 int size = obj.size_ & 0x0F;
2971 int count = size + 2; // +1 gives +2 iterations total
2972
2973 for (int s = 0; s < count; s++) {
2974 if (tiles.size() >= 1) {
2975 WriteTile8(bg, obj.x_ + s, obj.y_, tiles[0]);
2976 }
2977 }
2978}
2979
2981 const RoomObject& obj, gfx::BackgroundBuffer& bg,
2982 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
2983 // Pattern: 4x3 bar rightward with 6-tile X spacing (object 0x4C)
2984 // Assembly: RoomDraw_RightwardsBar4x3_1to16 - adds $0006 to X per iteration
2985 int size = obj.size_ & 0x0F;
2986 int count = size + 1;
2987
2988 for (int s = 0; s < count; s++) {
2989 if (tiles.size() >= 12) {
2990 int base_x = obj.x_ + (s * 6); // 6-tile X spacing
2991 // Row 0
2992 WriteTile8(bg, base_x, obj.y_, tiles[0]);
2993 WriteTile8(bg, base_x + 1, obj.y_, tiles[1]);
2994 WriteTile8(bg, base_x + 2, obj.y_, tiles[2]);
2995 WriteTile8(bg, base_x + 3, obj.y_, tiles[3]);
2996 // Row 1
2997 WriteTile8(bg, base_x, obj.y_ + 1, tiles[4]);
2998 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[5]);
2999 WriteTile8(bg, base_x + 2, obj.y_ + 1, tiles[6]);
3000 WriteTile8(bg, base_x + 3, obj.y_ + 1, tiles[7]);
3001 // Row 2
3002 WriteTile8(bg, base_x, obj.y_ + 2, tiles[8]);
3003 WriteTile8(bg, base_x + 1, obj.y_ + 2, tiles[9]);
3004 WriteTile8(bg, base_x + 2, obj.y_ + 2, tiles[10]);
3005 WriteTile8(bg, base_x + 3, obj.y_ + 2, tiles[11]);
3006 }
3007 }
3008}
3009
3011 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3012 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3013 // Pattern: 4x4 shelf rightward with complex column pattern (objects 0x4D-0x4F)
3014 // Assembly: RoomDraw_RightwardsShelf4x4_1to16
3015 // This draws 4x4 patterns with 6-tile X spacing
3016 int size = obj.size_ & 0x0F;
3017 int count = size + 1;
3018
3019 for (int s = 0; s < count; s++) {
3020 if (tiles.size() >= 16) {
3021 int base_x = obj.x_ + (s * 6); // 6-tile X spacing
3022 // Draw 4x4 pattern in COLUMN-MAJOR order
3023 for (int col = 0; col < 4; col++) {
3024 for (int row = 0; row < 4; row++) {
3025 int tile_idx = col * 4 + row;
3026 WriteTile8(bg, base_x + col, obj.y_ + row, tiles[tile_idx]);
3027 }
3028 }
3029 }
3030 }
3031}
3032
3034 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3035 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3036 // Pattern: Big rail with LEFT CAP (2x3) + MIDDLE (1x3 repeated) + RIGHT CAP (2x3)
3037 // Assembly: RoomDraw_RightwardsBigRail1x3_1to16plus5
3038 // Tile layout: [6 tiles for left cap] [3 tiles for middle] [6 tiles for right cap]
3039 // Left cap: 2 columns x 3 rows in COLUMN-MAJOR order
3040 // Middle: 1 column x 3 rows, repeated (size + 2) times
3041 // Right cap: 2 columns x 3 rows in COLUMN-MAJOR order
3042
3043 int size = obj.size_ & 0x0F;
3044 int middle_count = size + 2; // ASM: INC B2 after GetSize_1to16 = (size+1)+1 = size+2
3045
3046 if (tiles.size() < 15) return; // Need 6+3+6=15 tiles minimum
3047
3048 int x = obj.x_;
3049
3050 // Draw LEFT CAP: 2 columns x 3 rows (tiles 0-5 in column-major order)
3051 // Column 0
3052 WriteTile8(bg, x, obj.y_, tiles[0]);
3053 WriteTile8(bg, x, obj.y_ + 1, tiles[1]);
3054 WriteTile8(bg, x, obj.y_ + 2, tiles[2]);
3055 // Column 1
3056 WriteTile8(bg, x + 1, obj.y_, tiles[3]);
3057 WriteTile8(bg, x + 1, obj.y_ + 1, tiles[4]);
3058 WriteTile8(bg, x + 1, obj.y_ + 2, tiles[5]);
3059 x += 2;
3060
3061 // Draw MIDDLE: 1 column x 3 rows (tiles 6-8), repeated middle_count times
3062 for (int s = 0; s < middle_count; s++) {
3063 WriteTile8(bg, x + s, obj.y_, tiles[6]);
3064 WriteTile8(bg, x + s, obj.y_ + 1, tiles[7]);
3065 WriteTile8(bg, x + s, obj.y_ + 2, tiles[8]);
3066 }
3067 x += middle_count;
3068
3069 // Draw RIGHT CAP: 2 columns x 3 rows (tiles 9-14 in column-major order)
3070 // Column 0
3071 WriteTile8(bg, x, obj.y_, tiles[9]);
3072 WriteTile8(bg, x, obj.y_ + 1, tiles[10]);
3073 WriteTile8(bg, x, obj.y_ + 2, tiles[11]);
3074 // Column 1
3075 WriteTile8(bg, x + 1, obj.y_, tiles[12]);
3076 WriteTile8(bg, x + 1, obj.y_ + 1, tiles[13]);
3077 WriteTile8(bg, x + 1, obj.y_ + 2, tiles[14]);
3078}
3079
3081 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3082 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3083 // Pattern: 2x2 block rightward with 4-tile X spacing (object 0x5E)
3084 // Assembly: RoomDraw_RightwardsBlock2x2spaced2_1to16 - uses RoomDraw_Rightwards2x2
3085 // Tiles are in COLUMN-MAJOR order: [top-left, bottom-left, top-right, bottom-right]
3086 int size = obj.size_ & 0x0F;
3087 int count = size + 1;
3088
3089 for (int s = 0; s < count; s++) {
3090 if (tiles.size() >= 4) {
3091 int base_x = obj.x_ + (s * 4); // 4-tile X spacing (INY x4 = 2 tiles)
3092 // Draw 2x2 pattern in COLUMN-MAJOR order (matching ASM)
3093 WriteTile8(bg, base_x, obj.y_, tiles[0]); // top-left
3094 WriteTile8(bg, base_x, obj.y_ + 1, tiles[1]); // bottom-left
3095 WriteTile8(bg, base_x + 1, obj.y_, tiles[2]); // top-right
3096 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[3]); // bottom-right
3097 }
3098 }
3099}
3100
3101// ============================================================================
3102// Downwards Draw Routines
3103// ============================================================================
3104
3106 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3107 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3108 // Pattern: Draws 2x2 tiles downward (object 0x60)
3109 // Size byte determines how many times to repeat (1-15 or 32)
3110 int size = obj.size_;
3111 if (size == 0)
3112 size = 32; // Special case for object 0x60
3113
3114 for (int s = 0; s < size; s++) {
3115 if (tiles.size() >= 4) {
3116 // Draw 2x2 pattern in COLUMN-MAJOR order (matching assembly)
3117 // Assembly uses indirect pointers: $BF, $CB, $C2, $CE
3118 // tiles[0] → $BF → (col 0, row 0) = top-left
3119 // tiles[1] → $CB → (col 0, row 1) = bottom-left
3120 // tiles[2] → $C2 → (col 1, row 0) = top-right
3121 // tiles[3] → $CE → (col 1, row 1) = bottom-right
3122 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // col 0, row 0
3123 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[1]); // col 0, row 1
3124 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[2]); // col 1, row 0
3125 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1,
3126 tiles[3]); // col 1, row 1
3127 }
3128 }
3129}
3130
3132 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3133 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3134 // Pattern: Draws 4x2 tiles downward (objects 0x61-0x62)
3135 // This is 4 columns × 2 rows = 8 tiles in ROW-MAJOR order (per ZScream)
3136 int size = obj.size_;
3137 if (size == 0)
3138 size = 26; // Special case
3139
3140 LOG_DEBUG("ObjectDrawer", "Wall Draw 4x2 Vertical: obj=0x%03X pos=(%d,%d) size=%d tiles=%zu",
3141 obj.id_, obj.x_, obj.y_, size, tiles.size());
3142
3143 for (int s = 0; s < size; s++) {
3144 if (tiles.size() >= 8) {
3145 // Draw 4x2 pattern in ROW-MAJOR order (matching ZScream)
3146 // Row 0: tiles 0, 1, 2, 3 at x+0, x+1, x+2, x+3
3147 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
3148 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
3149 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[2]);
3150 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[3]);
3151 // Row 1: tiles 4, 5, 6, 7 at x+0, x+1, x+2, x+3
3152 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[4]);
3153 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[5]);
3154 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[6]);
3155 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[7]);
3156 } else if (tiles.size() >= 4) {
3157 // Fallback: with 4 tiles draw 4x1 row pattern
3158 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
3159 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
3160 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[2]);
3161 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[3]);
3162 }
3163 }
3164}
3165
3167 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3168 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3169 // Pattern: Same as above but draws to both BG1 and BG2 (objects 0x63-0x64)
3170 DrawDownwards4x2_1to15or26(obj, bg, tiles);
3171 // Note: BothBG would require access to both buffers - simplified for now
3172}
3173
3175 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3176 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3177 // Pattern: Draws 4x2 decoration downward with spacing (objects 0x65-0x66)
3178 // This is 4 columns × 2 rows = 8 tiles in COLUMN-MAJOR order with 6-tile Y spacing
3179 int size = obj.size_ & 0x0F;
3180
3181 // Assembly: GetSize_1to16, so count = size + 1
3182 int count = size + 1;
3183
3184 for (int s = 0; s < count; s++) {
3185 if (tiles.size() >= 8) {
3186 // Draw 4x2 pattern in COLUMN-MAJOR order
3187 // Column 0 (tiles 0-1)
3188 WriteTile8(bg, obj.x_, obj.y_ + (s * 6), tiles[0]); // col 0, row 0
3189 WriteTile8(bg, obj.x_, obj.y_ + (s * 6) + 1, tiles[1]); // col 0, row 1
3190 // Column 1 (tiles 2-3)
3191 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6), tiles[2]); // col 1, row 0
3192 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6) + 1, tiles[3]); // col 1, row 1
3193 // Column 2 (tiles 4-5)
3194 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6), tiles[4]); // col 2, row 0
3195 WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6) + 1, tiles[5]); // col 2, row 1
3196 // Column 3 (tiles 6-7)
3197 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6), tiles[6]); // col 3, row 0
3198 WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6) + 1, tiles[7]); // col 3, row 1
3199 }
3200 }
3201}
3202
3204 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3205 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3206 // Pattern: Draws 2x2 tiles downward (objects 0x67-0x68)
3207 int size = obj.size_ & 0x0F;
3208
3209 // Assembly: GetSize_1to16, so count = size + 1
3210 int count = size + 1;
3211
3212 for (int s = 0; s < count; s++) {
3213 if (tiles.size() >= 4) {
3214 // Draw 2x2 pattern in COLUMN-MAJOR order (matching assembly)
3215 // tiles[0] → col 0, row 0 = top-left
3216 // tiles[1] → col 0, row 1 = bottom-left
3217 // tiles[2] → col 1, row 0 = top-right
3218 // tiles[3] → col 1, row 1 = bottom-right
3219 WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // col 0, row 0
3220 WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[1]); // col 0, row 1
3221 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[2]); // col 1, row 0
3222 WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); // col 1, row 1
3223 }
3224 }
3225}
3226
3228 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3229 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3230 // Pattern: 1x1 tiles with edge detection +3 offset downward (object 0x69)
3231 int size = obj.size_ & 0x0F;
3232
3233 // Assembly: GetSize_1to16_timesA(2), so count = size + 2
3234 int count = size + 2;
3235
3236 for (int s = 0; s < count; s++) {
3237 if (tiles.size() >= 1) {
3238 // Use first 8x8 tile from span
3239 WriteTile8(bg, obj.x_ + 3, obj.y_ + s, tiles[0]);
3240 }
3241 }
3242}
3243
3245 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3246 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3247 // Pattern: Vertical rail with CORNER+MIDDLE+END pattern (objects 0x8A-0x8C)
3248 // ASM: RoomDraw_DownwardsHasEdge1x1_1to16_plus23 ($019314)
3249 // Matches horizontal rail 0x22 but in vertical orientation
3250 // Structure: [CORNER if needed] -> [MIDDLE * count] -> [END]
3251 int size = obj.size_ & 0x0F;
3252 int count = (size + 1) * 2; // Same calculation as horizontal rails
3253
3254 if (tiles.size() < 3) return;
3255
3256 int tile_y = obj.y_;
3257
3258 // Draw corner tile (tile 0) - always draw in editor
3259 WriteTile8(bg, obj.x_, tile_y, tiles[0]);
3260 tile_y++;
3261
3262 // Draw middle tiles (tile 1) repeated
3263 for (int s = 0; s < count; s++) {
3264 WriteTile8(bg, obj.x_, tile_y, tiles[1]);
3265 tile_y++;
3266 }
3267
3268 // Draw end tile (tile 2)
3269 WriteTile8(bg, obj.x_, tile_y, tiles[2]);
3270}
3271
3273 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3274 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3275 // Pattern: 1x1 edge tiles downward (objects 0x6A-0x6B)
3276 int size = obj.size_ & 0x0F;
3277
3278 // Assembly: GetSize_1to16, so count = size + 1
3279 int count = size + 1;
3280
3281 for (int s = 0; s < count; s++) {
3282 if (tiles.size() >= 1) {
3283 // Use first 8x8 tile from span
3284 WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]);
3285 }
3286 }
3287}
3288
3290 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3291 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3292 // Pattern: Left corner 2x1 tiles with +12 offset downward (object 0x6C)
3293 int size = obj.size_ & 0x0F;
3294
3295 // Assembly: GetSize_1to16_timesA(0x0A), so count = size + 10
3296 int count = size + 10;
3297
3298 for (int s = 0; s < count; s++) {
3299 if (tiles.size() >= 2) {
3300 // Use first tile span for 2x1 pattern
3301 WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
3302 WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
3303 }
3304 }
3305}
3306
3308 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3309 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3310 // Pattern: Right corner 2x1 tiles with +12 offset downward (object 0x6D)
3311 int size = obj.size_ & 0x0F;
3312
3313 // Assembly: GetSize_1to16_timesA(0x0A), so count = size + 10
3314 int count = size + 10;
3315
3316 for (int s = 0; s < count; s++) {
3317 if (tiles.size() >= 2) {
3318 // Use first tile span for 2x1 pattern
3319 WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
3320 WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
3321 }
3322 }
3323}
3324
3325// Additional Downwards draw routines (0x70-0x7F range)
3326
3328 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3329 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3330 // Pattern: Draws 4x4 floor tiles downward (object 0x70)
3331 // Assembly: RoomDraw_DownwardsFloor4x4_1to16 - adds $0200 to Y per iteration
3332 // $0200 = 512 = 4 tile rows (tilemap is 64 tiles wide = 128 bytes/row)
3333 int size = obj.size_ & 0x0F;
3334 int count = size + 1;
3335
3336 for (int s = 0; s < count; s++) {
3337 if (tiles.size() >= 16) {
3338 // Draw 4x4 pattern in COLUMN-MAJOR order
3339 for (int col = 0; col < 4; col++) {
3340 for (int row = 0; row < 4; row++) {
3341 int tile_idx = col * 4 + row;
3342 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 4) + row, tiles[tile_idx]);
3343 }
3344 }
3345 }
3346 }
3347}
3348
3350 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3351 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3352 // Pattern: 1x1 solid tiles downward with +3 modifier (object 0x71)
3353 // Assembly: RoomDraw_Downwards1x1Solid_1to16_plus3 - adds $0080 to Y per iteration
3354 int size = obj.size_ & 0x0F;
3355 int count = size + 4; // +3 gives +4 iterations total (1 + size + 3)
3356
3357 for (int s = 0; s < count; s++) {
3358 if (tiles.size() >= 1) {
3359 WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]);
3360 }
3361 }
3362}
3363
3365 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3366 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3367 // Pattern: 4x4 decoration with 2-tile spacing downward (objects 0x73-0x74)
3368 // Assembly: RoomDraw_DownwardsDecor4x4spaced2_1to16 - adds $0300 to Y per iteration
3369 // $0300 = 6 tile rows spacing
3370 int size = obj.size_ & 0x0F;
3371 int count = size + 1;
3372
3373 for (int s = 0; s < count; s++) {
3374 if (tiles.size() >= 16) {
3375 // Draw 4x4 pattern in COLUMN-MAJOR order with 6-tile Y spacing
3376 for (int col = 0; col < 4; col++) {
3377 for (int row = 0; row < 4; row++) {
3378 int tile_idx = col * 4 + row;
3379 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 6) + row, tiles[tile_idx]);
3380 }
3381 }
3382 }
3383 }
3384}
3385
3387 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3388 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3389 // Pattern: 2x4 pillar with 2-tile spacing downward (object 0x75, 0x87)
3390 // Assembly: RoomDraw_DownwardsPillar2x4spaced2_1to16 - adds $0300 to Y per iteration
3391 // $0300 = 6 tile rows spacing
3392 int size = obj.size_ & 0x0F;
3393 int count = size + 1;
3394
3395 for (int s = 0; s < count; s++) {
3396 if (tiles.size() >= 8) {
3397 // Draw 2x4 pattern in COLUMN-MAJOR order with 6-tile Y spacing
3398 for (int col = 0; col < 2; col++) {
3399 for (int row = 0; row < 4; row++) {
3400 int tile_idx = col * 4 + row;
3401 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 6) + row, tiles[tile_idx]);
3402 }
3403 }
3404 }
3405 }
3406}
3407
3409 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3410 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3411 // Pattern: 3x4 decoration with 4-tile spacing downward (objects 0x76-0x77)
3412 // Assembly: RoomDraw_DownwardsDecor3x4spaced4_1to16 - adds $0400 to Y per iteration
3413 // $0400 = 8 tile rows spacing
3414 int size = obj.size_ & 0x0F;
3415 int count = size + 1;
3416
3417 for (int s = 0; s < count; s++) {
3418 if (tiles.size() >= 12) {
3419 // Draw 3x4 pattern in COLUMN-MAJOR order with 8-tile Y spacing
3420 for (int col = 0; col < 3; col++) {
3421 for (int row = 0; row < 4; row++) {
3422 int tile_idx = col * 4 + row;
3423 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 8) + row, tiles[tile_idx]);
3424 }
3425 }
3426 }
3427 }
3428}
3429
3431 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3432 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3433 // Pattern: 2x2 decoration with 12-tile spacing downward (objects 0x78, 0x7B)
3434 // Assembly: RoomDraw_DownwardsDecor2x2spaced12_1to16 - adds $0600 to Y per iteration
3435 // $0600 = 14 tile rows spacing
3436 int size = obj.size_ & 0x0F;
3437 int count = size + 1;
3438
3439 for (int s = 0; s < count; s++) {
3440 if (tiles.size() >= 4) {
3441 // Draw 2x2 pattern in COLUMN-MAJOR order with 14-tile Y spacing
3442 for (int col = 0; col < 2; col++) {
3443 for (int row = 0; row < 2; row++) {
3444 int tile_idx = col * 2 + row;
3445 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 14) + row, tiles[tile_idx]);
3446 }
3447 }
3448 }
3449 }
3450}
3451
3453 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3454 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3455 // Pattern: 1x1 line downward with +1 modifier (object 0x7C)
3456 // Assembly: RoomDraw_DownwardsLine1x1_1to16plus1
3457 int size = obj.size_ & 0x0F;
3458 int count = size + 2; // +1 gives +2 iterations total
3459
3460 for (int s = 0; s < count; s++) {
3461 if (tiles.size() >= 1) {
3462 WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]);
3463 }
3464 }
3465}
3466
3468 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3469 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3470 // Pattern: 2x4 decoration with 8-tile spacing downward (objects 0x7F, 0x80)
3471 // Assembly: RoomDraw_DownwardsDecor2x4spaced8_1to16 - adds $0500 to Y per iteration
3472 // $0500 = 10 tile rows spacing
3473 int size = obj.size_ & 0x0F;
3474 int count = size + 1;
3475
3476 // Debug: Log vertical ceiling objects (0x80)
3477 if (obj.id_ == 0x80 && tiles.size() >= 8) {
3478 LOG_DEBUG("ObjectDrawer", "Vertical Ceiling Draw: obj=0x%02X pos=(%d,%d) size=%d tiles=%zu",
3479 obj.id_, obj.x_, obj.y_, size, tiles.size());
3480 LOG_DEBUG("ObjectDrawer", " Tile IDs: [%d, %d, %d, %d, %d, %d, %d, %d]",
3481 tiles[0].id_, tiles[1].id_, tiles[2].id_, tiles[3].id_,
3482 tiles[4].id_, tiles[5].id_, tiles[6].id_, tiles[7].id_);
3483 LOG_DEBUG("ObjectDrawer", " Palettes: [%d, %d, %d, %d, %d, %d, %d, %d]",
3484 tiles[0].palette_, tiles[1].palette_, tiles[2].palette_, tiles[3].palette_,
3485 tiles[4].palette_, tiles[5].palette_, tiles[6].palette_, tiles[7].palette_);
3486 }
3487
3488 for (int s = 0; s < count; s++) {
3489 if (tiles.size() >= 8) {
3490 // Draw 2x4 pattern in COLUMN-MAJOR order with 10-tile Y spacing
3491 for (int col = 0; col < 2; col++) {
3492 for (int row = 0; row < 4; row++) {
3493 int tile_idx = col * 4 + row;
3494 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 10) + row, tiles[tile_idx]);
3495 }
3496 }
3497 }
3498 }
3499}
3500
3501// ============================================================================
3502// Phase 4 Step 2: Simple Variant Routines (0x80-0x96, 0xB0-0xBD range)
3503// ============================================================================
3504
3506 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3507 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3508 // Pattern: 3x4 decoration with 2-tile spacing downward (objects 0x81-0x84)
3509 // 3 cols × 4 rows = 12 tiles, with 6-tile Y spacing (4 tile height + 2 spacing)
3510 int size = obj.size_ & 0x0F;
3511 int count = size + 1;
3512
3513 for (int s = 0; s < count; s++) {
3514 if (tiles.size() >= 12) {
3515 // Draw 3x4 pattern in COLUMN-MAJOR order with 6-tile Y spacing
3516 for (int col = 0; col < 3; col++) {
3517 for (int row = 0; row < 4; row++) {
3518 int tile_idx = col * 4 + row;
3519 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 6) + row, tiles[tile_idx]);
3520 }
3521 }
3522 }
3523 }
3524}
3525
3527 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3528 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3529 // Pattern: Big rail with TOP CAP (2x2) + MIDDLE (2x1 repeated) + BOTTOM CAP (2x3)
3530 // Assembly: RoomDraw_DownwardsBigRail3x1_1to16plus5
3531 // Tile layout: [4 tiles for top cap 2x2] [2 tiles for middle] [6 tiles for bottom cap 2x3]
3532 // Top cap: 2x2 in COLUMN-MAJOR order
3533 // Middle: 2 tiles wide (1 row), repeated (size + 1) times
3534 // Bottom cap: 2 columns x 3 rows in COLUMN-MAJOR order
3535
3536 int size = obj.size_ & 0x0F;
3537 int middle_count = size + 1; // GetSize_1to16 = size + 1
3538
3539 if (tiles.size() < 12) return; // Need 4+2+6=12 tiles minimum
3540
3541 int y = obj.y_;
3542
3543 // Draw TOP CAP: 2x2 block (tiles 0-3 in column-major order)
3544 // Column 0
3545 WriteTile8(bg, obj.x_, y, tiles[0]);
3546 WriteTile8(bg, obj.x_, y + 1, tiles[1]);
3547 // Column 1
3548 WriteTile8(bg, obj.x_ + 1, y, tiles[2]);
3549 WriteTile8(bg, obj.x_ + 1, y + 1, tiles[3]);
3550 y += 2;
3551
3552 // Draw MIDDLE: 2 tiles wide x 1 row (tiles 4-5), repeated middle_count times
3553 for (int s = 0; s < middle_count; s++) {
3554 WriteTile8(bg, obj.x_, y + s, tiles[4]);
3555 WriteTile8(bg, obj.x_ + 1, y + s, tiles[5]);
3556 }
3557 y += middle_count;
3558
3559 // Draw BOTTOM CAP: 2 columns x 3 rows (tiles 6-11 in column-major order)
3560 // Column 0
3561 WriteTile8(bg, obj.x_, y, tiles[6]);
3562 WriteTile8(bg, obj.x_, y + 1, tiles[7]);
3563 WriteTile8(bg, obj.x_, y + 2, tiles[8]);
3564 // Column 1
3565 WriteTile8(bg, obj.x_ + 1, y, tiles[9]);
3566 WriteTile8(bg, obj.x_ + 1, y + 1, tiles[10]);
3567 WriteTile8(bg, obj.x_ + 1, y + 2, tiles[11]);
3568}
3569
3571 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3572 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3573 // Pattern: 2x2 block downward with 4-tile Y spacing (object 0x89)
3574 // Vertical version of RightwardsBlock2x2spaced2_1to16
3575 int size = obj.size_ & 0x0F;
3576 int count = size + 1;
3577
3578 for (int s = 0; s < count; s++) {
3579 if (tiles.size() >= 4) {
3580 int base_y = obj.y_ + (s * 4); // 4-tile Y spacing
3581 // Draw 2x2 pattern in COLUMN-MAJOR order
3582 WriteTile8(bg, obj.x_, base_y, tiles[0]);
3583 WriteTile8(bg, obj.x_, base_y + 1, tiles[1]);
3584 WriteTile8(bg, obj.x_ + 1, base_y, tiles[2]);
3585 WriteTile8(bg, obj.x_ + 1, base_y + 1, tiles[3]);
3586 }
3587 }
3588}
3589
3591 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3592 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3593 // Pattern: 3x6 cannon hole downward (objects 0x85-0x86)
3594 // 3 cols × 6 rows = 18 tiles
3595 // Vertical version of RightwardsCannonHole4x3_1to16
3596 int size = obj.size_ & 0x0F;
3597 int count = size + 1;
3598
3599 for (int s = 0; s < count; s++) {
3600 if (tiles.size() >= 18) {
3601 // Draw 3x6 pattern in COLUMN-MAJOR order
3602 for (int col = 0; col < 3; col++) {
3603 for (int row = 0; row < 6; row++) {
3604 int tile_idx = col * 6 + row;
3605 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 6) + row, tiles[tile_idx]);
3606 }
3607 }
3608 }
3609 }
3610}
3611
3613 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3614 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3615 // Pattern: 2x3 bar downward (object 0x8F)
3616 // 2 cols × 3 rows = 6 tiles
3617 int size = obj.size_ & 0x0F;
3618 int count = size + 1;
3619
3620 for (int s = 0; s < count; s++) {
3621 if (tiles.size() >= 6) {
3622 // Draw 2x3 pattern in COLUMN-MAJOR order
3623 for (int col = 0; col < 2; col++) {
3624 for (int row = 0; row < 3; row++) {
3625 int tile_idx = col * 3 + row;
3626 WriteTile8(bg, obj.x_ + col, obj.y_ + (s * 3) + row, tiles[tile_idx]);
3627 }
3628 }
3629 }
3630 }
3631}
3632
3634 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3635 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3636 // Pattern: 2x2 pots downward (object 0x95)
3637 // Interactive object - draws 2x2 pattern vertically repeating
3638 int size = obj.size_ & 0x0F;
3639 int count = size + 1;
3640
3641 for (int s = 0; s < count; s++) {
3642 if (tiles.size() >= 4) {
3643 int base_y = obj.y_ + (s * 2);
3644 // Draw 2x2 pattern in COLUMN-MAJOR order
3645 WriteTile8(bg, obj.x_, base_y, tiles[0]);
3646 WriteTile8(bg, obj.x_, base_y + 1, tiles[1]);
3647 WriteTile8(bg, obj.x_ + 1, base_y, tiles[2]);
3648 WriteTile8(bg, obj.x_ + 1, base_y + 1, tiles[3]);
3649 }
3650 }
3651}
3652
3654 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3655 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3656 // Pattern: 2x2 hammer pegs downward (object 0x96)
3657 // Interactive object - draws 2x2 pattern vertically repeating
3658 int size = obj.size_ & 0x0F;
3659 int count = size + 1;
3660
3661 for (int s = 0; s < count; s++) {
3662 if (tiles.size() >= 4) {
3663 int base_y = obj.y_ + (s * 2);
3664 // Draw 2x2 pattern in COLUMN-MAJOR order
3665 WriteTile8(bg, obj.x_, base_y, tiles[0]);
3666 WriteTile8(bg, obj.x_, base_y + 1, tiles[1]);
3667 WriteTile8(bg, obj.x_ + 1, base_y, tiles[2]);
3668 WriteTile8(bg, obj.x_ + 1, base_y + 1, tiles[3]);
3669 }
3670 }
3671}
3672
3674 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3675 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3676 // Pattern: 1x1 edge tiles rightward with +7 offset (objects 0xB0-0xB1)
3677 // Assembly: RoomDraw_RightwardsEdge1x1_1to16plus7
3678 int size = obj.size_ & 0x0F;
3679 int count = size + 8; // +7 gives +8 iterations total
3680
3681 for (int s = 0; s < count; s++) {
3682 if (tiles.size() >= 1) {
3683 WriteTile8(bg, obj.x_ + s, obj.y_, tiles[0]);
3684 }
3685 }
3686}
3687
3689 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3690 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3691 // Pattern: 2x2 pots rightward (object 0xBC)
3692 // Interactive object - draws 2x2 pattern horizontally repeating
3693 int size = obj.size_ & 0x0F;
3694 int count = size + 1;
3695
3696 for (int s = 0; s < count; s++) {
3697 if (tiles.size() >= 4) {
3698 int base_x = obj.x_ + (s * 2);
3699 // Draw 2x2 pattern in COLUMN-MAJOR order
3700 WriteTile8(bg, base_x, obj.y_, tiles[0]);
3701 WriteTile8(bg, base_x, obj.y_ + 1, tiles[1]);
3702 WriteTile8(bg, base_x + 1, obj.y_, tiles[2]);
3703 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[3]);
3704 }
3705 }
3706}
3707
3709 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3710 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3711 // Pattern: 2x2 hammer pegs rightward (object 0xBD)
3712 // Interactive object - draws 2x2 pattern horizontally repeating
3713 int size = obj.size_ & 0x0F;
3714 int count = size + 1;
3715
3716 for (int s = 0; s < count; s++) {
3717 if (tiles.size() >= 4) {
3718 int base_x = obj.x_ + (s * 2);
3719 // Draw 2x2 pattern in COLUMN-MAJOR order
3720 WriteTile8(bg, base_x, obj.y_, tiles[0]);
3721 WriteTile8(bg, base_x, obj.y_ + 1, tiles[1]);
3722 WriteTile8(bg, base_x + 1, obj.y_, tiles[2]);
3723 WriteTile8(bg, base_x + 1, obj.y_ + 1, tiles[3]);
3724 }
3725 }
3726}
3727
3728// ============================================================================
3729// Phase 4 Step 3: Diagonal Ceiling Routines
3730// ============================================================================
3731
3733 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3734 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3735 // Pattern: Diagonal ceiling top-left - TRIANGLE shape (objects 0xA0, 0xA5, 0xA9)
3736 // Assembly: RoomDraw_DiagonalCeilingTopLeftA/B with GetSize_1to16_timesA(4)
3737 // count = (size & 0x0F) + 4 per ASM
3738 int count = (obj.size_ & 0x0F) + 4;
3739
3740 if (tiles.empty()) {
3741 LOG_DEBUG("ObjectDrawer", "DiagonalCeilingTopLeft: No tiles for obj 0x%02X", obj.id_);
3742 return;
3743 }
3744
3745 LOG_DEBUG("ObjectDrawer",
3746 "DiagonalCeilingTopLeft: obj=0x%02X pos=(%d,%d) size=%d count=%d",
3747 obj.id_, obj.x_, obj.y_, obj.size_, count);
3748
3749 // Each row: starts with 'count' tiles, then count-1, count-2, etc.
3750 int tiles_in_row = count;
3751 for (int row = 0; row < count && tiles_in_row > 0; row++) {
3752 for (int col = 0; col < tiles_in_row; col++) {
3753 WriteTile8(bg, obj.x_ + col, obj.y_ + row, tiles[0]);
3754 }
3755 tiles_in_row--; // One fewer tile each row (DEC $B2)
3756 }
3757}
3758
3760 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3761 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3762 // Pattern: Diagonal ceiling bottom-left - TRIANGLE shape (objects 0xA1, 0xA6, 0xAA)
3763 // Assembly: RoomDraw_DiagonalCeilingBottomLeftA/B with GetSize_1to16_timesA(4)
3764 // count = (size & 0x0F) + 4 per ASM
3765 int count = (obj.size_ & 0x0F) + 4;
3766
3767 if (tiles.empty()) return;
3768
3769 // Row 0: 1 tile, Row 1: 2 tiles, ..., Row count-1: count tiles
3770 for (int row = 0; row < count; row++) {
3771 int tiles_in_row = row + 1;
3772 for (int col = 0; col < tiles_in_row; col++) {
3773 WriteTile8(bg, obj.x_ + col, obj.y_ + row, tiles[0]);
3774 }
3775 }
3776}
3777
3779 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3780 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3781 // Pattern: Diagonal ceiling top-right - TRIANGLE shape (objects 0xA2, 0xA7, 0xAB)
3782 // Assembly: RoomDraw_DiagonalCeilingTopRightA/B with GetSize_1to16_timesA(4)
3783 // count = (size & 0x0F) + 4 per ASM
3784 int count = (obj.size_ & 0x0F) + 4;
3785
3786 if (tiles.empty()) return;
3787
3788 // Row 0 at (x,y): count tiles, Row 1 at (x+1,y+1): count-1 tiles, etc.
3789 int tiles_in_row = count;
3790 for (int row = 0; row < count && tiles_in_row > 0; row++) {
3791 for (int col = 0; col < tiles_in_row; col++) {
3792 WriteTile8(bg, obj.x_ + row + col, obj.y_ + row, tiles[0]);
3793 }
3794 tiles_in_row--;
3795 }
3796}
3797
3799 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3800 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
3801 // Pattern: Diagonal ceiling bottom-right - TRIANGLE shape (objects 0xA3, 0xA8, 0xAC)
3802 // Assembly: RoomDraw_DiagonalCeilingBottomRightA/B with GetSize_1to16_timesA(4)
3803 // count = (size & 0x0F) + 4 per ASM
3804 int count = (obj.size_ & 0x0F) + 4;
3805
3806 if (tiles.empty()) return;
3807
3808 // Row 0 at (x, y): count tiles, Row 1 at (x+1, y-1): count-1 tiles, etc.
3809 int tiles_in_row = count;
3810 for (int row = 0; row < count && tiles_in_row > 0; row++) {
3811 for (int col = 0; col < tiles_in_row; col++) {
3812 WriteTile8(bg, obj.x_ + row + col, obj.y_ - row, tiles[0]);
3813 }
3814 tiles_in_row--;
3815 }
3816}
3817
3818// ============================================================================
3819// Phase 4 Step 5: Special Routines (0xC1, 0xCD, 0xCE, 0xDC)
3820// ============================================================================
3821
3823 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3824 std::span<const gfx::TileInfo> tiles) {
3825 // ASM: RoomDraw_ClosedChestPlatform ($018CC7)
3826 //
3827 // Structure:
3828 // LDA.b $B2 ; Width size
3829 // CLC
3830 // ADC.w #$0004 ; width = B2 + 4
3831 // STA.b $B2
3832 // STA.b $0A
3833 // INC.b $B4 ; height = B4 + 1
3834 // JSR RoomDraw_ChestPlatformHorizontalWallWithCorners
3835 // ... vertical walls on sides ...
3836 //
3837 // The platform has 68 tiles forming a frame + carpet pattern.
3838 // Layer handling uses same $BF check as OpenChestPlatform.
3839
3840 if (tiles.size() < 16) return;
3841
3842 // width = B2 + 4, height = B4 + 1
3843 int width = (obj.size_ & 0x0F) + 4;
3844 int height = ((obj.size_ >> 4) & 0x0F) + 1;
3845
3846 LOG_DEBUG("ObjectDrawer",
3847 "DrawClosedChestPlatform: obj=0x%03X pos=(%d,%d) size=0x%02X width=%d height=%d",
3848 obj.id_, obj.x_, obj.y_, obj.size_, width, height);
3849
3850 // Draw outer frame + interior carpet pattern
3851 // The routine builds a rectangular platform with walls and floor
3852 size_t tile_idx = 0;
3853 for (int row = 0; row < height * 3; ++row) {
3854 for (int col = 0; col < width * 2; ++col) {
3855 WriteTile8(bg, obj.x_ + col, obj.y_ + row, tiles[tile_idx % tiles.size()]);
3856 tile_idx++;
3857 }
3858 }
3859}
3860
3862 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3863 std::span<const gfx::TileInfo> tiles) {
3864 // ASM: RoomDraw_MovingWallWest ($019190)
3865 // Draw vertical wall structure (28 tiles) - always draw in editor
3866 if (tiles.size() < 6) return;
3867
3868 int count = ((obj.size_ >> 4) & 0x0F) + 4;
3869
3870 // Draw wall columns
3871 for (int row = 0; row < count; ++row) {
3872 for (int col = 0; col < 3; ++col) {
3873 size_t tile_idx = (row * 3 + col) % tiles.size();
3874 WriteTile8(bg, obj.x_ + col, obj.y_ + row, tiles[tile_idx]);
3875 }
3876 }
3877}
3878
3880 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3881 std::span<const gfx::TileInfo> tiles) {
3882 // ASM: RoomDraw_MovingWallEast ($01921C)
3883 // Mirror of West - draws from right-to-left
3884 if (tiles.size() < 6) return;
3885
3886 int count = ((obj.size_ >> 4) & 0x0F) + 4;
3887
3888 for (int row = 0; row < count; ++row) {
3889 for (int col = 0; col < 3; ++col) {
3890 size_t tile_idx = (row * 3 + col) % tiles.size();
3891 WriteTile8(bg, obj.x_ - col, obj.y_ + row, tiles[tile_idx]);
3892 }
3893 }
3894}
3895
3897 const RoomObject& obj, gfx::BackgroundBuffer& bg,
3898 std::span<const gfx::TileInfo> tiles) {
3899 // ASM: RoomDraw_OpenChestPlatform ($019733)
3900 //
3901 // Layer handling from ASM:
3902 // LDA.b $BF ; Load current layer pointer
3903 // CMP.w #$4000 ; Compare with lower_layer ($4000)
3904 // BNE .upper_layer ; Skip if upper layer
3905 // TYA
3906 // ORA.w #$2000 ; Set BG2 priority bit for lower layer
3907 // TAY
3908 //
3909 // In the editor, layer handling is done via the object's layer_ field
3910 // and the DrawObject() method routes to the appropriate buffer.
3911 // The BG2 priority bit is not directly applicable here since we use
3912 // separate bitmaps for compositing.
3913
3914 if (tiles.size() < 8) return;
3915
3916 // width = B2 + 1 (ASM: INC.b $B2)
3917 int width = (obj.size_ & 0x0F) + 1;
3918
3919 // segments = (B4 * 2) + 5 (ASM: LDA.b $B4; ASL A; CLC; ADC.w #$0005)
3920 int segments = ((obj.size_ >> 4) & 0x0F) * 2 + 5;
3921
3922 LOG_DEBUG("ObjectDrawer",
3923 "DrawOpenChestPlatform: obj=0x%03X pos=(%d,%d) size=0x%02X width=%d segments=%d",
3924 obj.id_, obj.x_, obj.y_, obj.size_, width, segments);
3925
3926 // ASM: obj0AB4 is the tile data source (21 tiles total)
3927 // The routine draws segments, each segment is a row of 'width' tiles
3928 // Then draws two additional segments at the end (INY INY; JSR .draw_segment; INY INY)
3929
3930 // Draw the main segments
3931 int tile_offset = 0;
3932 for (int seg = 0; seg < segments; ++seg) {
3933 for (int col = 0; col < width; ++col) {
3934 size_t tile_idx = tile_offset % tiles.size();
3935 WriteTile8(bg, obj.x_ + col, obj.y_ + seg, tiles[tile_idx]);
3936 tile_offset++;
3937 }
3938 }
3939
3940 // Draw final two segments (ASM: INY INY; JSR .draw_segment; INY INY; JSR .draw_segment)
3941 // These use tiles at offset +2 and +4 from the current position
3942 tile_offset += 2;
3943 for (int col = 0; col < width; ++col) {
3944 size_t tile_idx = tile_offset % tiles.size();
3945 WriteTile8(bg, obj.x_ + col, obj.y_ + segments, tiles[tile_idx]);
3946 tile_offset++;
3947 }
3948 tile_offset += 2;
3949 for (int col = 0; col < width; ++col) {
3950 size_t tile_idx = tile_offset % tiles.size();
3951 WriteTile8(bg, obj.x_ + col, obj.y_ + segments + 1, tiles[tile_idx]);
3952 tile_offset++;
3953 }
3954}
3955
3956// ============================================================================
3957// Utility Methods
3958// ============================================================================
3959
3961 int tile_y, int pixel_width,
3962 int pixel_height) {
3963 auto& bitmap = bg1.bitmap();
3964 if (!bitmap.is_active() || bitmap.width() == 0) {
3965 LOG_DEBUG("ObjectDrawer", "MarkBG1Transparent: Bitmap not ready, skipping");
3966 return; // Bitmap not ready
3967 }
3968
3969 int start_px = tile_x * 8;
3970 int start_py = tile_y * 8;
3971 int canvas_width = bitmap.width();
3972 int canvas_height = bitmap.height();
3973 auto& data = bitmap.mutable_data();
3974
3975 int pixels_marked = 0;
3976
3977 // Mark pixels as transparent (255) where BG2 overlay objects are drawn
3978 // This creates "holes" in BG1 that allow BG2 content to show through
3979 for (int py = start_py; py < start_py + pixel_height && py < canvas_height;
3980 py++) {
3981 if (py < 0) continue;
3982 for (int px = start_px; px < start_px + pixel_width && px < canvas_width;
3983 px++) {
3984 if (px < 0) continue;
3985 int idx = py * canvas_width + px;
3986 if (idx >= 0 && idx < static_cast<int>(data.size())) {
3987 data[idx] = 255; // 255 = transparent in our compositing system
3988 pixels_marked++;
3989 }
3990 }
3991 }
3992 bitmap.set_modified(true);
3993
3994 LOG_DEBUG("ObjectDrawer",
3995 "MarkBG1Transparent: Marked %d pixels at tile(%d,%d) pixel(%d,%d) size(%d,%d)",
3996 pixels_marked, tile_x, tile_y, start_px, start_py, pixel_width, pixel_height);
3997}
3998
3999void ObjectDrawer::WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
4000 const gfx::TileInfo& tile_info) {
4001 // Draw directly to bitmap instead of tile buffer to avoid being overwritten
4002 auto& bitmap = bg.bitmap();
4003 if (!bitmap.is_active() || bitmap.width() == 0) {
4004 return; // Bitmap not ready
4005 }
4006
4007 // The room-specific graphics buffer (current_gfx16_) contains the assembled
4008 // tile graphics for the current room. Object tile IDs are relative to this
4009 // buffer.
4010 const uint8_t* gfx_data = room_gfx_buffer_;
4011
4012 if (!gfx_data) {
4013 LOG_DEBUG("ObjectDrawer", "ERROR: No graphics data available");
4014 return;
4015 }
4016
4017 // Draw single 8x8 tile directly to bitmap
4018 DrawTileToBitmap(bitmap, tile_info, tile_x * 8, tile_y * 8, gfx_data);
4019
4020 // Also update priority buffer with tile's priority bit
4021 // Priority (over_) affects Z-ordering in SNES Mode 1 compositing
4022 uint8_t priority = tile_info.over_ ? 1 : 0;
4023 int pixel_x = tile_x * 8;
4024 int pixel_y = tile_y * 8;
4025 auto& priority_buffer = bg.mutable_priority_data();
4026 int width = bitmap.width();
4027
4028 // Update priority for each pixel in the 8x8 tile
4029 const auto& bitmap_data = bitmap.vector();
4030 for (int py = 0; py < 8; py++) {
4031 int dest_y = pixel_y + py;
4032 if (dest_y < 0 || dest_y >= bitmap.height()) continue;
4033
4034 for (int px = 0; px < 8; px++) {
4035 int dest_x = pixel_x + px;
4036 if (dest_x < 0 || dest_x >= width) continue;
4037
4038 int dest_index = dest_y * width + dest_x;
4039 // Only set priority for non-transparent pixels
4040 // Check if this pixel was actually drawn (not transparent)
4041 if (dest_index < static_cast<int>(bitmap_data.size()) &&
4042 bitmap_data[dest_index] != 255) {
4043 priority_buffer[dest_index] = priority;
4044 }
4045 }
4046 }
4047}
4048
4049bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const {
4050 return tile_x >= 0 && tile_x < kMaxTilesX && tile_y >= 0 &&
4051 tile_y < kMaxTilesY;
4052}
4053
4055 const gfx::TileInfo& tile_info, int pixel_x,
4056 int pixel_y, const uint8_t* tiledata) {
4057 // Draw an 8x8 tile directly to bitmap at pixel coordinates
4058 // Graphics data is in 8BPP linear format (1 pixel per byte)
4059 if (!tiledata) return;
4060
4061 // DEBUG: Check if bitmap is valid
4062 if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) {
4063 LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d",
4064 bitmap.is_active(), bitmap.width(), bitmap.height());
4065 return;
4066 }
4067
4068 // Calculate tile position in 8BPP graphics buffer
4069 // Layout: 16 tiles per row, each tile is 8 pixels wide (8 bytes)
4070 // Row stride: 128 bytes (16 tiles * 8 bytes)
4071 // Buffer size: 0x10000 (65536 bytes) = 64 tile rows max
4072 constexpr int kGfxBufferSize = 0x10000;
4073 constexpr int kMaxTileRow = 63; // 64 rows (0-63), each 1024 bytes
4074
4075 int tile_col = tile_info.id_ % 16;
4076 int tile_row = tile_info.id_ / 16;
4077
4078 // CRITICAL: Validate tile_row to prevent index out of bounds
4079 if (tile_row > kMaxTileRow) {
4080 LOG_DEBUG("ObjectDrawer", "Tile ID 0x%03X out of bounds (row %d > %d)",
4081 tile_info.id_, tile_row, kMaxTileRow);
4082 return;
4083 }
4084
4085 int tile_base_x = tile_col * 8; // 8 bytes per tile horizontally
4086 int tile_base_y = tile_row * 1024; // 1024 bytes per tile row (8 rows * 128 bytes)
4087
4088 // DEBUG: Log first few tiles being drawn with their graphics data
4089 static int draw_debug_count = 0;
4090 if (draw_debug_count < 5) {
4091 int sample_index = tile_base_y + tile_base_x;
4092 LOG_DEBUG("ObjectDrawer", "DrawTile: id=%d (col=%d,row=%d) gfx_offset=%d (0x%04X)",
4093 tile_info.id_, tile_col, tile_row, sample_index, sample_index);
4094 draw_debug_count++;
4095 }
4096
4097 // Palette offset calculation using 16-color bank chunking (matches SNES CGRAM)
4098 //
4099 // SNES CGRAM layout:
4100 // - Each CGRAM row has 16 colors, with index 0 being transparent
4101 // - Dungeon tiles use palette bits 2-7, mapping to CGRAM rows 2-7
4102 // - We map palette bits 2-7 to SDL banks 0-5
4103 //
4104 // Drawing formula: final_color = pixel + (bank * 16)
4105 // Where pixel 0 = transparent (not written), pixel 1-15 = colors within bank
4106 uint8_t pal = tile_info.palette_ & 0x07;
4107 uint8_t palette_offset;
4108 if (pal >= 2 && pal <= 7) {
4109 // Map palette bits 2-7 to SDL banks 0-5 using 16-color stride
4110 palette_offset = (pal - 2) * 16;
4111 } else {
4112 // Palette 0-1 are for HUD/other - fallback to first bank
4113 palette_offset = 0;
4114 }
4115
4116 // Draw 8x8 pixels
4117 bool any_pixels_written = false;
4118
4119 for (int py = 0; py < 8; py++) {
4120 // Source row with vertical mirroring
4121 int src_row = tile_info.vertical_mirror_ ? (7 - py) : py;
4122
4123 for (int px = 0; px < 8; px++) {
4124 // Source column with horizontal mirroring
4125 int src_col = tile_info.horizontal_mirror_ ? (7 - px) : px;
4126
4127 // Calculate source index in 8BPP buffer
4128 // Stride is 128 bytes (sheet width)
4129 int src_index = (src_row * 128) + src_col + tile_base_x + tile_base_y;
4130 uint8_t pixel = tiledata[src_index];
4131
4132 if (pixel != 0) {
4133 // Pixel 0 is transparent (not written). Pixels 1-15 map to bank indices 1-15.
4134 // With 16-color bank chunking: final_color = pixel + (bank * 16)
4135 uint8_t final_color = pixel + palette_offset;
4136 int dest_x = pixel_x + px;
4137 int dest_y = pixel_y + py;
4138
4139 if (dest_x >= 0 && dest_x < bitmap.width() &&
4140 dest_y >= 0 && dest_y < bitmap.height()) {
4141 int dest_index = dest_y * bitmap.width() + dest_x;
4142 if (dest_index >= 0 &&
4143 dest_index < static_cast<int>(bitmap.mutable_data().size())) {
4144 bitmap.mutable_data()[dest_index] = final_color;
4145 any_pixels_written = true;
4146 }
4147 }
4148 }
4149 }
4150 }
4151
4152 // Mark bitmap as modified if we wrote any pixels
4153 if (any_pixels_written) {
4154 bitmap.set_modified(true);
4155 }
4156}
4157
4158// ============================================================================
4159// Type 3 / Special Routine Implementations
4160// ============================================================================
4161
4164 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
4165 // Pattern: Somaria Line (objects 0x203-0x20F, 0x214)
4166 // Draws a line of tiles based on direction encoded in object ID
4167 // Direction mapping based on ZScream reference:
4168 // 0x203: Horizontal right
4169 // 0x204: Vertical down
4170 // 0x205: Diagonal down-right
4171 // 0x206: Diagonal down-left
4172 // 0x207-0x209: Variations
4173 // 0x20A-0x20C: More variations
4174 // 0x20E-0x20F: Additional patterns
4175 // 0x214: Another line type
4176
4177 if (tiles.empty()) return;
4178
4179 int length = (obj.size_ & 0x0F) + 1;
4180 int obj_subid = obj.id_ & 0x0F; // Low nibble determines direction
4181
4182 // Determine direction based on object sub-ID
4183 int dx = 1, dy = 0; // Default: horizontal right
4184 switch (obj_subid) {
4185 case 0x03: dx = 1; dy = 0; break; // Horizontal right
4186 case 0x04: dx = 0; dy = 1; break; // Vertical down
4187 case 0x05: dx = 1; dy = 1; break; // Diagonal down-right
4188 case 0x06: dx = -1; dy = 1; break; // Diagonal down-left
4189 case 0x07: dx = 1; dy = 0; break; // Horizontal (variant)
4190 case 0x08: dx = 0; dy = 1; break; // Vertical (variant)
4191 case 0x09: dx = 1; dy = 1; break; // Diagonal (variant)
4192 case 0x0A: dx = 1; dy = 0; break; // Horizontal
4193 case 0x0B: dx = 0; dy = 1; break; // Vertical
4194 case 0x0C: dx = 1; dy = 1; break; // Diagonal
4195 case 0x0E: dx = 1; dy = 0; break; // Horizontal
4196 case 0x0F: dx = 0; dy = 1; break; // Vertical
4197 default: dx = 1; dy = 0; break; // Default horizontal
4198 }
4199
4200 // Draw tiles along the path using first tile (Somaria uses single tile)
4201 for (int i = 0; i < length; ++i) {
4202 int tile_idx = i % tiles.size(); // Cycle through tiles if multiple
4203 WriteTile8(bg, obj.x_ + (i * dx), obj.y_ + (i * dy), tiles[tile_idx]);
4204 }
4205}
4206
4209 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
4210 // Pattern: Water Face (Type 3 objects 0xF80-0xF82)
4211 // Draws a 2x2 face in COLUMN-MAJOR order
4212 // TODO: Implement state check from RoomDraw_EmptyWaterFace ($019D29)
4213 // Checks Room ID ($AF), Room State ($7EF000), Door Flags ($0403) to switch graphic
4214 if (tiles.size() >= 4) {
4215 WriteTile8(bg, obj.x_, obj.y_, tiles[0]); // col 0, row 0
4216 WriteTile8(bg, obj.x_, obj.y_ + 1, tiles[1]); // col 0, row 1
4217 WriteTile8(bg, obj.x_ + 1, obj.y_, tiles[2]); // col 1, row 0
4218 WriteTile8(bg, obj.x_ + 1, obj.y_ + 1, tiles[3]); // col 1, row 1
4219 }
4220}
4221
4224 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
4225 // Pattern: 4x4 Corner for Both BG (objects 0x108-0x10F for Type 2)
4226 // Type 3 objects (0xF9B-0xF9D) only have 8 tiles, draw 2x4 pattern
4227 if (tiles.size() >= 16) {
4228 DrawCorner4x4(obj, bg, tiles);
4229 } else if (tiles.size() >= 8) {
4230 // Draw 2x4 corner pattern (column-major)
4231 int tid = 0;
4232 for (int xx = 0; xx < 2; xx++) {
4233 for (int yy = 0; yy < 4; yy++) {
4234 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
4235 }
4236 }
4237 } else if (tiles.size() >= 4) {
4238 // Fallback: 2x2 pattern
4239 DrawWaterFace(obj, bg, tiles);
4240 }
4241}
4242
4244 const RoomObject& obj, gfx::BackgroundBuffer& bg,
4245 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
4246 // Pattern: Weird Corner Bottom (objects 0x110-0x113 for Type 2)
4247 // ASM: RoomDraw_WeirdCornerBottom_BothBG sets count=3, uses 4x4Corner pattern
4248 // Pattern: 3 columns × 4 rows = 12 tiles, column-major order
4249 if (tiles.size() >= 16) {
4250 DrawCorner4x4(obj, bg, tiles);
4251 } else if (tiles.size() >= 12) {
4252 // Draw 3x4 corner pattern (column-major, 3 columns × 4 rows)
4253 int tid = 0;
4254 for (int xx = 0; xx < 3; xx++) {
4255 for (int yy = 0; yy < 4; yy++) {
4256 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
4257 }
4258 }
4259 } else if (tiles.size() >= 8) {
4260 // Fallback: 2x4 column-major pattern
4261 int tid = 0;
4262 for (int xx = 0; xx < 2; xx++) {
4263 for (int yy = 0; yy < 4; yy++) {
4264 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
4265 }
4266 }
4267 } else if (tiles.size() >= 4) {
4268 DrawWaterFace(obj, bg, tiles);
4269 }
4270}
4271
4273 const RoomObject& obj, gfx::BackgroundBuffer& bg,
4274 std::span<const gfx::TileInfo> tiles, [[maybe_unused]] const DungeonState* state) {
4275 // Pattern: Weird Corner Top (objects 0x114-0x117 for Type 2)
4276 // ASM: RoomDraw_WeirdCornerTop_BothBG draws 4 columns of 3 tiles each
4277 // Pattern: 4 columns × 3 rows = 12 tiles, column-major order
4278 if (tiles.size() >= 16) {
4279 DrawCorner4x4(obj, bg, tiles);
4280 } else if (tiles.size() >= 12) {
4281 // Draw 4x3 corner pattern (column-major, 4 columns × 3 rows)
4282 int tid = 0;
4283 for (int xx = 0; xx < 4; xx++) {
4284 for (int yy = 0; yy < 3; yy++) {
4285 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
4286 }
4287 }
4288 } else if (tiles.size() >= 8) {
4289 // Fallback: 2x4 column-major pattern
4290 int tid = 0;
4291 for (int xx = 0; xx < 2; xx++) {
4292 for (int yy = 0; yy < 4; yy++) {
4293 WriteTile8(bg, obj.x_ + xx, obj.y_ + yy, tiles[tid++]);
4294 }
4295 }
4296 } else if (tiles.size() >= 4) {
4297 DrawWaterFace(obj, bg, tiles);
4298 }
4299}
4300
4303 std::span<const gfx::TileInfo> tiles,
4304 int width, int height) {
4305 // Generic large object drawer
4306 if (tiles.size() >= static_cast<size_t>(width * height)) {
4307 for (int y = 0; y < height; ++y) {
4308 for (int x = 0; x < width; ++x) {
4309 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[y * width + x]);
4310 }
4311 }
4312 }
4313}
4314
4315// ============================================================================
4316// Type 2 Special Object Routines
4317// ============================================================================
4318
4321 std::span<const gfx::TileInfo> tiles,
4322 [[maybe_unused]] const DungeonState* state) {
4323 // Pattern: 4 wide x 5 tall (20 tiles) in column-major order
4324 // Objects: 0x122, 0x128
4325 constexpr int kWidth = 4;
4326 constexpr int kHeight = 5;
4327
4328 if (tiles.size() >= kWidth * kHeight) {
4329 // Column-major layout (tiles go down first, then across)
4330 int tid = 0;
4331 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4332 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4333 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4334 }
4335 }
4336 } else {
4337 // Fallback: use 4x4 pattern
4338 DrawRightwards4x4_1to16(obj, bg, tiles);
4339 }
4340}
4341
4344 std::span<const gfx::TileInfo> tiles,
4345 [[maybe_unused]] const DungeonState* state) {
4346 // Pattern: 3 wide x 6 tall (18 tiles) in column-major order
4347 // Objects: 0x12C, 0x236, 0x237
4348 constexpr int kWidth = 3;
4349 constexpr int kHeight = 6;
4350
4351 if (tiles.size() >= kWidth * kHeight) {
4352 int tid = 0;
4353 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4354 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4355 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4356 }
4357 }
4358 } else {
4359 // Fallback: use available tiles in 4x4 pattern
4360 DrawRightwards4x4_1to16(obj, bg, tiles);
4361 }
4362}
4363
4366 std::span<const gfx::TileInfo> tiles,
4367 [[maybe_unused]] const DungeonState* state) {
4368 // Pattern: 6 wide x 3 tall (18 tiles) in row-major order
4369 // Objects: 0x13E, 0x24D, 0x25D
4370 constexpr int kWidth = 6;
4371 constexpr int kHeight = 3;
4372
4373 if (tiles.size() >= kWidth * kHeight) {
4374 int tid = 0;
4375 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4376 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4377 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4378 }
4379 }
4380 } else {
4381 // Fallback: use 4x3 pattern
4383 }
4384}
4385
4388 std::span<const gfx::TileInfo> tiles,
4389 [[maybe_unused]] const DungeonState* state) {
4390 // Pattern: 3 wide x 5 tall (15 tiles) in column-major order
4391 // Objects: 0x255, 0x25B
4392 constexpr int kWidth = 3;
4393 constexpr int kHeight = 5;
4394
4395 if (tiles.size() >= kWidth * kHeight) {
4396 int tid = 0;
4397 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4398 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4399 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4400 }
4401 }
4402 } else {
4403 // Fallback: use 4x4 pattern
4404 DrawRightwards4x4_1to16(obj, bg, tiles);
4405 }
4406}
4407
4408// ============================================================================
4409// Type 3 Special Object Routines
4410// ============================================================================
4411
4414 std::span<const gfx::TileInfo> tiles,
4415 [[maybe_unused]] const DungeonState* state) {
4416 // Pattern: Vertical pipe - 2 wide x 6 tall (12 tiles) in column-major order
4417 // Objects: 0xFBA, 0xFBB (ASM 23A, 23B)
4418 constexpr int kWidth = 2;
4419 constexpr int kHeight = 6;
4420
4421 if (tiles.size() >= kWidth * kHeight) {
4422 int tid = 0;
4423 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4424 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4425 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4426 }
4427 }
4428 } else if (tiles.size() >= 4) {
4429 // Fallback: draw as 2x2
4430 DrawRightwards2x2_1to16(obj, bg, tiles);
4431 }
4432}
4433
4436 std::span<const gfx::TileInfo> tiles,
4437 [[maybe_unused]] const DungeonState* state) {
4438 // Pattern: Horizontal pipe - 6 wide x 2 tall (12 tiles) in row-major order
4439 // Objects: 0xFBC, 0xFBD, 0xFDC (ASM 23C, 23D, 25C)
4440 constexpr int kWidth = 6;
4441 constexpr int kHeight = 2;
4442
4443 if (tiles.size() >= kWidth * kHeight) {
4444 int tid = 0;
4445 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4446 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4447 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4448 }
4449 }
4450 } else if (tiles.size() >= 4) {
4451 // Fallback: draw as 2x2
4452 DrawRightwards2x2_1to16(obj, bg, tiles);
4453 }
4454}
4455
4458 std::span<const gfx::TileInfo> tiles,
4459 [[maybe_unused]] const DungeonState* state) {
4460 // Pattern: Light beam on floor - 4x4 grid
4461 // Objects: 0xFF0 (ASM 270)
4462 // Standard 4x4 pattern, but tiles may have special transparency
4463 constexpr int kWidth = 4;
4464 constexpr int kHeight = 4;
4465
4466 if (tiles.size() >= kWidth * kHeight) {
4467 int tid = 0;
4468 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4469 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4470 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4471 }
4472 }
4473 } else {
4474 // Fallback
4475 DrawRightwards4x4_1to16(obj, bg, tiles);
4476 }
4477}
4478
4481 std::span<const gfx::TileInfo> tiles,
4482 [[maybe_unused]] const DungeonState* state) {
4483 // Pattern: Big light beam - 6x6 grid
4484 // Objects: 0xFF1 (ASM 271)
4485 constexpr int kWidth = 6;
4486 constexpr int kHeight = 6;
4487
4488 if (tiles.size() >= kWidth * kHeight) {
4489 int tid = 0;
4490 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4491 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4492 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4493 }
4494 }
4495 } else {
4496 // Fallback: use 4x4 pattern
4497 DrawRightwards4x4_1to16(obj, bg, tiles);
4498 }
4499}
4500
4503 std::span<const gfx::TileInfo> tiles,
4504 [[maybe_unused]] const DungeonState* state) {
4505 // Pattern: Boss shell (Trinexx, Vitreous, Kholdstare) - 4x4 grid
4506 // Objects: 0xFF2 (Trinexx 272), 0xF95 (Kholdstare 215), 0xFFB (Vitreous 27B)
4507 // Uses standard 4x4 pattern but may have state-dependent tile selection
4508 DrawRightwards4x4_1to16(obj, bg, tiles);
4509}
4510
4513 std::span<const gfx::TileInfo> tiles,
4514 [[maybe_unused]] const DungeonState* state) {
4515 // Pattern: Solid wall decoration - 3 wide x 4 tall (12 tiles) in column-major order
4516 // Objects: 0xFE9-0xFEA, 0xFEE-0xFEF (ASM 269-26A, 26E-26F)
4517 constexpr int kWidth = 3;
4518 constexpr int kHeight = 4;
4519
4520 if (tiles.size() >= kWidth * kHeight) {
4521 int tid = 0;
4522 for (int x = 0; x < kWidth && tid < static_cast<int>(tiles.size()); ++x) {
4523 for (int y = 0; y < kHeight && tid < static_cast<int>(tiles.size()); ++y) {
4524 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4525 }
4526 }
4527 } else {
4528 // Fallback: use 4x4 pattern
4529 DrawRightwards4x4_1to16(obj, bg, tiles);
4530 }
4531}
4532
4535 std::span<const gfx::TileInfo> tiles,
4536 [[maybe_unused]] const DungeonState* state) {
4537 // Pattern: Archery Game Target Door - two 3x3 sections stacked vertically
4538 // ASM: RoomDraw_ArcheryGameTargetDoor at $01A7A3:
4539 // 1. LDA #$0003, JSR RoomDraw_1x3N_rightwards -> 3 columns of 3 tiles (9 tiles)
4540 // 2. Y += 0x180 (0x180 / 0x80 = 3 rows down)
4541 // 3. LDA #$0003, JMP RoomDraw_1x3N_rightwards -> another 3 columns of 3 tiles
4542 //
4543 // RoomDraw_1x3N_rightwards draws COLUMN-MAJOR: each column is 3 tiles tall
4544 // Tile order: col0[row0,row1,row2], col1[row0,row1,row2], col2[row0,row1,row2]
4545 // Objects: 0xFE0-0xFE1 (ASM 260-261)
4546 // Total: 3 wide x 6 tall (18 tiles) in column-major order per section
4547 constexpr int kWidth = 3;
4548 constexpr int kSectionHeight = 3;
4549
4550 if (tiles.size() >= 18) {
4551 int tid = 0;
4552 // Section 1: Top 3x3 in COLUMN-MAJOR order
4553 for (int x = 0; x < kWidth; ++x) {
4554 for (int y = 0; y < kSectionHeight; ++y) {
4555 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4556 }
4557 }
4558
4559 // Section 2: Bottom 3x3 in COLUMN-MAJOR order (continues tile data)
4560 for (int x = 0; x < kWidth; ++x) {
4561 for (int y = 0; y < kSectionHeight; ++y) {
4562 WriteTile8(bg, obj.x_ + x, obj.y_ + kSectionHeight + y, tiles[tid++]);
4563 }
4564 }
4565 } else if (tiles.size() >= 9) {
4566 // Draw just the first section if not enough tiles
4567 int tid = 0;
4568 for (int x = 0; x < kWidth; ++x) {
4569 for (int y = 0; y < kSectionHeight; ++y) {
4570 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4571 }
4572 }
4573 } else {
4574 // Fallback: use 4x4 pattern
4575 DrawRightwards4x4_1to16(obj, bg, tiles);
4576 }
4577}
4578
4581 std::span<const gfx::TileInfo> tiles,
4582 [[maybe_unused]] const DungeonState* state) {
4583 // Pattern: Single 2x2 block (NO repetition based on size)
4584 // Used for pots, statues, and other single-instance 2x2 objects
4585 // ASM: RoomDraw_Rightwards2x2 at $019895 draws 4 tiles once
4586 // Tile order is COLUMN-MAJOR: [col0_row0, col0_row1, col1_row0, col1_row1]
4587 if (tiles.size() >= 4) {
4588 WriteTile8(bg, obj.x_, obj.y_, tiles[0]); // col 0, row 0
4589 WriteTile8(bg, obj.x_, obj.y_ + 1, tiles[1]); // col 0, row 1
4590 WriteTile8(bg, obj.x_ + 1, obj.y_, tiles[2]); // col 1, row 0
4591 WriteTile8(bg, obj.x_ + 1, obj.y_ + 1, tiles[3]); // col 1, row 1
4592 }
4593}
4594
4597 std::span<const gfx::TileInfo> tiles,
4598 [[maybe_unused]] const DungeonState* state) {
4599 // Pattern: Single 4x4 block in TILE16 terms = 8x8 in TILE8 terms
4600 // ASM: RoomDraw_4x4 draws in TILE16 units (each tile16 is 2x2 tile8s)
4601 // So 4x4 tile16 = 8x8 tile8 = 64x64 pixels
4602 // Tile order is COLUMN-MAJOR: tiles advance down each column, then right
4603
4604 // For 8x8 pattern (64 tiles needed), or use available tiles with wrapping
4605 int tid = 0;
4606 int num_tiles = static_cast<int>(tiles.size());
4607 if (num_tiles == 0) return;
4608
4609 for (int x = 0; x < 8; ++x) {
4610 for (int y = 0; y < 8; ++y) {
4611 // Wrap tile index if not enough tiles
4612 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid % num_tiles]);
4613 tid++;
4614 }
4615 }
4616}
4617
4620 std::span<const gfx::TileInfo> tiles,
4621 [[maybe_unused]] const DungeonState* state) {
4622 // Pattern: Single 4x3 block (NO repetition based on size)
4623 // ASM: RoomDraw_TableRock4x3 at $0199E6 - draws 4 columns of 3 rows (12 tiles)
4624 // Calls RoomDraw_1x3N_rightwards with A=4, which draws 4 columns of 3 rows
4625 // Tile order is COLUMN-MAJOR: tiles advance down each column, then right
4626 if (tiles.size() >= 12) {
4627 int tid = 0;
4628 for (int x = 0; x < 4; ++x) {
4629 for (int y = 0; y < 3; ++y) {
4630 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4631 }
4632 }
4633 }
4634}
4635
4638 std::span<const gfx::TileInfo> tiles,
4639 [[maybe_unused]] const DungeonState* state) {
4640 // ASM: RoomDraw_RupeeFloor at $019AA9
4641 // Draws 3 columns of tile pairs, each pair drawn at 3 Y positions:
4642 // Row 0-1: First pair
4643 // Row 3-4: Second pair (row 2 is skipped)
4644 // Row 6-7: Third pair (row 5 is skipped)
4645 // Uses 2 tiles per column (tile 0 and tile 1), drawn 3 times each
4646 // Total visual: 6 tiles wide x 8 tiles tall (with gaps at rows 2 and 5)
4647
4648 // Note: Original ASM checks room flags to hide if rupees collected
4649 // For editor preview, we always draw the pattern
4650
4651 if (tiles.size() < 2) return;
4652
4653 // Draw 3 columns (INX x4 = 2 tiles per iteration, 3 iterations)
4654 for (int col = 0; col < 3; ++col) {
4655 int x = obj.x_ + (col * 2); // 2 tiles per column (left and right of pair)
4656
4657 // Draw the tile pair at 3 Y positions
4658 // Rows 0-1
4659 WriteTile8(bg, x, obj.y_, tiles[0]);
4660 WriteTile8(bg, x + 1, obj.y_, tiles[0]);
4661 WriteTile8(bg, x, obj.y_ + 1, tiles[1]);
4662 WriteTile8(bg, x + 1, obj.y_ + 1, tiles[1]);
4663
4664 // Rows 3-4 (skip row 2)
4665 WriteTile8(bg, x, obj.y_ + 3, tiles[0]);
4666 WriteTile8(bg, x + 1, obj.y_ + 3, tiles[0]);
4667 WriteTile8(bg, x, obj.y_ + 4, tiles[1]);
4668 WriteTile8(bg, x + 1, obj.y_ + 4, tiles[1]);
4669
4670 // Rows 6-7 (skip row 5)
4671 WriteTile8(bg, x, obj.y_ + 6, tiles[0]);
4672 WriteTile8(bg, x + 1, obj.y_ + 6, tiles[0]);
4673 WriteTile8(bg, x, obj.y_ + 7, tiles[1]);
4674 WriteTile8(bg, x + 1, obj.y_ + 7, tiles[1]);
4675 }
4676}
4677
4680 std::span<const gfx::TileInfo> tiles,
4681 [[maybe_unused]] const DungeonState* state) {
4682 // ASM: RoomDraw_4x4 at $0197ED - draws exactly 4 columns x 4 rows = 16 tiles
4683 // This is an actual 4x4 tile8 pattern (32x32 pixels), NO repetition
4684 // Used for: 0xFE6 (pit) and other single 4x4 objects
4685 // Tile order is COLUMN-MAJOR: tiles advance down each column, then right
4686
4687 if (tiles.size() < 16) return;
4688
4689 int tid = 0;
4690 for (int x = 0; x < 4; ++x) {
4691 for (int y = 0; y < 4; ++y) {
4692 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4693 }
4694 }
4695}
4696
4699 std::span<const gfx::TileInfo> tiles,
4700 [[maybe_unused]] const DungeonState* state) {
4701 // Pattern: Ganon's Triforce Floor Decoration - THREE 4x4 blocks forming Triforce
4702 // ASM: RoomDraw_GanonTriforceFloorDecor at $01A7F0:
4703 // 1. JSR RoomDraw_4x4 -> Block 1 at Y, X advances to tiles 16
4704 // 2. PHX -> Save X (now at tiles 16)
4705 // 3. Y += 0x1FC, JSR RoomDraw_4x4 -> Block 2 with tiles 16-31
4706 // 4. PLX -> Restore X back to tiles 16
4707 // 5. Y += 0x204, JMP RoomDraw_4x4 -> Block 3 with tiles 16-31 (SAME as block 2!)
4708 //
4709 // VRAM layout: 0x80 bytes per row (64 tiles @ 2 bytes each = 128 bytes)
4710 // 0x1FC = 508 -> ~4 rows down, -2 cols (124 mod 128 = wraps left)
4711 // 0x204 = 516 -> ~4 rows down, +2 cols
4712 //
4713 // Visual pattern (Triforce):
4714 // [Block 1] <- Top center (tiles 0-15)
4715 // [Block 2][Block 3] <- Bottom row (BOTH use tiles 16-31!)
4716 constexpr int kBlockSize = 4;
4717
4718 if (tiles.size() >= 32) {
4719 // Block 1: Top center (tiles 0-15)
4720 int tid = 0;
4721 for (int y = 0; y < kBlockSize; ++y) {
4722 for (int x = 0; x < kBlockSize; ++x) {
4723 WriteTile8(bg, obj.x_ + x + 2, obj.y_ + y, tiles[tid++]);
4724 }
4725 }
4726
4727 // Block 2: Bottom-left (tiles 16-31)
4728 tid = 16;
4729 for (int y = 0; y < kBlockSize; ++y) {
4730 for (int x = 0; x < kBlockSize; ++x) {
4731 WriteTile8(bg, obj.x_ + x, obj.y_ + kBlockSize + y, tiles[tid++]);
4732 }
4733 }
4734
4735 // Block 3: Bottom-right (SAME tiles 16-31, per PLX restoring X)
4736 tid = 16;
4737 for (int y = 0; y < kBlockSize; ++y) {
4738 for (int x = 0; x < kBlockSize; ++x) {
4739 WriteTile8(bg, obj.x_ + x + 4, obj.y_ + kBlockSize + y, tiles[tid++]);
4740 }
4741 }
4742 } else if (tiles.size() >= 16) {
4743 // Draw just one 4x4 block if not enough tiles
4744 int tid = 0;
4745 for (int y = 0; y < kBlockSize; ++y) {
4746 for (int x = 0; x < kBlockSize; ++x) {
4747 WriteTile8(bg, obj.x_ + x, obj.y_ + y, tiles[tid++]);
4748 }
4749 }
4750 } else {
4751 // Fallback
4752 DrawRightwards4x4_1to16(obj, bg, tiles);
4753 }
4754}
4755
4756} // namespace zelda3
4757} // namespace yaze
4758
4760 if (!routines_initialized_) {
4762 }
4763
4764 // Default size 16x16 (2x2 tiles)
4765 int width = 16;
4766 int height = 16;
4767
4768 int routine_id = GetDrawRoutineId(object.id_);
4769 int size = object.size_;
4770
4771 // Based on routine ID, calculate dimensions
4772 // This logic must match the draw routines
4773 switch (routine_id) {
4774 case 0: // DrawRightwards2x2_1to15or32
4775 case 4: // DrawRightwards2x2_1to16
4776 case 7: // DrawDownwards2x2_1to15or32
4777 case 11: // DrawDownwards2x2_1to16
4778 // 2x2 tiles repeated
4779 if (routine_id == 0 || routine_id == 7) {
4780 if (size == 0) size = 32;
4781 } else {
4782 size = size & 0x0F;
4783 if (size == 0) size = 16; // 0 usually means 16 for 1to16 routines
4784 }
4785
4786 if (routine_id == 0 || routine_id == 4) {
4787 // Rightwards: size * 2 tiles width, 2 tiles height
4788 width = size * 16;
4789 height = 16;
4790 } else {
4791 // Downwards: 2 tiles width, size * 2 tiles height
4792 width = 16;
4793 height = size * 16;
4794 }
4795 break;
4796
4797 case 1: // RoomDraw_Rightwards2x4_1to15or26 (layout walls 0x01-0x02)
4798 {
4799 // ASM: GetSize_1to15or26 - defaults to 26 when size is 0
4800 int effective_size = (size == 0) ? 26 : (size & 0x0F);
4801 // Draws 2x4 tiles repeated 'effective_size' times horizontally
4802 width = effective_size * 16; // 2 tiles wide per repetition
4803 height = 32; // 4 tiles tall
4804 break;
4805 }
4806 case 2: // RoomDraw_Rightwards2x4spaced4_1to16 (objects 0x03-0x04)
4807 case 3: // RoomDraw_Rightwards2x4spaced4_1to16_BothBG (objects 0x05-0x06)
4808 {
4809 // ASM: GetSize_1to16, draws 2x4 tiles with 4-tile adjacent spacing
4810 size = size & 0x0F;
4811 int count = size + 1;
4812 width = count * 16; // 2 tiles wide per repetition (adjacent)
4813 height = 32; // 4 tiles tall
4814 break;
4815 }
4816
4817 case 5: // DrawDiagonalAcute_1to16
4818 case 6: // DrawDiagonalGrave_1to16
4819 {
4820 // ASM: RoomDraw_DiagonalAcute/Grave_1to16
4821 // Uses LDA #$0007; JSR RoomDraw_GetSize_1to16_timesA
4822 // count = size + 7
4823 // Each iteration draws 5 tiles vertically (RoomDraw_2x2and1 pattern)
4824 // Width = count tiles, Height = 5 tiles base + (count-1) diagonal offset
4825 size = size & 0x0F;
4826 int count = size + 7;
4827 width = count * 8;
4828 height = (count + 4) * 8; // 5 tiles + (count-1) = count + 4
4829 break;
4830 }
4831 case 17: // DrawDiagonalAcute_1to16_BothBG
4832 case 18: // DrawDiagonalGrave_1to16_BothBG
4833 {
4834 // ASM: RoomDraw_DiagonalAcute/Grave_1to16_BothBG
4835 // Uses LDA #$0006; JSR RoomDraw_GetSize_1to16_timesA
4836 // count = size + 6 (one less than non-BothBG)
4837 size = size & 0x0F;
4838 int count = size + 6;
4839 width = count * 8;
4840 height = (count + 4) * 8; // 5 tiles + (count-1) = count + 4
4841 break;
4842 }
4843
4844 case 8: // RoomDraw_Downwards4x2_1to15or26 (layout walls 0x61-0x62)
4845 {
4846 // ASM: GetSize_1to15or26 - defaults to 26 when size is 0
4847 int effective_size = (size == 0) ? 26 : (size & 0x0F);
4848 // Draws 4x2 tiles repeated 'effective_size' times vertically
4849 width = 32; // 4 tiles wide
4850 height = effective_size * 16; // 2 tiles tall per repetition
4851 break;
4852 }
4853 case 9: // RoomDraw_Downwards4x2_1to16_BothBG (objects 0x63-0x64)
4854 case 10: // RoomDraw_DownwardsDecor4x2spaced4_1to16 (objects 0x65-0x66)
4855 {
4856 // ASM: GetSize_1to16, draws 4x2 tiles with spacing
4857 size = size & 0x0F;
4858 int count = size + 1;
4859 width = 32; // 4 tiles wide
4860 height = count * 16; // 2 tiles tall per repetition (adjacent)
4861 break;
4862 }
4863
4864 case 12: // 2x2 downwards extended
4865 case 13:
4866 case 14:
4867 case 15:
4868 // 2x2 base with size extension downward
4869 size = size & 0x0F;
4870 width = 16;
4871 height = 16 + size * 16;
4872 break;
4873
4874 case 16: // DrawRightwards4x4_1to16 (Routine 16)
4875 {
4876 // 4x4 block repeated horizontally based on size
4877 // ASM: GetSize_1to16, count = (size & 0x0F) + 1
4878 int count = (size & 0x0F) + 1;
4879 width = 32 * count; // 4 tiles * 8 pixels * count
4880 height = 32; // 4 tiles * 8 pixels
4881 break;
4882 }
4883 case 19: // DrawCorner4x4 (Type 2 corners 0x100-0x103)
4884 case 34: // Water Face (4x4)
4885 case 35: // 4x4 Corner BothBG
4886 case 36: // Weird Corner Bottom
4887 case 37: // Weird Corner Top
4888 // 4x4 tiles (32x32 pixels) - fixed size, no repetition
4889 width = 32;
4890 height = 32;
4891 break;
4892 case 39: { // Chest routine (small or big)
4893 // Infer size from tile span: big chests provide >=16 tiles
4894 int tile_count = object.tiles().size();
4895 if (tile_count >= 16) {
4896 width = height = 32; // Big chest 4x4
4897 } else {
4898 width = height = 16; // Small chest 2x2
4899 }
4900 break;
4901 }
4902
4903 case 20: // Edge 1x2 (RoomDraw_Rightwards1x2_1to16_plus2)
4904 {
4905 // ASM: count = (size * 2) + 1, draws 1x2 tiles
4906 size = size & 0x0F;
4907 int count = (size * 2) + 1;
4908 width = count * 8;
4909 height = 16;
4910 break;
4911 }
4912
4913 case 21: // RoomDraw_RightwardsHasEdge1x1_1to16_plus3 (small rails 0x22)
4914 {
4915 // ASM: GetSize_1to16_timesA(2), count = (size + 1) * 2
4916 // Plus corner (1) + end (1) = count + 2 total width
4917 size = size & 0x0F;
4918 int count = (size + 1) * 2;
4919 width = (count + 2) * 8; // corner + middle*count + end
4920 height = 8;
4921 break;
4922 }
4923 case 22: // RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (carpet trim 0x23-0x2E)
4924 {
4925 // ASM: GetSize_1to16, count = size + 1
4926 // Plus corner (1) + end (1) = count + 2 total width
4927 size = size & 0x0F;
4928 int count = size + 1;
4929 width = (count + 2) * 8; // corner + middle*count + end
4930 height = 8;
4931 break;
4932 }
4933 case 25: // RoomDraw_Rightwards1x1Solid_1to16_plus3
4934 {
4935 // ASM: GetSize_1to16_timesA(4), so count = size + 4
4936 size = size & 0x0F;
4937 width = (size + 4) * 8;
4938 height = 8;
4939 break;
4940 }
4941
4942 case 23: // RightwardsTopCorners1x2_1to16_plus13
4943 case 24: // RightwardsBottomCorners1x2_1to16_plus13
4944 size = size & 0x0F;
4945 width = 8 + size * 8;
4946 height = 16;
4947 break;
4948
4949 case 26: // Door Switcher
4950 width = 32;
4951 height = 32;
4952 break;
4953
4954 case 27: // RoomDraw_RightwardsDecor4x4spaced2_1to16
4955 {
4956 // 4x4 tiles with 6-tile X spacing per repetition
4957 // ASM: s * 6 spacing, count = size + 1
4958 size = size & 0x0F;
4959 int count = size + 1;
4960 // Total width = (count - 1) * 6 (spacing) + 4 (last block)
4961 width = ((count - 1) * 6 + 4) * 8;
4962 height = 32; // 4 tiles
4963 break;
4964 }
4965
4966 case 28: // RoomDraw_RightwardsStatue2x3spaced2_1to16
4967 {
4968 // 2x3 tiles with 4-tile X spacing per repetition
4969 // ASM: s * 4 spacing, count = size + 1
4970 size = size & 0x0F;
4971 int count = size + 1;
4972 // Total width = (count - 1) * 4 (spacing) + 2 (last block)
4973 width = ((count - 1) * 4 + 2) * 8;
4974 height = 24; // 3 tiles
4975 break;
4976 }
4977
4978 case 29: // RoomDraw_RightwardsPillar2x4spaced4_1to16
4979 {
4980 // 2x4 tiles with 4-tile X spacing per repetition
4981 // ASM: ADC #$0008 = 4 tiles between starts
4982 size = size & 0x0F;
4983 int count = size + 1;
4984 // Total width = (count - 1) * 4 (spacing) + 2 (last block)
4985 width = ((count - 1) * 4 + 2) * 8;
4986 height = 32; // 4 tiles
4987 break;
4988 }
4989
4990 case 30: // RoomDraw_RightwardsDecor4x3spaced4_1to16
4991 {
4992 // 4x3 tiles with 8-tile X spacing per repetition
4993 // ASM: ADC #$0008 = 8-byte advance = 4 tiles gap between 4-tile objects
4994 size = size & 0x0F;
4995 int count = size + 1;
4996 // Total width = (count - 1) * 8 (spacing) + 4 (last block)
4997 width = ((count - 1) * 8 + 4) * 8;
4998 height = 24; // 3 tiles
4999 break;
5000 }
5001
5002 case 31: // RoomDraw_RightwardsDoubled2x2spaced2_1to16
5003 {
5004 // 4x2 tiles (doubled 2x2) with 6-tile X spacing
5005 // ASM: s * 6 spacing, count = size + 1
5006 size = size & 0x0F;
5007 int count = size + 1;
5008 // Total width = (count - 1) * 6 (spacing) + 4 (last block)
5009 width = ((count - 1) * 6 + 4) * 8;
5010 height = 16; // 2 tiles
5011 break;
5012 }
5013 case 32: // RoomDraw_RightwardsDecor2x2spaced12_1to16
5014 {
5015 // 2x2 tiles with 14-tile X spacing per repetition
5016 // ASM: s * 14 spacing, count = size + 1
5017 size = size & 0x0F;
5018 int count = size + 1;
5019 // Total width = (count - 1) * 14 (spacing) + 2 (last block)
5020 width = ((count - 1) * 14 + 2) * 8;
5021 height = 16; // 2 tiles
5022 break;
5023 }
5024
5025 case 33: // Somaria Line
5026 // Variable length, estimate from size
5027 size = size & 0x0F;
5028 width = 8 + size * 8;
5029 height = 8;
5030 break;
5031
5032 case 38: // Nothing (RoomDraw_Nothing)
5033 width = 8;
5034 height = 8;
5035 break;
5036
5037 case 40: // Rightwards 4x2 (FloorTile)
5038 {
5039 // 4 cols x 2 rows, GetSize_1to16
5040 size = size & 0x0F;
5041 int count = size + 1;
5042 width = count * 4 * 8; // 4 tiles per repetition
5043 height = 16; // 2 tiles
5044 break;
5045 }
5046
5047 case 41: // Rightwards Decor 1x8 spaced 12 (wall torches 0x55-0x56)
5048 {
5049 // ASM: 1 column x 8 rows with 12-tile horizontal spacing
5050 size = size & 0x0F;
5051 int count = size + 1;
5052 width = ((count - 1) * 12 + 1) * 8; // 1 tile wide per block
5053 height = 64; // 8 tiles tall
5054 break;
5055 }
5056
5057 case 42: // Rightwards Cannon Hole 4x3
5058 {
5059 // 4x3 tiles, GetSize_1to16
5060 size = size & 0x0F;
5061 int count = size + 1;
5062 width = count * 4 * 8;
5063 height = 24;
5064 break;
5065 }
5066
5067 case 43: // Downwards Floor 4x4
5068 {
5069 // 4x4 tiles, GetSize_1to16
5070 size = size & 0x0F;
5071 int count = size + 1;
5072 width = 32;
5073 height = count * 4 * 8;
5074 break;
5075 }
5076
5077 case 44: // Downwards 1x1 Solid +3
5078 {
5079 size = size & 0x0F;
5080 width = 8;
5081 height = (size + 4) * 8;
5082 break;
5083 }
5084
5085 case 45: // Downwards Decor 4x4 spaced 2
5086 {
5087 size = size & 0x0F;
5088 int count = size + 1;
5089 width = 32;
5090 height = ((count - 1) * 6 + 4) * 8;
5091 break;
5092 }
5093
5094 case 46: // Downwards Pillar 2x4 spaced 2
5095 {
5096 size = size & 0x0F;
5097 int count = size + 1;
5098 width = 16;
5099 height = ((count - 1) * 6 + 4) * 8;
5100 break;
5101 }
5102
5103 case 47: // Downwards Decor 3x4 spaced 4
5104 {
5105 size = size & 0x0F;
5106 int count = size + 1;
5107 width = 24;
5108 height = ((count - 1) * 6 + 4) * 8;
5109 break;
5110 }
5111
5112 case 48: // Downwards Decor 2x2 spaced 12
5113 {
5114 size = size & 0x0F;
5115 int count = size + 1;
5116 width = 16;
5117 height = ((count - 1) * 14 + 2) * 8;
5118 break;
5119 }
5120
5121 case 49: // Downwards Line 1x1 +1
5122 {
5123 size = size & 0x0F;
5124 width = 8;
5125 height = (size + 2) * 8;
5126 break;
5127 }
5128
5129 case 50: // Downwards Decor 2x4 spaced 8
5130 {
5131 size = size & 0x0F;
5132 int count = size + 1;
5133 width = 16;
5134 height = ((count - 1) * 12 + 4) * 8;
5135 break;
5136 }
5137
5138 case 51: // Rightwards Line 1x1 +1
5139 {
5140 size = size & 0x0F;
5141 width = (size + 2) * 8;
5142 height = 8;
5143 break;
5144 }
5145
5146 case 52: // Rightwards Bar 4x3
5147 {
5148 size = size & 0x0F;
5149 int count = size + 1;
5150 width = ((count - 1) * 6 + 4) * 8;
5151 height = 24;
5152 break;
5153 }
5154
5155 case 53: // Rightwards Shelf 4x4
5156 {
5157 size = size & 0x0F;
5158 int count = size + 1;
5159 width = ((count - 1) * 6 + 4) * 8;
5160 height = 32;
5161 break;
5162 }
5163
5164 case 54: // Rightwards Big Rail 1x3 +5
5165 {
5166 size = size & 0x0F;
5167 width = (size + 6) * 8;
5168 height = 24;
5169 break;
5170 }
5171
5172 case 55: // Rightwards Block 2x2 spaced 2
5173 {
5174 size = size & 0x0F;
5175 int count = size + 1;
5176 width = ((count - 1) * 4 + 2) * 8;
5177 height = 16;
5178 break;
5179 }
5180
5181 // Routines 56-64: SuperSquare patterns
5182 // ASM: These routines use size_x = (size & 0x0F) + 1, size_y = ((size >> 4) & 0x0F) + 1
5183 // Each super square unit is 4 tiles (32 pixels) in each dimension
5184 case 56: // 4x4BlocksIn4x4SuperSquare
5185 case 57: // 3x3FloorIn4x4SuperSquare
5186 case 58: // 4x4FloorIn4x4SuperSquare
5187 case 59: // 4x4FloorOneIn4x4SuperSquare
5188 case 60: // 4x4FloorTwoIn4x4SuperSquare
5189 case 62: // Spike2x2In4x4SuperSquare
5190 {
5191 int size_x = (size & 0x0F) + 1;
5192 int size_y = ((size >> 4) & 0x0F) + 1;
5193 width = size_x * 32; // 4 tiles per super square
5194 height = size_y * 32; // 4 tiles per super square
5195 break;
5196 }
5197 case 61: // BigHole4x4
5198 case 63: // TableRock4x4
5199 case 64: // WaterOverlay8x8
5200 width = 32;
5201 height = 32;
5202 break;
5203
5204 // Routines 65-74: Various downwards/rightwards patterns
5205 case 65: // DownwardsDecor3x4spaced2
5206 {
5207 size = size & 0x0F;
5208 int count = size + 1;
5209 width = 24;
5210 height = ((count - 1) * 5 + 4) * 8;
5211 break;
5212 }
5213
5214 case 66: // DownwardsBigRail3x1 +5
5215 {
5216 // Top cap (2x2) + Middle (2x1 x count) + Bottom cap (2x3)
5217 // Total: 2 tiles wide, 2 + (size+1) + 3 = size + 6 tiles tall
5218 size = size & 0x0F;
5219 width = 16; // 2 tiles wide
5220 height = (size + 6) * 8;
5221 break;
5222 }
5223
5224 case 67: // DownwardsBlock2x2spaced2
5225 {
5226 size = size & 0x0F;
5227 int count = size + 1;
5228 width = 16;
5229 height = ((count - 1) * 4 + 2) * 8;
5230 break;
5231 }
5232
5233 case 68: // DownwardsCannonHole3x6
5234 {
5235 size = size & 0x0F;
5236 int count = size + 1;
5237 width = 24;
5238 height = count * 6 * 8;
5239 break;
5240 }
5241
5242 case 69: // DownwardsBar2x3
5243 {
5244 size = size & 0x0F;
5245 int count = size + 1;
5246 width = 16;
5247 height = ((count - 1) * 3 + 3) * 8;
5248 break;
5249 }
5250
5251 case 70: // DownwardsPots2x2
5252 case 71: // DownwardsHammerPegs2x2
5253 {
5254 size = size & 0x0F;
5255 int count = size + 1;
5256 width = 16;
5257 height = count * 2 * 8;
5258 break;
5259 }
5260
5261 case 72: // RightwardsEdge1x1 +7
5262 {
5263 size = size & 0x0F;
5264 width = (size + 8) * 8;
5265 height = 8;
5266 break;
5267 }
5268
5269 case 73: // RightwardsPots2x2
5270 case 74: // RightwardsHammerPegs2x2
5271 {
5272 size = size & 0x0F;
5273 int count = size + 1;
5274 width = count * 2 * 8;
5275 height = 16;
5276 break;
5277 }
5278
5279 // Diagonal ceilings (75-78) - TRIANGLE shapes
5280 // Draw uses count = (size & 0x0F) + 4
5281 // Outline uses smaller size since triangle only fills half the square area
5282 case 75: // DiagonalCeilingTopLeft - triangle at origin
5283 case 76: // DiagonalCeilingBottomLeft - triangle at origin
5284 {
5285 // Smaller outline for triangle - use half the drawn area
5286 int count = (size & 0x0F) + 2;
5287 width = count * 8;
5288 height = count * 8;
5289 break;
5290 }
5291 case 77: // DiagonalCeilingTopRight - triangle shifts diagonally
5292 case 78: // DiagonalCeilingBottomRight - triangle shifts diagonally
5293 {
5294 // Smaller outline for diagonal triangles
5295 int count = (size & 0x0F) + 2;
5296 width = count * 8;
5297 height = count * 8;
5298 break;
5299 }
5300
5301 // Special platform routines (79-82)
5302 case 79: // ClosedChestPlatform
5303 case 80: // MovingWallWest
5304 case 81: // MovingWallEast
5305 case 82: // OpenChestPlatform
5306 width = 64; // 8 tiles wide
5307 height = 64; // 8 tiles tall
5308 break;
5309
5310 // Stair routines - different sizes for different types
5311
5312 // 4x4 stair patterns (32x32 pixels)
5313 case 83: // InterRoomFatStairsUp (0x12D)
5314 case 84: // InterRoomFatStairsDownA (0x12E)
5315 case 85: // InterRoomFatStairsDownB (0x12F)
5316 case 86: // AutoStairs (0x130-0x133)
5317 case 87: // StraightInterroomStairs (0xF9E-0xFA9)
5318 width = 32; // 4 tiles
5319 height = 32; // 4 tiles (4x4 pattern)
5320 break;
5321
5322 // 4x3 stair patterns (32x24 pixels)
5323 case 88: // SpiralStairsGoingUpUpper (0x138)
5324 case 89: // SpiralStairsGoingDownUpper (0x139)
5325 case 90: // SpiralStairsGoingUpLower (0x13A)
5326 case 91: // SpiralStairsGoingDownLower (0x13B)
5327 // ASM: RoomDraw_1x3N_rightwards with A=4 -> 4 columns x 3 rows
5328 width = 32; // 4 tiles
5329 height = 24; // 3 tiles
5330 break;
5331
5332 case 92: // BigKeyLock
5333 width = 16;
5334 height = 24;
5335 break;
5336
5337 case 93: // BombableFloor
5338 width = 32;
5339 height = 32;
5340 break;
5341
5342 case 94: // EmptyWaterFace
5343 case 95: // SpittingWaterFace
5344 case 96: // DrenchingWaterFace
5345 width = 32;
5346 height = 32;
5347 break;
5348
5349 case 97: // PrisonCell
5350 width = 32;
5351 height = 48;
5352 break;
5353
5354 case 98: // Bed4x5
5355 width = 32;
5356 height = 40;
5357 break;
5358
5359 case 99: // Rightwards3x6
5360 width = 24;
5361 height = 48;
5362 break;
5363
5364 case 100: // Utility6x3
5365 width = 48;
5366 height = 24;
5367 break;
5368
5369 case 101: // Utility3x5
5370 width = 24;
5371 height = 40;
5372 break;
5373
5374 case 102: // VerticalTurtleRockPipe
5375 width = 16;
5376 height = 48;
5377 break;
5378
5379 case 103: // HorizontalTurtleRockPipe
5380 width = 48;
5381 height = 16;
5382 break;
5383
5384 case 104: // LightBeam
5385 width = 16;
5386 height = 32;
5387 break;
5388
5389 case 105: // BigLightBeam
5390 width = 32;
5391 height = 64;
5392 break;
5393
5394 case 106: // BossShell4x4
5395 width = 32;
5396 height = 32;
5397 break;
5398
5399 case 107: // SolidWallDecor3x4
5400 width = 24;
5401 height = 32;
5402 break;
5403
5404 case 108: // ArcheryGameTargetDoor
5405 width = 24;
5406 height = 48;
5407 break;
5408
5409 case 109: // GanonTriforceFloorDecor
5410 width = 32;
5411 height = 64;
5412 break;
5413
5414 case 110: // Single2x2
5415 width = 16;
5416 height = 16;
5417 break;
5418
5419 case 111: // Waterfall47 (object 0x47)
5420 {
5421 // ASM: count = (size+1)*2, draws 1x5 columns
5422 // Width = first column + middle columns + last column = 2 + count tiles
5423 size = size & 0x0F;
5424 int count = (size + 1) * 2;
5425 width = (2 + count) * 8;
5426 height = 40; // 5 tiles
5427 break;
5428 }
5429 case 112: // Waterfall48 (object 0x48)
5430 {
5431 // ASM: count = (size+1)*2, draws 1x3 columns
5432 // Width = first column + middle columns + last column = 2 + count tiles
5433 size = size & 0x0F;
5434 int count = (size + 1) * 2;
5435 width = (2 + count) * 8;
5436 height = 24; // 3 tiles
5437 break;
5438 }
5439
5440 case 113: // Single4x4 (no repetition) - 4x4 TILE16 = 8x8 TILE8
5441 // 4 tile16 wide x 4 tile16 tall = 8 tile8 x 8 tile8 = 64x64 pixels
5442 width = 64;
5443 height = 64;
5444 break;
5445
5446 case 114: // Single4x3 (no repetition)
5447 // 4 tiles wide x 3 tiles tall = 32x24 pixels
5448 width = 32;
5449 height = 24;
5450 break;
5451
5452 case 115: // RupeeFloor (special pattern)
5453 // 6 tiles wide (3 columns x 2 tiles) x 8 tiles tall = 48x64 pixels
5454 width = 48;
5455 height = 64;
5456 break;
5457
5458 case 116: // Actual4x4 (true 4x4 tile8 pattern, no repetition)
5459 // 4 tile8s x 4 tile8s = 32x32 pixels
5460 width = 32;
5461 height = 32;
5462 break;
5463
5464 default:
5465 // Fallback to naive calculation if not handled
5466 // Matches DungeonCanvasViewer::DrawRoomObjects logic
5467 {
5468 int size_h = (object.size_ & 0x0F);
5469 int size_v = (object.size_ >> 4) & 0x0F;
5470 width = (size_h + 1) * 8;
5471 height = (size_v + 1) * 8;
5472 }
5473 break;
5474 }
5475
5476 return {width, height};
5477}
5478
5480 [[maybe_unused]] std::span<const gfx::TileInfo> tiles,
5481 [[maybe_unused]] const DungeonState* state) {
5482 // CustomObjectManager should be initialized by DungeonEditorV2 with the
5483 // project's custom_objects_folder path before any objects are drawn
5484 auto& manager = CustomObjectManager::Get();
5485
5486 int subtype = obj.size_ & 0x1F;
5487 auto result = manager.GetObjectInternal(obj.id_, subtype);
5488 if (!result.ok()) {
5489 LOG_DEBUG("ObjectDrawer", "Custom object 0x%03X subtype %d not found: %s",
5490 obj.id_, subtype, result.status().message().data());
5491 return;
5492 }
5493
5494 auto custom_obj = result.value();
5495 if (!custom_obj || custom_obj->IsEmpty()) return;
5496
5497 int tile_x = obj.x_;
5498 int tile_y = obj.y_;
5499
5500 for (const auto& entry : custom_obj->tiles) {
5501 // entry.tile_data is vhopppcc cccccccc (SNES tilemap word format)
5502 // Convert to TileInfo and render using WriteTile8 (not SetTileAt which
5503 // only stores to buffer without rendering)
5504 gfx::TileInfo tile_info = gfx::WordToTileInfo(entry.tile_data);
5505 WriteTile8(bg, tile_x + entry.rel_x, tile_y + entry.rel_y, tile_info);
5506 }
5507}
5508
5509void yaze::zelda3::ObjectDrawer::DrawPotItem(uint8_t item_id, int x, int y,
5511 // Draw a small colored indicator for pot items
5512 // Item types from ZELDA3_DUNGEON_SPEC.md Section 7.2
5513 // Uses palette indices that map to recognizable colors
5514
5515 if (item_id == 0) return; // Nothing - skip
5516
5517 auto& bitmap = bg.bitmap();
5518 if (!bitmap.is_active() || bitmap.width() == 0) return;
5519
5520 // Convert tile coordinates to pixel coordinates
5521 // Items are drawn offset from pot position (centered on pot)
5522 int pixel_x = (x * 8) + 2; // Offset 2 pixels into the pot tile
5523 int pixel_y = (y * 8) + 2;
5524
5525 // Choose color based on item category
5526 // Using palette indices that should be visible in dungeon palettes
5527 uint8_t color_idx;
5528 switch (item_id) {
5529 // Rupees (green/blue/red tones)
5530 case 1: // Green rupee
5531 case 7: // Blue rupee
5532 case 12: // Blue rupee variant
5533 color_idx = 30; // Greenish (palette 2, index 0)
5534 break;
5535
5536 // Hearts (red tones)
5537 case 6: // Heart
5538 case 11: // Heart
5539 case 13: // Heart variant
5540 color_idx = 0; // Should be reddish in dungeon palette
5541 break;
5542
5543 // Keys (yellow/gold)
5544 case 8: // Key*8
5545 case 19: // Key
5546 color_idx = 45; // Yellowish (palette 3)
5547 break;
5548
5549 // Bombs (dark/black)
5550 case 5: // Bomb
5551 case 10: // 1 bomb
5552 case 16: // Bomb refill
5553 color_idx = 60; // Darker color (palette 4)
5554 break;
5555
5556 // Arrows (brown/wood)
5557 case 9: // Arrow
5558 case 17: // Arrow refill
5559 color_idx = 15; // Brownish (palette 1)
5560 break;
5561
5562 // Magic (blue/purple)
5563 case 14: // Small magic
5564 case 15: // Big magic
5565 color_idx = 75; // Bluish (palette 5)
5566 break;
5567
5568 // Fairy (pink/light)
5569 case 18: // Fairy
5570 case 20: // Fairy*8
5571 color_idx = 5; // Pinkish
5572 break;
5573
5574 // Special/Traps (distinct colors)
5575 case 2: // Rock crab
5576 case 3: // Bee
5577 color_idx = 20; // Enemy indicator
5578 break;
5579
5580 case 23: // Hole
5581 case 24: // Warp
5582 case 25: // Staircase
5583 color_idx = 10; // Transport indicator
5584 break;
5585
5586 case 26: // Bombable
5587 case 27: // Switch
5588 color_idx = 35; // Interactive indicator
5589 break;
5590
5591 case 4: // Random
5592 default:
5593 color_idx = 50; // Default/random indicator
5594 break;
5595 }
5596
5597 // Draw a 4x4 colored square as item indicator
5598 int bitmap_width = bitmap.width();
5599 int bitmap_height = bitmap.height();
5600
5601 for (int py = 0; py < 4; py++) {
5602 for (int px = 0; px < 4; px++) {
5603 int dest_x = pixel_x + px;
5604 int dest_y = pixel_y + py;
5605
5606 // Bounds check
5607 if (dest_x >= 0 && dest_x < bitmap_width &&
5608 dest_y >= 0 && dest_y < bitmap_height) {
5609 int offset = (dest_y * bitmap_width) + dest_x;
5610 bitmap.WriteToPixel(offset, color_idx);
5611 }
5612 }
5613 }
5614}
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
static Flags & get()
Definition features.h:92
std::vector< uint8_t > & mutable_priority_data()
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:581
bool is_active() const
Definition bitmap.h:384
void set_modified(bool modified)
Definition bitmap.h:388
int height() const
Definition bitmap.h:374
int width() const
Definition bitmap.h:373
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:378
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
static CustomObjectManager & Get()
static DrawRoutineRegistry & Get()
Interface for accessing dungeon game state.
Draws dungeon objects to background buffers using game patterns.
void DrawDiagonalAcute_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
int GetDrawRoutineId(int16_t object_id) const
Get draw routine ID for an object.
void WriteTile8(gfx::BackgroundBuffer &bg, int tile_x, int tile_y, const gfx::TileInfo &tile_info)
void DrawDiagonalGrave_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawBed4x5(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawClosedChestPlatform(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards2x4_1to15or26(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards2x4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawTileToBitmap(gfx::Bitmap &bitmap, const gfx::TileInfo &tile_info, int pixel_x, int pixel_y, const uint8_t *tiledata)
Draw a single tile directly to bitmap.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
void DrawRightwardsPillar2x4spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards4x4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawUtility6x3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsEdge1x1_1to16plus7(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
std::vector< DrawRoutine > draw_routines_
void DrawRightwardsDecor4x2spaced8_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsHammerPegs2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsPillar2x4spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawHorizontalTurtleRockPipe(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwards2x2_1to15or32(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDiagonalCeilingBottomRight(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsBlock2x2spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawSingle2x2(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
std::pair< int, int > CalculateObjectDimensions(const RoomObject &object)
Calculate the dimensions (width, height) of an object in pixels.
void DrawDoorSwitcherer(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards2x2_1to15or32(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
const uint8_t * room_gfx_buffer_
void DrawUtility3x5(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawSingle4x3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRupeeFloor(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsEdge1x1_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsBar2x3_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
static bool RoutineDrawsToBothBGs(int routine_id)
void DrawDownwardsDecor2x4spaced8_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawNothing(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw all objects in a room.
void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsLine1x1_1to16plus1(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr)
Draw a door to background buffers.
void DrawDiagonalCeilingTopLeft(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDiagonalAcute_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawPotItem(uint8_t item_id, int x, int y, gfx::BackgroundBuffer &bg)
Draw a pot item visualization.
void DrawLargeCanvasObject(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, int width, int height)
void Draw4x4Corner_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawBigLightBeam(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsCannonHole3x6_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwards2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsLine1x1_1to16plus1(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawVerticalTurtleRockPipe(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards4x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawCorner4x4(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsHammerPegs2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards2x4_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawCustomObject(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsStatue2x3spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDiagonalCeilingTopRight(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawWaterFace(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsDecor4x3spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsShelf4x4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDiagonalGrave_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawMovingWallEast(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsPots2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
std::unordered_map< int16_t, int > object_to_routine_map_
void DrawDownwardsFloor4x4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsDecor3x4spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsPots2x2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawActual4x4(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawOpenChestPlatform(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawRightwards1x1Solid_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void MarkBG1Transparent(gfx::BackgroundBuffer &bg1, int tile_x, int tile_y, int pixel_width, int pixel_height)
Mark BG1 pixels as transparent where BG2 overlay objects are drawn.
void DrawChest(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsDecor4x4spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void CustomDraw(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawSolidWallDecor3x4(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDiagonalCeilingBottomLeft(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsCannonHole4x3_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawWeirdCornerBottom_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards3x6(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwards4x2_1to15or26(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
ObjectDrawer(Rom *rom, int room_id, const uint8_t *room_gfx_buffer=nullptr)
void DrawRightwardsDecor4x4spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawSingle4x4(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawLightBeam(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
void DrawWeirdCornerTop_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwards4x2_1to16_BothBG(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsDecor3x4spaced4_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
static constexpr int kMaxTilesY
void DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawBossShell4x4(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwards1x2_1to16_plus2(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawGanonTriforceFloorDecor(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsBar4x3_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
bool IsValidTilePosition(int tile_x, int tile_y) const
void DrawRightwardsDecor2x2spaced12_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsBlock2x2spaced2_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDoorIndicator(gfx::Bitmap &bitmap, int tile_x, int tile_y, int width, int height, DoorType type, DoorDirection direction)
void DrawDownwardsHasEdge1x1_1to16_plus23(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwards1x1Solid_1to16_plus3(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawSomariaLine(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawArcheryGameTargetDoor(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawRightwardsBigRail1x3_1to16plus5(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawMovingWallWest(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles)
void DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsDecor2x2spaced12_1to16(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
void DrawDownwardsBigRail3x1_1to16plus5(const RoomObject &obj, gfx::BackgroundBuffer &bg, std::span< const gfx::TileInfo > tiles, const DungeonState *state=nullptr)
const std::vector< gfx::TileInfo > & tiles() const
Definition room_object.h:88
void SetRom(Rom *rom)
Definition room_object.h:68
#define LOG_DEBUG(category, format,...)
Definition log.h:103
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378
void DrawTableRock4x4_1to16(const DrawContext &ctx)
Draw 4x4 table rock pattern.
void DrawInterRoomFatStairsDownA(const DrawContext &ctx)
Draw inter-room fat stairs going down A (Type 2 object 0x12E)
void DrawAutoStairs(const DrawContext &ctx)
Draw auto stairs (Type 2/3 objects 0x130-0x133, 0x21B-0x21D, 0x233)
void DrawSpittingWaterFace(const DrawContext &ctx)
Draw spitting water face (Type 3 object 0x201)
void Draw4x4BlocksIn4x4SuperSquare(const DrawContext &ctx)
Draw 4x4 solid blocks in a super square grid.
void DrawBigHole4x4_1to16(const DrawContext &ctx)
Draw 4x4 big hole pattern.
void Draw4x4FloorTwoIn4x4SuperSquare(const DrawContext &ctx)
Draw two 4x4 floor pattern variant.
void DrawSpike2x2In4x4SuperSquare(const DrawContext &ctx)
Draw 2x2 spike pattern in super square units.
void DrawSpiralStairs(const DrawContext &ctx, bool going_up, bool is_upper)
Draw spiral stairs (Type 2 objects 0x138-0x13B)
void DrawBombableFloor(const DrawContext &ctx)
Draw bombable floor (Type 3 object 0x247)
void DrawDrenchingWaterFace(const DrawContext &ctx)
Draw drenching water face (Type 3 object 0x202)
void DrawEmptyWaterFace(const DrawContext &ctx)
Draw empty water face (Type 3 object 0x200)
void Draw4x4FloorIn4x4SuperSquare(const DrawContext &ctx)
Draw 4x4 floor pattern in super square units.
void DrawInterRoomFatStairsUp(const DrawContext &ctx)
Draw inter-room fat stairs going up (Type 2 object 0x12D)
void Draw3x3FloorIn4x4SuperSquare(const DrawContext &ctx)
Draw 3x3 floor pattern in super square units.
void DrawStraightInterRoomStairs(const DrawContext &ctx)
Draw straight inter-room stairs (Type 3 objects 0x21E-0x229)
void DrawInterRoomFatStairsDownB(const DrawContext &ctx)
Draw inter-room fat stairs going down B (Type 2 object 0x12F)
void DrawWaterOverlay8x8_1to16(const DrawContext &ctx)
Draw water overlay 8x8 pattern.
void Draw4x4FloorOneIn4x4SuperSquare(const DrawContext &ctx)
Draw single 4x4 floor pattern variant.
void DrawBigKeyLock(const DrawContext &ctx)
Draw big key lock (Type 3 object 0x218)
void DrawPrisonCell(const DrawContext &ctx)
Draw prison cell with bars (Type 3 objects 0x20D, 0x217)
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ TopShutterLower
Top-sided shutter door (lower layer)
@ FancyDungeonExitLower
Fancy dungeon exit (lower layer)
@ FancyDungeonExit
Fancy dungeon exit.
@ SmallKeyDoor
Small key door.
@ SmallKeyStairsDown
Small key stairs (downwards)
@ BombableCaveExit
Bombable cave exit.
@ SmallKeyStairsUp
Small key stairs (upwards)
@ DungeonSwapMarker
Dungeon swap marker.
@ NormalDoor
Normal door (upper layer)
@ BombableDoor
Bombable door.
@ LayerSwapMarker
Layer swap marker.
@ BottomShutterLower
Bottom-sided shutter door (lower layer)
@ ExplodingWall
Exploding wall.
@ TopSidedShutter
Top-sided shutter door.
@ LitCaveExitLower
Lit cave exit (lower layer)
@ DoubleSidedShutterLower
Double-sided shutter (lower layer)
@ UnopenableBigKeyDoor
Unopenable, double-sided big key door.
@ NormalDoorLower
Normal door (lower layer)
@ BottomSidedShutter
Bottom-sided shutter door.
@ SmallKeyStairsDownLower
Small key stairs (lower layer; downwards)
@ CurtainDoor
Curtain door.
@ WaterfallDoor
Waterfall door.
@ BigKeyDoor
Big key door.
@ EyeWatchDoor
Eye watch door.
@ SmallKeyStairsUpLower
Small key stairs (lower layer; upwards)
@ ExitMarker
Exit marker.
@ DoubleSidedShutter
Double sided shutter door.
constexpr int kDoorGfxDown
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
Definition door_types.h:161
constexpr int kDoorGfxLeft
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Definition door_types.h:106
constexpr int kDoorGfxUp
DoorDirection
Door direction on room walls.
Definition door_types.h:18
@ South
Bottom wall (horizontal door, 4x3 tiles)
@ North
Top wall (horizontal door, 4x3 tiles)
@ East
Right wall (vertical door, 3x4 tiles)
@ West
Left wall (vertical door, 3x4 tiles)
constexpr int kDoorGfxRight
Represents a group of palettes.
int width_tiles
Width in 8x8 tiles.
Definition door_types.h:179
Context passed to draw routines containing all necessary state.
std::pair< int, int > GetTileCoords() const
DoorDimensions GetDimensions() const