yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room.cc
Go to the documentation of this file.
1#include "room.h"
2
3#include <yaze.h>
4
5#include <algorithm>
6#include <cstdint>
7#include <vector>
8
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
15#include "rom/rom.h"
16#include "rom/snes.h"
17#include "util/log.h"
24
25namespace yaze {
26namespace zelda3 {
27
28// Define room effect names in a single translation unit to avoid SIOF
29const std::string RoomEffect[8] = {"Nothing",
30 "Nothing",
31 "Moving Floor",
32 "Moving Water",
33 "Trinexx Shell",
34 "Red Flashes",
35 "Light Torch to See Floor",
36 "Ganon's Darkness"};
37
38// Define room tag names in a single translation unit to avoid SIOF
39const std::string RoomTag[65] = {"Nothing",
40 "NW Kill Enemy to Open",
41 "NE Kill Enemy to Open",
42 "SW Kill Enemy to Open",
43 "SE Kill Enemy to Open",
44 "W Kill Enemy to Open",
45 "E Kill Enemy to Open",
46 "N Kill Enemy to Open",
47 "S Kill Enemy to Open",
48 "Clear Quadrant to Open",
49 "Clear Full Tile to Open",
50 "NW Push Block to Open",
51 "NE Push Block to Open",
52 "SW Push Block to Open",
53 "SE Push Block to Open",
54 "W Push Block to Open",
55 "E Push Block to Open",
56 "N Push Block to Open",
57 "S Push Block to Open",
58 "Push Block to Open",
59 "Pull Lever to Open",
60 "Collect Prize to Open",
61 "Hold Switch Open Door",
62 "Toggle Switch to Open Door",
63 "Turn off Water",
64 "Turn on Water",
65 "Water Gate",
66 "Water Twin",
67 "Moving Wall Right",
68 "Moving Wall Left",
69 "Crash",
70 "Crash",
71 "Push Switch Exploding Wall",
72 "Holes 0",
73 "Open Chest (Holes 0)",
74 "Holes 1",
75 "Holes 2",
76 "Defeat Boss for Dungeon Prize",
77 "SE Kill Enemy to Push Block",
78 "Trigger Switch Chest",
79 "Pull Lever Exploding Wall",
80 "NW Kill Enemy for Chest",
81 "NE Kill Enemy for Chest",
82 "SW Kill Enemy for Chest",
83 "SE Kill Enemy for Chest",
84 "W Kill Enemy for Chest",
85 "E Kill Enemy for Chest",
86 "N Kill Enemy for Chest",
87 "S Kill Enemy for Chest",
88 "Clear Quadrant for Chest",
89 "Clear Full Tile for Chest",
90 "Light Torches to Open",
91 "Holes 3",
92 "Holes 4",
93 "Holes 5",
94 "Holes 6",
95 "Agahnim Room",
96 "Holes 7",
97 "Holes 8",
98 "Open Chest for Holes 8",
99 "Push Block for Chest",
100 "Clear Room for Triforce Door",
101 "Light Torches for Chest",
102 "Kill Boss Again"};
103
104RoomSize CalculateRoomSize(Rom* rom, int room_id) {
105 // Calculate the size of the room based on how many objects are used per room
106 // Some notes from hacker Zarby89
107 // vanilla rooms are using in average ~0x80 bytes
108 // a "normal" person who wants more details than vanilla will use around 0x100
109 // bytes per rooms you could fit 128 rooms like that in 1 bank
110 // F8000 I don't remember if that's PC or snes tho
111 // Check last rooms
112 // F8000+(roomid*3)
113 // So we want to search the rom() object at this addressed based on the room
114 // ID since it's the roomid * 3 we will by pulling 3 bytes at a time We can do
115 // this with the rom()->ReadByteVector(addr, size)
116 // Existing room size address calculation...
117 RoomSize room_size;
118 room_size.room_size_pointer = 0;
119 room_size.room_size = 0;
120
121 if (!rom || !rom->is_loaded() || rom->size() == 0) {
122 return room_size;
123 }
124
125 auto room_size_address = 0xF8000 + (room_id * 3);
126
127 // Bounds check
128 if (room_size_address < 0 ||
129 room_size_address + 2 >= static_cast<int>(rom->size())) {
130 return room_size;
131 }
132
133 // Reading bytes for long address construction
134 uint8_t low = rom->data()[room_size_address];
135 uint8_t high = rom->data()[room_size_address + 1];
136 uint8_t bank = rom->data()[room_size_address + 2];
137
138 // Constructing the long address
139 int long_address = (bank << 16) | (high << 8) | low;
140 room_size.room_size_pointer = long_address;
141
142 if (long_address == 0x0A8000) {
143 // Blank room disregard in size calculation
144 room_size.room_size = 0;
145 } else {
146 // use the long address to calculate the size of the room
147 // we will use the room_id_ to calculate the next room's address
148 // and subtract the two to get the size of the room
149
150 int next_room_address = 0xF8000 + ((room_id + 1) * 3);
151
152 // Bounds check for next room address
153 if (next_room_address < 0 ||
154 next_room_address + 2 >= static_cast<int>(rom->size())) {
155 return room_size;
156 }
157
158 // Reading bytes for long address construction
159 uint8_t next_low = rom->data()[next_room_address];
160 uint8_t next_high = rom->data()[next_room_address + 1];
161 uint8_t next_bank = rom->data()[next_room_address + 2];
162
163 // Constructing the long address
164 int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
165
166 // Calculate the size of the room
167 int actual_room_size = next_long_address - long_address;
168 room_size.room_size = actual_room_size;
169 }
170
171 return room_size;
172}
173
174// Loads a room from the ROM.
175// ASM: Bank 01, Underworld_LoadRoom ($01873A)
176Room LoadRoomFromRom(Rom* rom, int room_id) {
177 // Use the header loader to get the base room with properties
178 // ASM: JSR Underworld_LoadHeader ($01873A)
179 Room room = LoadRoomHeaderFromRom(rom, room_id);
180
181 // Load room objects
182 // ASM: RoomData_ObjectDataPointers ($018742 - reads pointer table)
183 int object_pointer = SnesToPc(room_object_pointer);
184 int room_address = object_pointer + (room_id * 3);
185 int objects_location = SnesToPc(room_address);
186
187 // Load sprites
188 // ASM: Bank 09 logic (referenced via long pointers in Bank 01)
189 int spr_ptr = 0x040000 | rooms_sprite_pointer;
190 int sprite_address = SnesToPc(dungeon_spr_ptrs | spr_ptr + (room_id * 2));
191
192 // Load additional room features
193 room.LoadDoors();
194 room.LoadPotItems();
195 room.LoadTorches();
196 room.LoadTorches();
197 room.LoadTorches();
198 room.LoadBlocks();
199 room.LoadPits();
200
201 room.SetLoaded(true);
202 return room;
203}
204
205Room LoadRoomHeaderFromRom(Rom* rom, int room_id) {
206 Room room(room_id, rom);
207
208 if (!rom || !rom->is_loaded() || rom->size() == 0) {
209 return room;
210 }
211
212 // Validate kRoomHeaderPointer access
213 if (kRoomHeaderPointer < 0 ||
214 kRoomHeaderPointer + 2 >= static_cast<int>(rom->size())) {
215 return room;
216 }
217
218 // ASM: RoomHeader_RoomToPointer table lookup
219 int header_pointer = (rom->data()[kRoomHeaderPointer + 2] << 16) +
220 (rom->data()[kRoomHeaderPointer + 1] << 8) +
221 (rom->data()[kRoomHeaderPointer]);
222 header_pointer = SnesToPc(header_pointer);
223
224 // Validate kRoomHeaderPointerBank access
225 if (kRoomHeaderPointerBank < 0 ||
226 kRoomHeaderPointerBank >= static_cast<int>(rom->size())) {
227 return room;
228 }
229
230 // Validate header_pointer table access
231 int table_offset = (header_pointer) + (room_id * 2);
232 if (table_offset < 0 || table_offset + 1 >= static_cast<int>(rom->size())) {
233 return room;
234 }
235
236 int address = (rom->data()[kRoomHeaderPointerBank] << 16) +
237 (rom->data()[table_offset + 1] << 8) +
238 rom->data()[table_offset];
239
240 auto header_location = SnesToPc(address);
241
242 // Validate header_location access (we read up to +13 bytes)
243 if (header_location < 0 ||
244 header_location + 13 >= static_cast<int>(rom->size())) {
245 return room;
246 }
247
248 room.SetBg2((background2)((rom->data()[header_location] >> 5) & 0x07));
249 room.SetCollision((CollisionKey)((rom->data()[header_location] >> 2) & 0x07));
250 room.SetIsLight(((rom->data()[header_location]) & 0x01) == 1);
251
252 if (room.IsLight()) {
253 room.SetBg2(background2::DarkRoom);
254 }
255
256 room.SetPalette(((rom->data()[header_location + 1] & 0x3F)));
257 room.SetBlockset((rom->data()[header_location + 2]));
258 room.SetSpriteset((rom->data()[header_location + 3]));
259 room.SetEffect((EffectKey)((rom->data()[header_location + 4])));
260 room.SetTag1((TagKey)((rom->data()[header_location + 5])));
261 room.SetTag2((TagKey)((rom->data()[header_location + 6])));
262
263 room.SetStaircasePlane(0, ((rom->data()[header_location + 7] >> 2) & 0x03));
264 room.SetStaircasePlane(1, ((rom->data()[header_location + 7] >> 4) & 0x03));
265 room.SetStaircasePlane(2, ((rom->data()[header_location + 7] >> 6) & 0x03));
266 room.SetStaircasePlane(3, ((rom->data()[header_location + 8]) & 0x03));
267
268 room.SetHolewarp((rom->data()[header_location + 9]));
269 room.SetStaircaseRoom(0, (rom->data()[header_location + 10]));
270 room.SetStaircaseRoom(1, (rom->data()[header_location + 11]));
271 room.SetStaircaseRoom(2, (rom->data()[header_location + 12]));
272 room.SetStaircaseRoom(3, (rom->data()[header_location + 13]));
273
274 // =====
275
276 // Validate kRoomHeaderPointer access (again, just in case)
277 if (kRoomHeaderPointer < 0 ||
278 kRoomHeaderPointer + 2 >= static_cast<int>(rom->size())) {
279 return room;
280 }
281
282 int header_pointer_2 = (rom->data()[kRoomHeaderPointer + 2] << 16) +
283 (rom->data()[kRoomHeaderPointer + 1] << 8) +
284 (rom->data()[kRoomHeaderPointer]);
285 header_pointer_2 = SnesToPc(header_pointer_2);
286
287 // Validate kRoomHeaderPointerBank access
288 if (kRoomHeaderPointerBank < 0 ||
289 kRoomHeaderPointerBank >= static_cast<int>(rom->size())) {
290 return room;
291 }
292
293 // Validate header_pointer_2 table access
294 int table_offset_2 = (header_pointer_2) + (room_id * 2);
295 if (table_offset_2 < 0 ||
296 table_offset_2 + 1 >= static_cast<int>(rom->size())) {
297 return room;
298 }
299
300 int address_2 = (rom->data()[kRoomHeaderPointerBank] << 16) +
301 (rom->data()[table_offset_2 + 1] << 8) +
302 rom->data()[table_offset_2];
303
304 room.SetMessageIdDirect(messages_id_dungeon + (room_id * 2));
305
306 auto hpos = SnesToPc(address_2);
307
308 // Validate hpos access (we read sequentially)
309 // We read about 14 bytes (hpos++ calls)
310 if (hpos < 0 || hpos + 14 >= static_cast<int>(rom->size())) {
311 return room;
312 }
313
314 hpos++;
315 uint8_t b = rom->data()[hpos];
316
317 room.SetLayer2Mode((b >> 5));
318 room.SetLayerMerging(kLayerMergeTypeList[(b & 0x0C) >> 2]);
319
320 room.SetIsDark((b & 0x01) == 0x01);
321 hpos++;
322 room.SetPaletteDirect(rom->data()[hpos]);
323 hpos++;
324
325 room.SetBackgroundTileset(rom->data()[hpos]);
326 hpos++;
327
328 room.SetSpriteTileset(rom->data()[hpos]);
329 hpos++;
330
331 room.SetLayer2Behavior(rom->data()[hpos]);
332 hpos++;
333
334 room.SetTag1Direct((TagKey)rom->data()[hpos]);
335 hpos++;
336
337 room.SetTag2Direct((TagKey)rom->data()[hpos]);
338 hpos++;
339
340 b = rom->data()[hpos];
341
342 room.SetPitsTargetLayer((uint8_t)(b & 0x03));
343 room.SetStair1TargetLayer((uint8_t)((b >> 2) & 0x03));
344 room.SetStair2TargetLayer((uint8_t)((b >> 4) & 0x03));
345 room.SetStair3TargetLayer((uint8_t)((b >> 6) & 0x03));
346 hpos++;
347 room.SetStair4TargetLayer((uint8_t)(rom->data()[hpos] & 0x03));
348 hpos++;
349
350 room.SetPitsTarget(rom->data()[hpos]);
351 hpos++;
352 room.SetStair1Target(rom->data()[hpos]);
353 hpos++;
354 room.SetStair2Target(rom->data()[hpos]);
355 hpos++;
356 room.SetStair3Target(rom->data()[hpos]);
357 hpos++;
358 room.SetStair4Target(rom->data()[hpos]);
359
360 // Note: We do NOT set is_loaded_ to true here, as this is just the header
361 return room;
362}
363
364Room::Room(int room_id, Rom* rom, GameData* game_data)
365 : room_id_(room_id),
366 rom_(rom),
367 game_data_(game_data),
368 dungeon_state_(std::make_unique<EditorDungeonState>(rom, game_data)) {}
369
370Room::Room() = default;
371Room::~Room() = default;
372Room::Room(Room&&) = default;
373Room& Room::operator=(Room&&) = default;
374
375void Room::LoadRoomGraphics(uint8_t entrance_blockset) {
376 if (!game_data_) {
377 LOG_DEBUG("Room", "GameData not set for room %d", room_id_);
378 return;
379 }
380
381 const auto& room_gfx = game_data_->room_blockset_ids;
382 const auto& sprite_gfx = game_data_->spriteset_ids;
383
384 LOG_DEBUG("Room", "Room %d: blockset=%d, spriteset=%d, palette=%d", room_id_,
386
387 for (int i = 0; i < 8; i++) {
389 // Block 6 can be overridden by entrance-specific room graphics (index 3)
390 // Note: The "3-6" comment was misleading - only block 6 uses room_gfx
391 if (i == 6) {
392 if (entrance_blockset != 0xFF && room_gfx[entrance_blockset][3] != 0) {
393 blocks_[i] = room_gfx[entrance_blockset][3];
394 }
395 }
396 }
397
398 blocks_[8] = 115 + 0; // Static Sprites Blocksets (fairy,pot,ect...)
399 blocks_[9] = 115 + 10;
400 blocks_[10] = 115 + 6;
401 blocks_[11] = 115 + 7;
402 for (int i = 0; i < 4; i++) {
403 blocks_[12 + i] = (uint8_t)(sprite_gfx[spriteset + 64][i] + 115);
404 } // 12-16 sprites
405
406 LOG_DEBUG("Room", "Sheet IDs BG[0-7]: %d %d %d %d %d %d %d %d", blocks_[0],
407 blocks_[1], blocks_[2], blocks_[3], blocks_[4], blocks_[5],
408 blocks_[6], blocks_[7]);
409}
410
411constexpr int kGfxBufferOffset = 92 * 2048;
412constexpr int kGfxBufferStride = 1024;
413constexpr int kGfxBufferAnimatedFrameOffset = 7 * 4096;
414constexpr int kGfxBufferAnimatedFrameStride = 1024;
415constexpr int kGfxBufferRoomOffset = 4096;
416constexpr int kGfxBufferRoomSpriteOffset = 1024;
417constexpr int kGfxBufferRoomSpriteStride = 4096;
419
421 if (!rom_ || !rom_->is_loaded()) {
422 LOG_DEBUG("Room", "CopyRoomGraphicsToBuffer: ROM not loaded");
423 return;
424 }
425
426 if (!game_data_) {
427 LOG_DEBUG("Room", "CopyRoomGraphicsToBuffer: GameData not set");
428 return;
429 }
430 auto* gfx_buffer_data = &game_data_->graphics_buffer;
431 if (gfx_buffer_data->empty()) {
432 LOG_DEBUG("Room", "CopyRoomGraphicsToBuffer: Graphics buffer is empty");
433 return;
434 }
435
436 LOG_DEBUG("Room", "Room %d: Copying 8BPP graphics (buffer size: %zu)",
437 room_id_, gfx_buffer_data->size());
438
439 // Clear destination buffer
440 std::fill(current_gfx16_.begin(), current_gfx16_.end(), 0);
441
442 // Process each of the 16 graphics blocks
443 for (int block = 0; block < 16; block++) {
444 int sheet_id = blocks_[block];
445
446 // Validate block index
447 if (sheet_id >= 223) { // kNumGfxSheets
448 LOG_WARN("Room", "Invalid sheet index %d for block %d", sheet_id, block);
449 continue;
450 }
451
452 // Source offset in ROM graphics buffer (now 8BPP format)
453 // Each 8BPP sheet is 4096 bytes (128x32 pixels)
454 int src_sheet_offset = sheet_id * 4096;
455
456 // Validate source bounds
457 if (src_sheet_offset + 4096 > gfx_buffer_data->size()) {
458 LOG_ERROR("Room", "Graphics offset out of bounds: %d (size: %zu)",
459 src_sheet_offset, gfx_buffer_data->size());
460 continue;
461 }
462
463 // Copy 4096 bytes for the 8BPP sheet
464 int dest_index_base = block * 4096;
465 if (dest_index_base + 4096 <= current_gfx16_.size()) {
466 memcpy(current_gfx16_.data() + dest_index_base,
467 gfx_buffer_data->data() + src_sheet_offset, 4096);
468 }
469 }
470
471 LOG_DEBUG("Room", "Room %d: Graphics blocks copied successfully", room_id_);
473}
474
476 if (composite_dirty_) {
477 layer_mgr.CompositeToOutput(*this, composite_bitmap_);
478 composite_dirty_ = false;
479 }
480 return composite_bitmap_;
481}
482
484 // PERFORMANCE OPTIMIZATION: Check if room properties have changed
485 bool properties_changed = false;
486
487 // Check if graphics properties changed
498 graphics_dirty_ = true;
499 properties_changed = true;
500 }
501
502 // Check if effect/tags changed
503 if (cached_effect_ != static_cast<uint8_t>(effect_) ||
505 cached_effect_ = static_cast<uint8_t>(effect_);
508 objects_dirty_ = true;
509 properties_changed = true;
510 }
511
512 // If nothing changed and textures exist, skip rendering
513 if (!properties_changed && !graphics_dirty_ && !objects_dirty_ &&
515 auto& bg1_bmp = bg1_buffer_.bitmap();
516 auto& bg2_bmp = bg2_buffer_.bitmap();
517 if (bg1_bmp.texture() && bg2_bmp.texture()) {
518 LOG_DEBUG("[RenderRoomGraphics]",
519 "Room %d: No changes detected, skipping render", room_id_);
520 return;
521 }
522 }
523
524 LOG_DEBUG("[RenderRoomGraphics]",
525 "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
528
529 // Capture dirty state BEFORE clearing flags (needed for floor/bg draw logic)
530 bool was_graphics_dirty = graphics_dirty_;
531 bool was_layout_dirty = layout_dirty_;
532
533 // STEP 0: Load graphics if needed
534 if (graphics_dirty_) {
535 // Ensure blocks_[] array is properly initialized before copying graphics
536 // LoadRoomGraphics sets up which sheets go into which blocks
539 graphics_dirty_ = false;
540 }
541
542 // Debug: Log floor graphics values
543 LOG_DEBUG("[RenderRoomGraphics]",
544 "Room %d: floor1=%d, floor2=%d, blocks_size=%zu", room_id_,
546
547 // STEP 1: Draw floor tiles to bitmaps (base layer) - if graphics changed OR
548 // bitmaps not created yet
549 bool need_floor_draw = was_graphics_dirty;
550 auto& bg1_bmp = bg1_buffer_.bitmap();
551 auto& bg2_bmp = bg2_buffer_.bitmap();
552
553 // Always draw floor if bitmaps don't exist yet (first time rendering)
554 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
555 bg2_bmp.width() == 0) {
556 need_floor_draw = true;
557 LOG_DEBUG("[RenderRoomGraphics]",
558 "Room %d: Bitmaps not created yet, forcing floor draw", room_id_);
559 }
560
561 if (need_floor_draw) {
566 }
567
568 // STEP 2: Draw background tiles (floor pattern) to bitmap
569 // This converts the floor tile buffer to pixels
570 bool need_bg_draw = was_graphics_dirty || need_floor_draw;
571 if (need_bg_draw) {
572 bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
573 bg2_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
574 }
575
576 // STEP 3: Draw layout objects ON TOP of floor
577 // Layout objects (walls, corners) are drawn after floor so they appear over it
578 // TODO(zelda3-hacking-expert): Mirror the SNES four-pass pipeline from
579 // assets/asm/usdasm/bank_01.asm (documented in
580 // docs/internal/agents/dungeon-object-rendering-spec.md): layout list,
581 // main list, BG2 overlay list, BG1 overlay list, with BothBG routines
582 // writing simultaneously. Today we only emit one layout pass + one object
583 // list, so BG overlays and dual-layer draws can end up wrong (layout objects
584 // rendering above later passes or BG merge treated as exclusive).
585 if (was_layout_dirty || need_floor_draw) {
587 layout_dirty_ = false;
588 }
589
590 // Get and apply palette BEFORE rendering objects (so objects use correct colors)
591 if (!game_data_)
592 return;
593 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
594 int num_palettes = dungeon_pal_group.size();
595 if (num_palettes == 0)
596 return;
597
598 // Look up dungeon palette ID using the two-level paletteset_ids table.
599 // paletteset_ids[palette][0] contains a BYTE OFFSET into the palette pointer
600 // table at kDungeonPalettePointerTable. The word at that offset, divided by
601 // 180 (bytes per palette), gives the actual palette index (0-19).
602 int palette_id = palette;
603 if (palette < game_data_->paletteset_ids.size() &&
604 !game_data_->paletteset_ids[palette].empty()) {
605 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
606 auto palette_word =
607 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
608 if (palette_word.ok()) {
609 palette_id = palette_word.value() / 180;
610 }
611 }
612 if (palette_id < 0 || palette_id >= num_palettes) {
613 palette_id = 0;
614 }
615
616 auto bg1_palette = dungeon_pal_group[palette_id];
617
618 // DEBUG: Log palette loading
619 PaletteDebugger::Get().LogPaletteLoad("Room::RenderRoomGraphics", palette_id,
620 bg1_palette);
621
622 LOG_DEBUG("Room", "RenderRoomGraphics: Palette ID=%d, Size=%zu", palette_id,
623 bg1_palette.size());
624 if (!bg1_palette.empty()) {
625 LOG_DEBUG("Room", "RenderRoomGraphics: First color: R=%d G=%d B=%d",
626 bg1_palette[0].rom_color().red, bg1_palette[0].rom_color().green,
627 bg1_palette[0].rom_color().blue);
628 }
629
630 // Store current palette and bitmap for pixel inspector debugging
633
634 if (bg1_palette.size() > 0) {
635 // Apply dungeon palette using 16-color bank chunking (matches SNES CGRAM)
636 //
637 // SNES CGRAM layout for dungeons:
638 // - CGRAM has 16-color banks, each bank's index 0 is transparent
639 // - Dungeon tiles use palette bits 2-7, mapping to CGRAM rows 2-7
640 // - ROM stores 15 colors per bank (excluding transparent index 0)
641 // - 6 banks × 15 colors = 90 colors in ROM
642 //
643 // SDL palette mapping (16-color chunks):
644 // - Bank N (N=0-5): SDL indices [N*16 .. N*16+15]
645 // - Index N*16 = transparent for that bank
646 // - ROM colors [N*15 .. N*15+14] → SDL indices [N*16+1 .. N*16+15]
647 //
648 // Drawing formula: final_color = pixel + (bank * 16)
649 // Where pixel 0 = transparent (not written), pixel 1-15 = colors 1-15 in bank
650 auto set_dungeon_palette = [](gfx::Bitmap& bmp,
651 const gfx::SnesPalette& pal) {
652 std::vector<SDL_Color> colors(
653 256, {0, 0, 0, 0}); // Initialize all transparent
654
655 // Map ROM palette to 16-color banks
656 // ROM: 90 colors (6 banks × 15 colors each)
657 // SDL: 96 indices (6 banks × 16 indices each)
658 constexpr int kColorsPerRomBank = 15;
659 constexpr int kIndicesPerSdlBank = 16;
660 constexpr int kNumBanks = 6;
661
662 for (int bank = 0; bank < kNumBanks; bank++) {
663 // Index 0 of each bank is transparent (already initialized to {0,0,0,0})
664 // ROM colors map to SDL indices 1-15 within each bank
665 for (int color = 0; color < kColorsPerRomBank; color++) {
666 size_t rom_index = bank * kColorsPerRomBank + color;
667 if (rom_index >= pal.size())
668 break;
669
670 int sdl_index =
671 bank * kIndicesPerSdlBank + color + 1; // +1 to skip transparent
672 ImVec4 rgb = pal[rom_index].rgb();
673 colors[sdl_index] = {
674 static_cast<Uint8>(rgb.x), static_cast<Uint8>(rgb.y),
675 static_cast<Uint8>(rgb.z),
676 255 // Opaque
677 };
678 }
679 }
680
681 // Index 255 is also transparent (fill color for undrawn areas)
682 colors[255] = {0, 0, 0, 0};
683
684 bmp.SetPalette(colors);
685 if (bmp.surface()) {
686 // Set color key to 255 for proper alpha blending (undrawn areas)
687 SDL_SetColorKey(bmp.surface(), SDL_TRUE, 255);
688 SDL_SetSurfaceBlendMode(bmp.surface(), SDL_BLENDMODE_BLEND);
689 }
690 };
691
692 set_dungeon_palette(bg1_bmp, bg1_palette);
693 set_dungeon_palette(bg2_bmp, bg1_palette);
694 set_dungeon_palette(object_bg1_buffer_.bitmap(), bg1_palette);
695 set_dungeon_palette(object_bg2_buffer_.bitmap(), bg1_palette);
696
697 // DEBUG: Verify palette was applied to SDL surface
698 auto* surface = bg1_bmp.surface();
699 if (surface) {
700 SDL_Palette* palette = platform::GetSurfacePalette(surface);
701 if (palette) {
703 "Room::RenderRoomGraphics (BG1)", palette_id, true);
704
705 // Log surface state for detailed debugging
707 "Room::RenderRoomGraphics (after SetPalette)", surface);
708 } else {
710 "Room::RenderRoomGraphics", palette_id, false,
711 "SDL surface has no palette!");
712 }
713 }
714
715 // Apply Layer Merge effects (Transparency/Blending) to BG2
716 // NOTE: These SDL blend settings are for direct SDL rendering paths.
717 // RoomLayerManager::CompositeToOutput uses manual pixel compositing and
718 // handles blend modes separately via its layer_blend_mode_ array.
719 // TODO(scawful): Consolidate blend handling - either implement proper
720 // blending in CompositeLayer or ensure SDL path uses RoomLayerManager.
722 // Set alpha mod for translucency (50%)
723 if (bg2_bmp.surface()) {
724 SDL_SetSurfaceAlphaMod(bg2_bmp.surface(), 128);
725 }
726 if (object_bg2_buffer_.bitmap().surface()) {
727 SDL_SetSurfaceAlphaMod(object_bg2_buffer_.bitmap().surface(), 128);
728 }
729
730 // Check for Addition mode (ID 0x05)
731 if (layer_merging_.ID == 0x05) {
732 if (bg2_bmp.surface()) {
733 SDL_SetSurfaceBlendMode(bg2_bmp.surface(), SDL_BLENDMODE_ADD);
734 }
735 if (object_bg2_buffer_.bitmap().surface()) {
736 SDL_SetSurfaceBlendMode(object_bg2_buffer_.bitmap().surface(),
737 SDL_BLENDMODE_ADD);
738 }
739 }
740 }
741 }
742
743 // Render objects ON TOP of background tiles (AFTER palette is set)
744 // ObjectDrawer will write indexed pixel data that uses the palette we just
745 // set
747
748 // PERFORMANCE OPTIMIZATION: Queue texture commands but DON'T process
749 // immediately. This allows multiple rooms to batch their texture updates
750 // together. Processing happens in DrawDungeonCanvas() once per frame.
751 //
752 // IMPORTANT: Check each buffer INDIVIDUALLY for existing texture.
753 // Layout and object buffers may have different states (e.g., layout rendered
754 // but objects added later need CREATE, not UPDATE).
755 auto queue_texture = [](gfx::Bitmap* bitmap, const char* name) {
756 if (bitmap->texture()) {
757 LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for %s", name);
760 } else {
761 LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for %s", name);
764 }
765 };
766
767 queue_texture(&bg1_bmp, "bg1_buffer");
768 queue_texture(&bg2_bmp, "bg2_buffer");
769 queue_texture(&object_bg1_buffer_.bitmap(), "object_bg1_buffer");
770 queue_texture(&object_bg2_buffer_.bitmap(), "object_bg2_buffer");
771
772 // Mark textures as clean after successful queuing
773 textures_dirty_ = false;
774
775 // IMPORTANT: Mark composite as dirty after any render work
776 // This ensures GetCompositeBitmap() regenerates the merged output
777 composite_dirty_ = true;
778
779 // REMOVED: Don't process texture queue here - let it be batched!
780 // Processing happens once per frame in DrawDungeonCanvas()
781 // This dramatically improves performance when multiple rooms are open
782 // gfx::Arena::Get().ProcessTextureQueue(nullptr); // OLD: Caused slowdown!
783 LOG_DEBUG("[RenderRoomGraphics]",
784 "Texture commands queued for batch processing");
785}
786
788 LOG_DEBUG("Room", "LoadLayoutTilesToBuffer for room %d, layout=%d", room_id_,
789 layout);
790
791 if (!rom_ || !rom_->is_loaded()) {
792 LOG_DEBUG("Room", "ROM not loaded, aborting");
793 return;
794 }
795
796 // Load layout tiles from ROM if not already loaded
798 auto layout_status = layout_.LoadLayout(layout);
799 if (!layout_status.ok()) {
800 LOG_DEBUG("Room", "Failed to load layout %d: %s", layout,
801 layout_status.message().data());
802 return;
803 }
804
805 const auto& layout_objects = layout_.GetObjects();
806 LOG_DEBUG("Room", "Layout %d has %zu objects", layout, layout_objects.size());
807 if (layout_objects.empty()) {
808 return;
809 }
810
811 // Use ObjectDrawer to render layout objects properly
812 // Layout objects are the same format as room objects and need draw routines
813 // to render correctly (walls, corners, etc.)
814 if (!game_data_) {
815 LOG_DEBUG("RenderRoomGraphics", "GameData not set, cannot render layout");
816 return;
817 }
818
819 // Get palette for layout rendering
820 // Get palette for layout rendering using two-level lookup
821 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
822 int num_palettes = dungeon_pal_group.size();
823 if (num_palettes == 0)
824 return;
825 int palette_id = palette;
826 if (palette < game_data_->paletteset_ids.size() &&
827 !game_data_->paletteset_ids[palette].empty()) {
828 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
829 auto palette_word =
830 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
831 if (palette_word.ok()) {
832 palette_id = palette_word.value() / 180;
833 }
834 }
835 if (palette_id < 0 || palette_id >= num_palettes) {
836 palette_id = 0;
837 }
838
839 auto room_palette = dungeon_pal_group[palette_id];
840 gfx::PaletteGroup palette_group;
841 palette_group.AddPalette(room_palette);
842 // TODO(zelda3-hacking-expert): Align palette chunking with 16-color banks
843 // per docs/internal/agents/dungeon-palette-fix-plan.md. SDL palette should
844 // map each subpalette to indices [n*16..n*16+15] with index 0 transparent,
845 // using the same palette for bg1/bg2/object buffers. Add assertions/logging
846 // for palette_id/pointer mismatch against usdasm ($0DEC4B pointers).
847
848 // Draw layout objects using proper draw routines via RoomLayout
849 auto status = layout_.Draw(room_id_, current_gfx16_.data(), bg1_buffer_,
850 bg2_buffer_, palette_group, dungeon_state_.get());
851
852 if (!status.ok()) {
853 LOG_DEBUG(
854 "RenderRoomGraphics", "Layout Draw failed: %s",
855 std::string(status.message().data(), status.message().size()).c_str());
856 } else {
857 LOG_DEBUG("RenderRoomGraphics", "Layout rendered with %zu objects",
858 layout_objects.size());
859 }
860}
861
863 LOG_DEBUG("[RenderObjectsToBackground]",
864 "Starting object rendering for room %d", room_id_);
865
866 if (!rom_ || !rom_->is_loaded()) {
867 LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting");
868 return;
869 }
870
871 // PERFORMANCE OPTIMIZATION: Only render objects if they have changed or if
872 // graphics changed Also render if bitmaps were just created (need_floor_draw
873 // was true in RenderRoomGraphics)
874 auto& bg1_bmp = bg1_buffer_.bitmap();
875 auto& bg2_bmp = bg2_buffer_.bitmap();
876 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
877 bg2_bmp.is_active() && bg2_bmp.width() > 0;
878
879 if (!objects_dirty_ && !graphics_dirty_ && bitmaps_exist) {
880 LOG_DEBUG("[RenderObjectsToBackground]",
881 "Room %d: Objects not dirty, skipping render", room_id_);
882 return;
883 }
884
885 // Handle rendering based on mode (currently using emulator-based rendering)
886 // Emulator or Hybrid mode (use ObjectDrawer)
887 LOG_DEBUG("[RenderObjectsToBackground]",
888 "Room %d: Emulator rendering objects", room_id_);
889 // Get palette group for object rendering (use SAME lookup as
890 // RenderRoomGraphics)
891 if (!game_data_)
892 return;
893 auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
894 int num_palettes = dungeon_pal_group.size();
895
896 // Look up dungeon palette ID using the two-level paletteset_ids table.
897 // (same lookup as RenderRoomGraphics and LoadLayoutTilesToBuffer)
898 int palette_id = palette;
899 if (palette < game_data_->paletteset_ids.size() &&
900 !game_data_->paletteset_ids[palette].empty()) {
901 auto dungeon_palette_ptr = game_data_->paletteset_ids[palette][0];
902 auto palette_word =
903 rom()->ReadWord(kDungeonPalettePointerTable + dungeon_palette_ptr);
904 if (palette_word.ok()) {
905 palette_id = palette_word.value() / 180;
906 }
907 }
908 if (palette_id < 0 || palette_id >= num_palettes) {
909 palette_id = 0;
910 }
911
912 auto room_palette = dungeon_pal_group[palette_id];
913 // Dungeon palettes are 90-color palettes for 3BPP graphics (8-color strides)
914 // Pass the full palette to ObjectDrawer so it can handle all palette indices
915 gfx::PaletteGroup palette_group;
916 palette_group.AddPalette(room_palette);
917
918 // Use ObjectDrawer for pattern-based object rendering
919 // This provides proper wall/object drawing patterns
920 // Pass the room-specific graphics buffer (current_gfx16_) so objects use
921 // correct tiles
923 // TODO(zelda3-hacking-expert): When we split the object stream into the
924 // four ASM layers, ensure DrawObjectList is invoked per stream with proper
925 // target buffers so BothBG routines (ceiling corners, merged stairs, etc.)
926 // land on both BG1/BG2 as in bank_01.asm. See
927 // docs/internal/agents/dungeon-object-rendering-spec.md for the expected
928 // order and dual-layer handling.
929
930 // Clear object buffers before rendering
931 // IMPORTANT: Fill with 255 (transparent color key) so objects overlay correctly
932 // on the floor. We use index 255 as transparent since palette has 90 colors (0-89).
935 object_bg1_buffer_.bitmap().Fill(255);
936 object_bg2_buffer_.bitmap().Fill(255);
937
938 // IMPORTANT: Clear priority buffers when clearing object buffers
939 // Otherwise, old priority values persist and cause incorrect Z-ordering
942
943 // Log layer distribution for this room
944 int layer0_count = 0, layer1_count = 0, layer2_count = 0;
945 for (const auto& obj : tile_objects_) {
946 switch (obj.GetLayerValue()) {
947 case 0:
948 layer0_count++;
949 break;
950 case 1:
951 layer1_count++;
952 break;
953 case 2:
954 layer2_count++;
955 break;
956 }
957 }
958 LOG_DEBUG(
959 "Room",
960 "Room %03X Object Layer Summary: L0(BG1)=%d, L1(BG2)=%d, L2(BG3)=%d",
961 room_id_, layer0_count, layer1_count, layer2_count);
962
963 // Render objects to appropriate buffers
964 // BG1 = Floor/Main (Layer 0, 2)
965 // BG2 = Overlay (Layer 1)
966 // Pass bg1_buffer_ for BG2 object masking - this creates "holes" in the floor
967 // so BG2 overlay content (platforms, statues) shows through BG1 floor tiles
968 auto status = drawer.DrawObjectList(tile_objects_, object_bg1_buffer_,
969 object_bg2_buffer_, palette_group,
971
972 // Render doors using DoorDef struct with enum types
973 // Doors are drawn to the OBJECT buffer for layer visibility control
974 // This allows doors to remain visible when toggling BG1_Layout off
975 for (int i = 0; i < static_cast<int>(doors_.size()); ++i) {
976 const auto& door = doors_[i];
977 ObjectDrawer::DoorDef door_def;
978 door_def.type = door.type;
979 door_def.direction = door.direction;
980 door_def.position = door.position;
981 // Draw doors to object buffers (not layout buffers) so they remain visible
982 // when BG1_Layout is hidden. Doors are objects, not layout tiles.
984 dungeon_state_.get());
985 }
986 // Mark object buffer as modified so texture gets updated
987 if (!doors_.empty()) {
988 object_bg1_buffer_.bitmap().set_modified(true);
989 }
990
991 // Render pot items
992 // Pot items now have their own position from ROM data
993 // No need to match to objects - each item has exact coordinates
994 for (const auto& pot_item : pot_items_) {
995 if (pot_item.item != 0) { // Skip "Nothing" items
996 // PotItem provides pixel coordinates, convert to tile coords
997 int tile_x = pot_item.GetTileX();
998 int tile_y = pot_item.GetTileY();
999 drawer.DrawPotItem(pot_item.item, tile_x, tile_y, object_bg1_buffer_);
1000 }
1001 }
1002
1003 // Render sprites (for key drops)
1004 // We don't have full sprite rendering yet, but we can visualize key drops
1005 for (const auto& sprite : sprites_) {
1006 if (sprite.key_drop() > 0) {
1007 // Draw key drop visualization
1008 // Use a special item ID or just draw a key icon
1009 // We can reuse DrawPotItem with a special ID for key
1010 // Or add DrawKeyDrop to ObjectDrawer
1011 // For now, let's use DrawPotItem with ID 0xFD (Small Key) or 0xFE (Big Key)
1012 uint8_t key_item = (sprite.key_drop() == 1) ? 0xFD : 0xFE;
1013 drawer.DrawPotItem(key_item, sprite.x(), sprite.y(), object_bg1_buffer_);
1014 }
1015 }
1016
1017 // Log only failures, not successes
1018 if (!status.ok()) {
1019 LOG_DEBUG(
1020 "[RenderObjectsToBackground]",
1021 "ObjectDrawer failed: %s - FALLING BACK TO MANUAL",
1022 std::string(status.message().data(), status.message().size()).c_str());
1023
1024 LOG_DEBUG("[RenderObjectsToBackground]",
1025 "Room %d: Manual rendering objects (fallback)", room_id_);
1026 auto& bg1_bmp = bg1_buffer_.bitmap();
1027 // Simple manual rendering: draw a colored rectangle for each object
1028 for (const auto& obj : tile_objects_) {
1029 int x = obj.x() * 8;
1030 int y = obj.y() * 8;
1031 int width = 16; // Default size for manual draw
1032 int height = 16;
1033
1034 // Basic layer-based coloring for manual mode
1035 uint8_t color_idx = 0; // Default transparent
1036 if (obj.GetLayerValue() == 0) {
1037 color_idx = 5; // Example: Reddish for Layer 0
1038 } else if (obj.GetLayerValue() == 1) {
1039 color_idx = 10; // Example: Greenish for Layer 1
1040 } else {
1041 color_idx = 15; // Example: Bluish for Layer 2
1042 }
1043 // Draw simple rectangle using WriteToBGRSurface
1044 for (int py = y; py < y + height && py < bg1_bmp.height(); ++py) {
1045 for (int px = x; px < x + width && px < bg1_bmp.width(); ++px) {
1046 int pixel_offset = (py * bg1_bmp.width()) + px;
1047 bg1_bmp.WriteToPixel(pixel_offset, color_idx);
1048 }
1049 }
1050 }
1051 objects_dirty_ = false; // Mark as clean after manual draw
1052 } else {
1053 // Mark objects as clean after successful render
1054 objects_dirty_ = false;
1055 LOG_DEBUG("[RenderObjectsToBackground]",
1056 "Room %d: Objects rendered successfully", room_id_);
1057 }
1058}
1059
1060// LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead
1061// Room rendering no longer depends on Arena graphics sheets
1062
1064 if (!rom_ || !rom_->is_loaded()) {
1065 return;
1066 }
1067
1068 if (!game_data_) {
1069 return;
1070 }
1071 auto* gfx_buffer_data = &game_data_->graphics_buffer;
1072 if (gfx_buffer_data->empty()) {
1073 return;
1074 }
1075
1076 auto rom_data = rom()->vector();
1077 if (rom_data.empty()) {
1078 return;
1079 }
1080
1081 // Validate animated_frame_ bounds
1082 if (animated_frame_ < 0 || animated_frame_ > 10) {
1083 return;
1084 }
1085
1086 // Validate background_tileset_ bounds
1087 if (background_tileset_ < 0 || background_tileset_ > 255) {
1088 return;
1089 }
1090
1091 int gfx_ptr = SnesToPc(version_constants().kGfxAnimatedPointer);
1092 if (gfx_ptr < 0 || gfx_ptr >= static_cast<int>(rom_data.size())) {
1093 return;
1094 }
1095
1096 int data = 0;
1097 while (data < 1024) {
1098 // Validate buffer access for first operation
1099 // 92 * 4096 = 376832. 1024 * 10 = 10240. Total ~387KB.
1100 int first_offset = data + (92 * 4096) + (1024 * animated_frame_);
1101 if (first_offset >= 0 &&
1102 first_offset < static_cast<int>(gfx_buffer_data->size())) {
1103 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
1104
1105 // Validate current_gfx16_ access
1106 int gfx_offset = data + (7 * 4096);
1107 if (gfx_offset >= 0 &&
1108 gfx_offset < static_cast<int>(current_gfx16_.size())) {
1109 current_gfx16_[gfx_offset] = map_byte;
1110 }
1111 }
1112
1113 // Validate buffer access for second operation
1114 int tileset_index = rom_data[gfx_ptr + background_tileset_];
1115 int second_offset =
1116 data + (tileset_index * 4096) + (1024 * animated_frame_);
1117 if (second_offset >= 0 &&
1118 second_offset < static_cast<int>(gfx_buffer_data->size())) {
1119 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
1120
1121 // Validate current_gfx16_ access
1122 int gfx_offset = data + (7 * 4096) - 1024;
1123 if (gfx_offset >= 0 &&
1124 gfx_offset < static_cast<int>(current_gfx16_.size())) {
1125 current_gfx16_[gfx_offset] = map_byte;
1126 }
1127 }
1128
1129 data++;
1130 }
1131}
1132
1134 LOG_DEBUG("[LoadObjects]", "Starting LoadObjects for room %d", room_id_);
1135 auto rom_data = rom()->vector();
1136
1137 // Enhanced object loading with comprehensive validation
1138 int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
1139 (rom_data[room_object_pointer + 1] << 8) +
1140 (rom_data[room_object_pointer]);
1141 object_pointer = SnesToPc(object_pointer);
1142
1143 // Enhanced bounds checking for object pointer
1144 if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
1145 return;
1146 }
1147
1148 int room_address = object_pointer + (room_id_ * 3);
1149
1150 // Enhanced bounds checking for room address
1151 if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
1152 return;
1153 }
1154
1155 int tile_address = (rom_data[room_address + 2] << 16) +
1156 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1157
1158 int objects_location = SnesToPc(tile_address);
1159
1160 // Enhanced bounds checking for objects location
1161 if (objects_location < 0 || objects_location >= (int)rom_->size()) {
1162 return;
1163 }
1164
1165 // Parse floor graphics and layout with validation
1166 if (objects_location + 1 < (int)rom_->size()) {
1167 if (is_floor_) {
1169 static_cast<uint8_t>(rom_data[objects_location] & 0x0F);
1171 static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F);
1172 LOG_DEBUG("[LoadObjects]",
1173 "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
1175 }
1176
1177 layout = static_cast<uint8_t>((rom_data[objects_location + 1] >> 2) & 0x07);
1178 }
1179
1180 LoadChests();
1181
1182 // Parse objects with enhanced error handling
1183 ParseObjectsFromLocation(objects_location + 2);
1184}
1185
1186void Room::ParseObjectsFromLocation(int objects_location) {
1187 auto rom_data = rom()->vector();
1188
1189 // Clear existing objects before parsing to prevent accumulation on reload
1190 tile_objects_.clear();
1191 doors_.clear();
1192 z3_staircases_.clear();
1193 int nbr_of_staircase = 0;
1194
1195 int pos = objects_location;
1196 uint8_t b1 = 0;
1197 uint8_t b2 = 0;
1198 uint8_t b3 = 0;
1199 int layer = 0;
1200 bool door = false;
1201 bool end_read = false;
1202
1203 // Enhanced parsing loop with bounds checking
1204 // ASM: Main object loop logic (implicit in structure)
1205 while (!end_read && pos < (int)rom_->size()) {
1206 // Check if we have enough bytes to read
1207 if (pos + 1 >= (int)rom_->size()) {
1208 break;
1209 }
1210
1211 b1 = rom_data[pos];
1212 b2 = rom_data[pos + 1];
1213
1214 // ASM Marker: 0xFF 0xFF - End of Layer
1215 // Signals transition between object layers:
1216 // Layer 0 -> BG1 buffer (main floor/walls)
1217 // Layer 1 -> BG2 buffer (overlay layer)
1218 // Layer 2 -> BG1 buffer (priority objects, still on BG1)
1219 if (b1 == 0xFF && b2 == 0xFF) {
1220 pos += 2; // Jump to next layer
1221 layer++;
1222 LOG_DEBUG("Room", "Room %03X: Layer transition to layer %d (%s)",
1223 room_id_, layer,
1224 layer == 1 ? "BG2" : (layer == 2 ? "BG3" : "END"));
1225 door = false;
1226 if (layer == 3) {
1227 break;
1228 }
1229 continue;
1230 }
1231
1232 // ASM Marker: 0xF0 0xFF - Start of Door List
1233 // See RoomDraw_DoorObject ($018916) logic
1234 if (b1 == 0xF0 && b2 == 0xFF) {
1235 pos += 2; // Jump to door section
1236 door = true;
1237 continue;
1238 }
1239
1240 // Check if we have enough bytes for object data
1241 if (pos + 2 >= (int)rom_->size()) {
1242 break;
1243 }
1244
1245 b3 = rom_data[pos + 2];
1246 if (door) {
1247 pos += 2;
1248 } else {
1249 pos += 3;
1250 }
1251
1252 if (!door) {
1253 // ASM: RoomDraw_RoomObject ($01893C)
1254 // Handles Subtype 1, 2, 3 parsing based on byte values
1256 b1, b2, b3, static_cast<uint8_t>(layer));
1257
1258 LOG_DEBUG("Room", "Room %03X: Object 0x%03X at (%d,%d) layer=%d (%s)",
1259 room_id_, r.id_, r.x_, r.y_, layer,
1260 layer == 0 ? "BG1" : (layer == 1 ? "BG2" : "BG3"));
1261
1262 // Validate object ID before adding to the room
1263 // Object IDs can be up to 12-bit (0xFFF) to support Type 3 objects
1264 if (r.id_ >= 0 && r.id_ <= 0xFFF) {
1265 r.SetRom(rom_);
1266 tile_objects_.push_back(r);
1267
1268 // Handle special object types (staircases, chests, etc.)
1269 HandleSpecialObjects(r.id_, r.x(), r.y(), nbr_of_staircase);
1270 }
1271 } else {
1272 // Handle door objects
1273 // ASM format (from RoomDraw_DoorObject):
1274 // b1: bits 4-7 = position index, bits 0-1 = direction
1275 // b2: door type (full byte)
1276 auto door = Door::FromRomBytes(b1, b2);
1277 LOG_DEBUG("Room",
1278 "ParseDoor: room=%d b1=0x%02X b2=0x%02X pos=%d dir=%d type=%d",
1279 room_id_, b1, b2, door.position,
1280 static_cast<int>(door.direction), static_cast<int>(door.type));
1281 doors_.push_back(door);
1282 }
1283 }
1284}
1285
1286// ============================================================================
1287// Object Saving Implementation (Phase 1, Task 1.3)
1288// ============================================================================
1289
1290std::vector<uint8_t> Room::EncodeObjects() const {
1291 std::vector<uint8_t> bytes;
1292
1293 // Organize objects by layer
1294 std::vector<RoomObject> layer0_objects;
1295 std::vector<RoomObject> layer1_objects;
1296 std::vector<RoomObject> layer2_objects;
1297
1298 for (const auto& obj : tile_objects_) {
1299 switch (obj.GetLayerValue()) {
1300 case 0:
1301 layer0_objects.push_back(obj);
1302 break;
1303 case 1:
1304 layer1_objects.push_back(obj);
1305 break;
1306 case 2:
1307 layer2_objects.push_back(obj);
1308 break;
1309 }
1310 }
1311
1312 // Encode Layer 1 (BG2)
1313 for (const auto& obj : layer0_objects) {
1314 auto encoded = obj.EncodeObjectToBytes();
1315 bytes.push_back(encoded.b1);
1316 bytes.push_back(encoded.b2);
1317 bytes.push_back(encoded.b3);
1318 }
1319 bytes.push_back(0xFF);
1320 bytes.push_back(0xFF);
1321
1322 // Encode Layer 2 (BG1)
1323 for (const auto& obj : layer1_objects) {
1324 auto encoded = obj.EncodeObjectToBytes();
1325 bytes.push_back(encoded.b1);
1326 bytes.push_back(encoded.b2);
1327 bytes.push_back(encoded.b3);
1328 }
1329 bytes.push_back(0xFF);
1330 bytes.push_back(0xFF);
1331
1332 // Encode Layer 3
1333 for (const auto& obj : layer2_objects) {
1334 auto encoded = obj.EncodeObjectToBytes();
1335 bytes.push_back(encoded.b1);
1336 bytes.push_back(encoded.b2);
1337 bytes.push_back(encoded.b3);
1338 }
1339
1340 // Final terminator
1341 bytes.push_back(0xFF);
1342 bytes.push_back(0xFF);
1343
1344 return bytes;
1345}
1346
1347std::vector<uint8_t> Room::EncodeSprites() const {
1348 std::vector<uint8_t> bytes;
1349
1350 for (const auto& sprite : sprites_) {
1351 uint8_t b1, b2, b3;
1352
1353 // b3 is simply the ID
1354 b3 = sprite.id();
1355
1356 // b2 = (X & 0x1F) | ((Flags & 0x07) << 5)
1357 // Flags 0-2 come from b2 5-7
1358 b2 = (sprite.x() & 0x1F) | ((sprite.subtype() & 0x07) << 5);
1359
1360 // b1 = (Y & 0x1F) | ((Flags & 0x18) << 2) | ((Layer & 1) << 7)
1361 // Flags 3-4 come from b1 5-6. (0x18 is 00011000)
1362 // Layer bit 0 comes from b1 7
1363 b1 = (sprite.y() & 0x1F) | ((sprite.subtype() & 0x18) << 2) |
1364 ((sprite.layer() & 0x01) << 7);
1365
1366 bytes.push_back(b1);
1367 bytes.push_back(b2);
1368 bytes.push_back(b3);
1369 }
1370
1371 // Terminator
1372 bytes.push_back(0xFF);
1373
1374 return bytes;
1375}
1376
1377absl::Status Room::SaveObjects() {
1378 if (rom_ == nullptr) {
1379 return absl::InvalidArgumentError("ROM pointer is null");
1380 }
1381
1382 auto rom_data = rom()->vector();
1383
1384 // Get object pointer
1385 int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
1386 (rom_data[room_object_pointer + 1] << 8) +
1387 (rom_data[room_object_pointer]);
1388 object_pointer = SnesToPc(object_pointer);
1389
1390 if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
1391 return absl::OutOfRangeError("Object pointer out of range");
1392 }
1393
1394 int room_address = object_pointer + (room_id_ * 3);
1395
1396 if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
1397 return absl::OutOfRangeError("Room address out of range");
1398 }
1399
1400 int tile_address = (rom_data[room_address + 2] << 16) +
1401 (rom_data[room_address + 1] << 8) + rom_data[room_address];
1402
1403 int objects_location = SnesToPc(tile_address);
1404
1405 if (objects_location < 0 || objects_location >= (int)rom_->size()) {
1406 return absl::OutOfRangeError("Objects location out of range");
1407 }
1408
1409 // Calculate available space
1410 RoomSize room_size_info = CalculateRoomSize(rom_, room_id_);
1411 int available_size = room_size_info.room_size;
1412
1413 // Skip graphics/layout header (2 bytes)
1414 int write_pos = objects_location + 2;
1415
1416 // Encode all objects
1417 auto encoded_bytes = EncodeObjects();
1418
1419 // VALIDATION: Check if new data fits in available space
1420 // We subtract 2 bytes for the header which is not part of encoded_bytes
1421 if (encoded_bytes.size() > available_size - 2) {
1422 return absl::OutOfRangeError(absl::StrFormat(
1423 "Room %d object data too large! Size: %d, Available: %d", room_id_,
1424 encoded_bytes.size(), available_size - 2));
1425 }
1426
1427 // Write encoded bytes to ROM using WriteVector
1428 return rom_->WriteVector(write_pos, encoded_bytes);
1429}
1430
1431absl::Status Room::SaveSprites() {
1432 if (rom_ == nullptr) {
1433 return absl::InvalidArgumentError("ROM pointer is null");
1434 }
1435
1436 auto rom_data = rom()->vector();
1437
1438 // Calculate sprite pointer table location
1439 // Bank 09 + rooms_sprite_pointer (was incorrectly 0x04)
1440 int sprite_pointer = (0x09 << 16) +
1441 (rom_data[rooms_sprite_pointer + 1] << 8) +
1442 (rom_data[rooms_sprite_pointer]);
1443 sprite_pointer = SnesToPc(sprite_pointer);
1444
1445 if (sprite_pointer < 0 ||
1446 sprite_pointer + (room_id_ * 2) + 1 >= (int)rom_->size()) {
1447 return absl::OutOfRangeError("Sprite table pointer out of range");
1448 }
1449
1450 // Read room sprite address from table
1451 int sprite_address_snes =
1452 (0x09 << 16) + (rom_data[sprite_pointer + (room_id_ * 2) + 1] << 8) +
1453 rom_data[sprite_pointer + (room_id_ * 2)];
1454
1455 int sprite_address = SnesToPc(sprite_address_snes);
1456
1457 if (sprite_address < 0 || sprite_address >= (int)rom_->size()) {
1458 return absl::OutOfRangeError("Sprite address out of range");
1459 }
1460
1461 // Calculate available space for sprites
1462 // Check next room's sprite pointer
1463 int next_sprite_address_snes =
1464 (0x09 << 16) +
1465 (rom_data[sprite_pointer + ((room_id_ + 1) * 2) + 1] << 8) +
1466 rom_data[sprite_pointer + ((room_id_ + 1) * 2)];
1467
1468 int next_sprite_address = SnesToPc(next_sprite_address_snes);
1469
1470 // Handle wrap-around or end of bank if needed, but usually sequential
1471 int available_size = next_sprite_address - sprite_address;
1472
1473 // If calculation seems wrong (negative or too large), fallback to a safe limit or error
1474 if (available_size <= 0 || available_size > 0x1000) {
1475 // Fallback: Assume standard max or just warn.
1476 // For now, let's be strict but allow a reasonable max if calculation fails (e.g. last room)
1477 if (room_id_ == NumberOfRooms - 1) {
1478 available_size = 0x100; // Arbitrary safe limit for last room
1479 } else {
1480 // If negative, it means pointers are not sequential.
1481 // This happens in some ROMs. We can't easily validate size then without a free space map.
1482 // We'll log a warning and proceed with caution? No, prompt says "Free space validation".
1483 // Let's error out if we can't determine size.
1484 return absl::InternalError(absl::StrFormat(
1485 "Cannot determine available sprite space for room %d", room_id_));
1486 }
1487 }
1488
1489 // Handle sortsprites byte (skip if present)
1490 bool has_sort_sprite = false;
1491 if (rom_data[sprite_address] == 1) {
1492 has_sort_sprite = true;
1493 sprite_address += 1;
1494 available_size -= 1;
1495 }
1496
1497 auto encoded_bytes = EncodeSprites();
1498
1499 // VALIDATION
1500 if (encoded_bytes.size() > available_size) {
1501 return absl::OutOfRangeError(absl::StrFormat(
1502 "Room %d sprite data too large! Size: %d, Available: %d", room_id_,
1503 encoded_bytes.size(), available_size));
1504 }
1505
1506 return rom_->WriteVector(sprite_address, encoded_bytes);
1507}
1508
1509// ============================================================================
1510// Object Manipulation Methods (Phase 3)
1511// ============================================================================
1512
1513absl::Status Room::AddObject(const RoomObject& object) {
1514 // Validate object
1515 if (!ValidateObject(object)) {
1516 return absl::InvalidArgumentError("Invalid object parameters");
1517 }
1518
1519 // Add to internal list
1520 tile_objects_.push_back(object);
1522
1523 return absl::OkStatus();
1524}
1525
1526absl::Status Room::RemoveObject(size_t index) {
1527 if (index >= tile_objects_.size()) {
1528 return absl::OutOfRangeError("Object index out of range");
1529 }
1530
1531 tile_objects_.erase(tile_objects_.begin() + index);
1533
1534 return absl::OkStatus();
1535}
1536
1537absl::Status Room::UpdateObject(size_t index, const RoomObject& object) {
1538 if (index >= tile_objects_.size()) {
1539 return absl::OutOfRangeError("Object index out of range");
1540 }
1541
1542 if (!ValidateObject(object)) {
1543 return absl::InvalidArgumentError("Invalid object parameters");
1544 }
1545
1546 tile_objects_[index] = object;
1548
1549 return absl::OkStatus();
1550}
1551
1552absl::StatusOr<size_t> Room::FindObjectAt(int x, int y, int layer) const {
1553 for (size_t i = 0; i < tile_objects_.size(); i++) {
1554 const auto& obj = tile_objects_[i];
1555 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
1556 return i;
1557 }
1558 }
1559 return absl::NotFoundError("No object found at position");
1560}
1561
1562bool Room::ValidateObject(const RoomObject& object) const {
1563 // Validate position (0-63 for both X and Y)
1564 if (object.x() < 0 || object.x() > 63)
1565 return false;
1566 if (object.y() < 0 || object.y() > 63)
1567 return false;
1568
1569 // Validate layer (0-2)
1570 if (object.GetLayerValue() < 0 || object.GetLayerValue() > 2)
1571 return false;
1572
1573 // Validate object ID range
1574 if (object.id_ < 0 || object.id_ > 0xFFF)
1575 return false;
1576
1577 // Validate size for Type 1 objects
1578 if (object.id_ < 0x100 && object.size() > 15)
1579 return false;
1580
1581 return true;
1582}
1583
1584void Room::HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY,
1585 int& nbr_of_staircase) {
1586 // Handle staircase objects
1587 for (short stair : stairsObjects) {
1588 if (stair == oid) {
1589 if (nbr_of_staircase < 4) {
1590 tile_objects_.back().set_options(ObjectOption::Stairs |
1591 tile_objects_.back().options());
1592 z3_staircases_.push_back(
1593 {posX, posY,
1594 absl::StrCat("To ", staircase_rooms_[nbr_of_staircase]).data()});
1595 nbr_of_staircase++;
1596 } else {
1597 tile_objects_.back().set_options(ObjectOption::Stairs |
1598 tile_objects_.back().options());
1599 z3_staircases_.push_back({posX, posY, "To ???"});
1600 }
1601 break;
1602 }
1603 }
1604
1605 // Handle chest objects
1606 if (oid == 0xF99) {
1607 if (chests_in_room_.size() > 0) {
1608 tile_objects_.back().set_options(ObjectOption::Chest |
1609 tile_objects_.back().options());
1610 chests_in_room_.erase(chests_in_room_.begin());
1611 }
1612 } else if (oid == 0xFB1) {
1613 if (chests_in_room_.size() > 0) {
1614 tile_objects_.back().set_options(ObjectOption::Chest |
1615 tile_objects_.back().options());
1616 chests_in_room_.erase(chests_in_room_.begin());
1617 }
1618 }
1619}
1620
1622 auto rom_data = rom()->vector();
1623 int sprite_pointer = (0x04 << 16) +
1624 (rom_data[rooms_sprite_pointer + 1] << 8) +
1625 (rom_data[rooms_sprite_pointer]);
1626 int sprite_address_snes =
1627 (0x09 << 16) + (rom_data[sprite_pointer + (room_id_ * 2) + 1] << 8) +
1628 rom_data[sprite_pointer + (room_id_ * 2)];
1629
1630 int sprite_address = SnesToPc(sprite_address_snes);
1631 if (rom_data[sprite_address] == 1) {
1632 // sortsprites is unused
1633 }
1634 sprite_address += 1;
1635
1636 while (true) {
1637 uint8_t b1 = rom_data[sprite_address];
1638 uint8_t b2 = rom_data[sprite_address + 1];
1639 uint8_t b3 = rom_data[sprite_address + 2];
1640
1641 if (b1 == 0xFF) {
1642 break;
1643 }
1644
1645 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
1646 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
1647 (b1 & 0x80) >> 7);
1648
1649 if (sprites_.size() > 1) {
1650 Sprite& spr = sprites_.back();
1651 Sprite& prevSprite = sprites_[sprites_.size() - 2];
1652
1653 if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1E &&
1654 spr.layer() == 1 && spr.subtype() == 0x18) {
1655 prevSprite.set_key_drop(1);
1656 sprites_.pop_back();
1657 }
1658
1659 if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1D &&
1660 spr.layer() == 1 && spr.subtype() == 0x18) {
1661 prevSprite.set_key_drop(2);
1662 sprites_.pop_back();
1663 }
1664 }
1665
1666 sprite_address += 3;
1667 }
1668}
1669
1671 auto rom_data = rom()->vector();
1672 uint32_t cpos = SnesToPc((rom_data[chests_data_pointer1 + 2] << 16) +
1673 (rom_data[chests_data_pointer1 + 1] << 8) +
1674 (rom_data[chests_data_pointer1]));
1675 size_t clength = (rom_data[chests_length_pointer + 1] << 8) +
1676 (rom_data[chests_length_pointer]);
1677
1678 for (size_t i = 0; i < clength; i++) {
1679 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
1680 0x7FFF) == room_id_) {
1681 // There's a chest in that room !
1682 bool big = false;
1683 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
1684 0x8000) == 0x8000) {
1685 big = true;
1686 }
1687
1688 chests_in_room_.emplace_back(
1689 chest_data{rom_data[cpos + (i * 3) + 2], big});
1690 }
1691 }
1692}
1693
1695 auto rom_data = rom()->vector();
1696
1697 // Doors are loaded as part of the object stream in LoadObjects()
1698 // When the parser encounters 0xF0 0xFF, it enters door mode
1699 // Door objects have format: b1 (position/direction), b2 (type)
1700 // Door encoding: b1 = (door_pos << 4) | (door_dir & 0x03)
1701 // position in bits 4-7, direction in bits 0-1
1702 // b2 = door_type (full byte, values 0x00, 0x02, 0x04, etc.)
1703 // This is already handled in ParseObjectsFromLocation()
1704
1705 LOG_DEBUG("Room",
1706 "LoadDoors for room %d - doors are loaded via object stream",
1707 room_id_);
1708}
1709
1711 auto rom_data = rom()->vector();
1712
1713 // Read torch data length
1714 int bytes_count = (rom_data[torches_length_pointer + 1] << 8) |
1715 rom_data[torches_length_pointer];
1716
1717 LOG_DEBUG("Room", "LoadTorches: room_id=%d, bytes_count=%d", room_id_,
1718 bytes_count);
1719
1720 // Iterate through torch data to find torches for this room
1721 for (int i = 0; i < bytes_count; i += 2) {
1722 if (i + 1 >= bytes_count)
1723 break;
1724
1725 uint8_t b1 = rom_data[torch_data + i];
1726 uint8_t b2 = rom_data[torch_data + i + 1];
1727
1728 // Skip 0xFFFF markers
1729 if (b1 == 0xFF && b2 == 0xFF) {
1730 continue;
1731 }
1732
1733 // Check if this entry is for our room
1734 uint16_t torch_room_id = (b2 << 8) | b1;
1735 if (torch_room_id == room_id_) {
1736 // Found torches for this room, read them
1737 i += 2;
1738 while (i < bytes_count) {
1739 if (i + 1 >= bytes_count)
1740 break;
1741
1742 b1 = rom_data[torch_data + i];
1743 b2 = rom_data[torch_data + i + 1];
1744
1745 // End of torch list for this room
1746 if (b1 == 0xFF && b2 == 0xFF) {
1747 break;
1748 }
1749
1750 // Decode torch position and properties
1751 int address = ((b2 & 0x1F) << 8 | b1) >> 1;
1752 uint8_t px = address % 64;
1753 uint8_t py = address >> 6;
1754 uint8_t layer = (b2 & 0x20) >> 5;
1755 bool lit = (b2 & 0x80) == 0x80;
1756
1757 // Create torch object (ID 0x150)
1758 RoomObject torch_obj(0x150, px, py, 0, layer);
1759 torch_obj.SetRom(rom_);
1761 // Store lit state if needed (may require adding a field to RoomObject)
1762
1763 tile_objects_.push_back(torch_obj);
1764
1765 LOG_DEBUG("Room", "Loaded torch at (%d,%d) layer=%d lit=%d", px, py,
1766 layer, lit);
1767
1768 i += 2;
1769 }
1770 break; // Found and processed our room's torches
1771 } else {
1772 // Skip to next room's torches
1773 i += 2;
1774 while (i < bytes_count) {
1775 if (i + 1 >= bytes_count)
1776 break;
1777 b1 = rom_data[torch_data + i];
1778 b2 = rom_data[torch_data + i + 1];
1779 if (b1 == 0xFF && b2 == 0xFF) {
1780 break;
1781 }
1782 i += 2;
1783 }
1784 }
1785 }
1786}
1787
1789 auto rom_data = rom()->vector();
1790
1791 // Read blocks length
1792 int blocks_count =
1793 (rom_data[blocks_length + 1] << 8) | rom_data[blocks_length];
1794
1795 LOG_DEBUG("Room", "LoadBlocks: room_id=%d, blocks_count=%d", room_id_,
1796 blocks_count);
1797
1798 // Load block data from multiple pointers
1799 std::vector<uint8_t> blocks_data(blocks_count);
1800
1801 int pos1 = blocks_pointer1;
1802 int pos2 = blocks_pointer2;
1803 int pos3 = blocks_pointer3;
1804 int pos4 = blocks_pointer4;
1805
1806 // Read block data from 4 different locations
1807 for (int i = 0; i < 0x80 && i < blocks_count; i++) {
1808 blocks_data[i] = rom_data[pos1 + i];
1809
1810 if (i + 0x80 < blocks_count) {
1811 blocks_data[i + 0x80] = rom_data[pos2 + i];
1812 }
1813 if (i + 0x100 < blocks_count) {
1814 blocks_data[i + 0x100] = rom_data[pos3 + i];
1815 }
1816 if (i + 0x180 < blocks_count) {
1817 blocks_data[i + 0x180] = rom_data[pos4 + i];
1818 }
1819 }
1820
1821 // Parse blocks for this room (4 bytes per block entry)
1822 for (int i = 0; i < blocks_count; i += 4) {
1823 if (i + 3 >= blocks_count)
1824 break;
1825
1826 uint8_t b1 = blocks_data[i];
1827 uint8_t b2 = blocks_data[i + 1];
1828 uint8_t b3 = blocks_data[i + 2];
1829 uint8_t b4 = blocks_data[i + 3];
1830
1831 // Check if this block belongs to our room
1832 uint16_t block_room_id = (b2 << 8) | b1;
1833 if (block_room_id == room_id_) {
1834 // End marker for this room's blocks
1835 if (b3 == 0xFF && b4 == 0xFF) {
1836 break;
1837 }
1838
1839 // Decode block position
1840 int address = ((b4 & 0x1F) << 8 | b3) >> 1;
1841 uint8_t px = address % 64;
1842 uint8_t py = address >> 6;
1843 uint8_t layer = (b4 & 0x20) >> 5;
1844
1845 // Create block object (ID 0x0E00)
1846 RoomObject block_obj(0x0E00, px, py, 0, layer);
1847 block_obj.SetRom(rom_);
1849
1850 tile_objects_.push_back(block_obj);
1851
1852 LOG_DEBUG("Room", "Loaded block at (%d,%d) layer=%d", px, py, layer);
1853 }
1854 }
1855}
1856
1858 if (!rom_ || !rom_->is_loaded())
1859 return;
1860 auto rom_data = rom()->vector();
1861
1862 // Load pot items
1863 // Format per ASM analysis (bank_01.asm):
1864 // - Pointer table at kRoomItemsPointers (0x01DB69)
1865 // - Each room has a pointer to item data
1866 // - Item data format: 3 bytes per item
1867 // - 2 bytes: position word (Y_hi, X_lo encoding)
1868 // - 1 byte: item type
1869 // - Terminated by 0xFFFF position word
1870
1871 int table_addr = kRoomItemsPointers; // 0x01DB69
1872
1873 // Read pointer for this room
1874 int ptr_addr = table_addr + (room_id_ * 2);
1875 if (ptr_addr + 1 >= static_cast<int>(rom_data.size()))
1876 return;
1877
1878 uint16_t item_ptr = (rom_data[ptr_addr + 1] << 8) | rom_data[ptr_addr];
1879
1880 // Convert to PC address (Bank 01 offset)
1881 int item_addr = SnesToPc(0x010000 | item_ptr);
1882
1883 pot_items_.clear();
1884
1885 // Read 3-byte entries until 0xFFFF terminator
1886 while (item_addr + 2 < static_cast<int>(rom_data.size())) {
1887 // Read position word (little endian)
1888 uint16_t position = (rom_data[item_addr + 1] << 8) | rom_data[item_addr];
1889
1890 // Check for terminator
1891 if (position == 0xFFFF)
1892 break;
1893
1894 // Read item type (3rd byte)
1895 uint8_t item_type = rom_data[item_addr + 2];
1896
1897 PotItem pot_item;
1898 pot_item.position = position;
1899 pot_item.item = item_type;
1900 pot_items_.push_back(pot_item);
1901
1902 item_addr += 3; // Move to next entry
1903 }
1904}
1905
1907 auto rom_data = rom()->vector();
1908
1909 // Read pit count
1910 int pit_entries = rom_data[pit_count] / 2;
1911
1912 // Read pit pointer (long pointer)
1913 int pit_ptr = (rom_data[pit_pointer + 2] << 16) |
1914 (rom_data[pit_pointer + 1] << 8) | rom_data[pit_pointer];
1915 int pit_data_addr = SnesToPc(pit_ptr);
1916
1917 LOG_DEBUG("Room", "LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
1918 room_id_, pit_entries, pit_ptr);
1919
1920 // Pit data is stored as: room_id (2 bytes), target info (2 bytes)
1921 // This data is already loaded in LoadRoomFromRom() into pits_ destination
1922 // struct The pit destination (where you go when you fall) is set via
1923 // SetPitsTarget()
1924
1925 // Pits are typically represented in the layout/collision data, not as objects
1926 // The pits_ member already contains the target room and layer
1927 LOG_DEBUG("Room", "Pit destination - target=%d, target_layer=%d",
1929}
1930
1931} // namespace zelda3
1932} // namespace yaze
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
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:344
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:35
static Arena & Get()
Definition arena.cc:20
void DrawBackground(std::span< uint8_t > gfx16_data)
void DrawFloor(const std::vector< uint8_t > &rom_data, int tile_address, int tile_address_floor, uint8_t floor_graphics)
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
TextureHandle texture() const
Definition bitmap.h:380
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
SDL_Surface * surface() const
Definition bitmap.h:379
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Editor implementation of DungeonState.
Draws dungeon objects to background buffers using game patterns.
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 DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr)
Draw a door to background buffers.
void DrawPotItem(uint8_t item_id, int x, int y, gfx::BackgroundBuffer &bg)
Draw a pot item visualization.
void SetCurrentBitmap(gfx::Bitmap *bitmap)
void LogPaletteLoad(const std::string &location, int palette_id, const gfx::SnesPalette &palette)
static PaletteDebugger & Get()
void LogPaletteApplication(const std::string &location, int palette_id, bool success, const std::string &reason="")
void SetCurrentPalette(const gfx::SnesPalette &palette)
void LogSurfaceState(const std::string &location, SDL_Surface *surface)
RoomLayerManager - Manages layer visibility and compositing.
void CompositeToOutput(Room &room, gfx::Bitmap &output) const
Composite all visible layers into a single output bitmap.
void SetRom(Rom *rom)
Definition room_layout.h:21
const std::vector< RoomObject > & GetObjects() const
Definition room_layout.h:31
absl::Status Draw(int room_id, const uint8_t *gfx_data, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, DungeonState *state) const
absl::Status LoadLayout(int layout_id)
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
void SetRom(Rom *rom)
Definition room_object.h:68
void set_options(ObjectOption options)
void SetBlockset(uint8_t blockset)
Definition room.h:409
bool composite_dirty_
Definition room.h:576
bool ValidateObject(const RoomObject &object) const
Definition room.cc:1562
destination pits_
Definition room.h:634
std::vector< RoomObject > tile_objects_
Definition room.h:618
uint8_t cached_blockset_
Definition room.h:585
void SetSpriteset(uint8_t spriteset)
Definition room.h:415
EffectKey effect_
Definition room.h:629
gfx::BackgroundBuffer object_bg1_buffer_
Definition room.h:571
void SetTag2Direct(TagKey tag2)
Definition room.h:465
uint8_t cached_layout_
Definition room.h:588
absl::Status UpdateObject(size_t index, const RoomObject &object)
Definition room.cc:1537
TagKey cached_tag2_
Definition room.h:593
void SetStair4Target(uint8_t target)
Definition room.h:475
void SetPitsTarget(uint8_t target)
Definition room.h:471
void SetIsLight(bool is_light)
Definition room.h:402
void LoadChests()
Definition room.cc:1670
void MarkObjectsDirty()
Definition room.h:366
gfx::BackgroundBuffer bg2_buffer_
Definition room.h:570
uint8_t cached_floor2_graphics_
Definition room.h:590
std::vector< zelda3::Sprite > sprites_
Definition room.h:620
GameData * game_data_
Definition room.h:563
void SetLoaded(bool loaded)
Definition room.h:479
void CopyRoomGraphicsToBuffer()
Definition room.cc:420
uint8_t cached_palette_
Definition room.h:587
zelda3_version_pointers version_constants() const
Definition room.h:534
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
Definition room.cc:375
uint8_t cached_effect_
Definition room.h:591
std::vector< Door > doors_
Definition room.h:623
bool graphics_dirty_
Definition room.h:596
void SetStaircaseRoom(int index, uint8_t room)
Definition room.h:444
absl::Status RemoveObject(size_t index)
Definition room.cc:1526
void SetStair1TargetLayer(uint8_t layer)
Definition room.h:467
void LoadBlocks()
Definition room.cc:1788
bool layout_dirty_
Definition room.h:598
void SetLayer2Mode(uint8_t mode)
Definition room.h:457
RoomLayout layout_
Definition room.h:625
void LoadLayoutTilesToBuffer()
Definition room.cc:787
void SetTag2(TagKey tag2)
Definition room.h:433
void ParseObjectsFromLocation(int objects_location)
Definition room.cc:1186
uint8_t cached_floor1_graphics_
Definition room.h:589
void SetTag1Direct(TagKey tag1)
Definition room.h:464
void LoadTorches()
Definition room.cc:1710
void SetPaletteDirect(uint8_t palette)
Definition room.h:460
void SetStair2Target(uint8_t target)
Definition room.h:473
int animated_frame_
Definition room.h:602
void SetCollision(CollisionKey collision)
Definition room.h:401
uint8_t blockset
Definition room.h:490
gfx::BackgroundBuffer bg1_buffer_
Definition room.h:569
absl::Status SaveObjects()
Definition room.cc:1377
void SetStaircasePlane(int index, uint8_t plane)
Definition room.h:439
void SetIsDark(bool is_dark)
Definition room.h:459
bool objects_dirty_
Definition room.h:597
uint8_t layout
Definition room.h:493
void RenderRoomGraphics()
Definition room.cc:483
bool textures_dirty_
Definition room.h:599
gfx::Bitmap composite_bitmap_
Definition room.h:575
Room & operator=(Room &&)
uint8_t staircase_rooms_[4]
Definition room.h:605
gfx::Bitmap & GetCompositeBitmap(RoomLayerManager &layer_mgr)
Get a composite bitmap of all layers merged.
Definition room.cc:475
std::vector< uint8_t > EncodeObjects() const
Definition room.cc:1290
void SetEffect(EffectKey effect)
Definition room.h:421
gfx::BackgroundBuffer object_bg2_buffer_
Definition room.h:572
std::array< uint8_t, 16 > blocks_
Definition room.h:615
void SetTag1(TagKey tag1)
Definition room.h:427
void SetBackgroundTileset(uint8_t tileset)
Definition room.h:461
void SetStair3TargetLayer(uint8_t layer)
Definition room.h:469
uint8_t floor2_graphics_
Definition room.h:612
bool IsLight() const
Definition room.h:453
uint8_t floor1_graphics_
Definition room.h:611
void SetMessageIdDirect(uint16_t message_id)
Definition room.h:456
absl::Status SaveSprites()
Definition room.cc:1431
void SetLayerMerging(LayerMergeType merging)
Definition room.h:458
void SetPitsTargetLayer(uint8_t layer)
Definition room.h:466
void LoadObjects()
Definition room.cc:1133
uint8_t spriteset
Definition room.h:491
void LoadPotItems()
Definition room.cc:1857
void SetSpriteTileset(uint8_t tileset)
Definition room.h:462
void SetStair1Target(uint8_t target)
Definition room.h:472
void SetBg2(background2 bg2)
Definition room.h:400
uint8_t palette
Definition room.h:492
std::unique_ptr< DungeonState > dungeon_state_
Definition room.h:640
void LoadAnimatedGraphics()
Definition room.cc:1063
std::vector< uint8_t > EncodeSprites() const
Definition room.cc:1347
std::vector< chest_data > chests_in_room_
Definition room.h:622
LayerMergeType layer_merging_
Definition room.h:627
uint8_t cached_spriteset_
Definition room.h:586
std::array< uint8_t, 0x10000 > current_gfx16_
Definition room.h:565
std::vector< staircase > z3_staircases_
Definition room.h:621
void SetPalette(uint8_t palette)
Definition room.h:403
std::vector< PotItem > pot_items_
Definition room.h:624
void LoadSprites()
Definition room.cc:1621
TagKey cached_tag1_
Definition room.h:592
void SetHolewarp(uint8_t holewarp)
Definition room.h:443
uint8_t background_tileset_
Definition room.h:607
void SetStair3Target(uint8_t target)
Definition room.h:474
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int &nbr_of_staircase)
Definition room.cc:1584
void SetStair4TargetLayer(uint8_t layer)
Definition room.h:470
absl::Status AddObject(const RoomObject &object)
Definition room.cc:1513
absl::StatusOr< size_t > FindObjectAt(int x, int y, int layer) const
Definition room.cc:1552
void SetStair2TargetLayer(uint8_t layer)
Definition room.h:468
void RenderObjectsToBackground()
Definition room.cc:862
void SetLayer2Behavior(uint8_t behavior)
Definition room.h:463
A class for managing sprites in the overworld and underworld.
Definition sprite.h:35
auto id() const
Definition sprite.h:95
auto layer() const
Definition sprite.h:106
auto set_key_drop(int key)
Definition sprite.h:114
auto subtype() const
Definition sprite.h:107
auto y() const
Definition sprite.h:98
auto x() const
Definition sprite.h:97
zelda3_bg2_effect
Background layer 2 effects.
Definition zelda.h:369
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:375
constexpr int kGfxBufferAnimatedFrameStride
Definition room.cc:414
const std::string RoomTag[65]
Definition room.cc:39
constexpr int kGfxBufferAnimatedFrameOffset
Definition room.cc:413
constexpr int chests_length_pointer
Definition room.h:41
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:205
constexpr int chests_data_pointer1
Definition room.h:42
constexpr int pit_count
Definition room.h:56
constexpr int blocks_pointer3
Definition room.h:47
constexpr int blocks_pointer4
Definition room.h:48
constexpr int NumberOfRooms
Definition room.h:70
constexpr int kGfxBufferRoomOffset
Definition room.cc:415
constexpr int rooms_sprite_pointer
Definition room.h:39
constexpr int kGfxBufferRoomSpriteOffset
Definition room.cc:416
RoomSize CalculateRoomSize(Rom *rom, int room_id)
Definition room.cc:104
constexpr int messages_id_dungeon
Definition room.h:43
constexpr int blocks_length
Definition room.h:44
constexpr int tile_address_floor
Definition room.h:69
constexpr int blocks_pointer2
Definition room.h:46
constexpr int kRoomItemsPointers
constexpr int kGfxBufferRoomSpriteStride
Definition room.cc:417
constexpr uint16_t stairsObjects[]
Definition room.h:71
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:176
constexpr uint32_t kDungeonPalettePointerTable
Definition game_data.h:44
constexpr int blocks_pointer1
Definition room.h:45
constexpr int torches_length_pointer
Definition room.h:50
const std::string RoomEffect[8]
Definition room.cc:29
constexpr int kRoomHeaderPointer
constexpr int torch_data
Definition room.h:49
constexpr int kRoomHeaderPointerBank
constexpr int kGfxBufferStride
Definition room.cc:412
constexpr int room_object_pointer
Definition room.h:34
constexpr int kGfxBufferRoomSpriteLastLineOffset
Definition room.cc:418
constexpr int tile_address
Definition room.h:68
constexpr int pit_pointer
Definition room.h:55
constexpr int dungeon_spr_ptrs
Definition room.h:67
constexpr int kGfxBufferOffset
Definition room.cc:411
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
SDL2/SDL3 compatibility layer.
Legacy chest data structure.
Definition zelda.h:438
uint8_t target_layer
Definition zelda.h:451
uint8_t target
Definition zelda.h:450
Represents a group of palettes.
void AddPalette(SnesPalette pal)
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition game_data.h:93
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:92
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
Definition game_data.h:99
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:91
std::vector< uint8_t > graphics_buffer
Definition game_data.h:82
uint16_t position
Definition room.h:136
int64_t room_size_pointer
Definition room.h:650
static Door FromRomBytes(uint8_t b1, uint8_t b2)
Definition room.h:310
Yet Another Zelda3 Editor (YAZE) - Public C API.