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