#include #include #include #include #include #include "app/rom.h" #include "app/zelda3/dungeon/room.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/dungeon/dungeon_object_editor.h" #include "app/zelda3/dungeon/object_renderer.h" #include "app/zelda3/dungeon/dungeon_editor_system.h" #include "app/gfx/snes_palette.h" namespace yaze { namespace zelda3 { /** * @brief Mock ROM class for testing without real ROM files * * This class provides a mock ROM implementation that can be used for testing * the dungeon object rendering system without requiring actual ROM files. */ class MockRom : public Rom { public: MockRom() { // Initialize mock ROM data InitializeMockData(); } ~MockRom() = default; // Override key methods for testing absl::Status LoadFromFile(const std::string& filename) { // Mock implementation - always succeeds is_loaded_ = true; return absl::OkStatus(); } bool is_loaded() const { return is_loaded_; } size_t size() const { return mock_data_.size(); } uint8_t operator[](size_t index) const { if (index < mock_data_.size()) { return mock_data_[index]; } return 0xFF; // Default value for out-of-bounds } absl::StatusOr ReadByte(size_t address) const { if (address < mock_data_.size()) { return mock_data_[address]; } return absl::OutOfRangeError("Address out of range"); } absl::StatusOr ReadWord(size_t address) const { if (address + 1 < mock_data_.size()) { return static_cast(mock_data_[address]) | (static_cast(mock_data_[address + 1]) << 8); } return absl::OutOfRangeError("Address out of range"); } absl::Status ValidateHeader() const { // Mock validation - always succeeds return absl::OkStatus(); } // Mock palette data struct MockPaletteGroup { std::vector palettes; }; MockPaletteGroup& palette_group() { return mock_palette_group_; } const MockPaletteGroup& palette_group() const { return mock_palette_group_; } private: void InitializeMockData() { // Create mock ROM data (2MB) mock_data_.resize(2 * 1024 * 1024, 0xFF); // Set up mock ROM header mock_data_[0x7FC0] = 'Z'; // ROM name start mock_data_[0x7FC1] = 'E'; mock_data_[0x7FC2] = 'L'; mock_data_[0x7FC3] = 'D'; mock_data_[0x7FC4] = 'A'; mock_data_[0x7FC5] = '3'; mock_data_[0x7FC6] = 0x00; // Version mock_data_[0x7FC7] = 0x00; mock_data_[0x7FD5] = 0x21; // ROM type mock_data_[0x7FD6] = 0x20; // ROM size mock_data_[0x7FD7] = 0x00; // SRAM size mock_data_[0x7FD8] = 0x00; // Country mock_data_[0x7FD9] = 0x00; // License mock_data_[0x7FDA] = 0x00; // Version mock_data_[0x7FDB] = 0x00; // Set up mock room data pointers starting at 0x1F8000 constexpr uint32_t kRoomDataPointersStart = 0x1F8000; constexpr uint32_t kRoomDataStart = 0x0A8000; for (int i = 0; i < 512; i++) { uint32_t pointer_addr = kRoomDataPointersStart + (i * 3); uint32_t room_data_addr = kRoomDataStart + (i * 100); // Mock room data if (pointer_addr + 2 < mock_data_.size()) { mock_data_[pointer_addr] = room_data_addr & 0xFF; mock_data_[pointer_addr + 1] = (room_data_addr >> 8) & 0xFF; mock_data_[pointer_addr + 2] = (room_data_addr >> 16) & 0xFF; } } // Initialize mock palette data InitializeMockPalettes(); is_loaded_ = true; } void InitializeMockPalettes() { // Create mock dungeon palettes for (int i = 0; i < 8; i++) { gfx::SnesPalette palette; // Create a simple 16-color palette for (int j = 0; j < 16; j++) { int intensity = j * 16; palette.AddColor(gfx::SnesColor(intensity, intensity, intensity)); } mock_palette_group_.palettes.push_back(palette); } } std::vector mock_data_; MockPaletteGroup mock_palette_group_; bool is_loaded_ = false; }; /** * @brief Mock room data generator */ class MockRoomGenerator { public: static Room GenerateMockRoom(int room_id, Rom* rom) { Room room(room_id, rom); // Set basic room properties room.SetPalette(room_id % 8); room.SetBlockset(room_id % 16); room.SetSpriteset(room_id % 8); room.SetFloor1(0x00); room.SetFloor2(0x00); room.SetMessageId(0x0000); // Generate mock objects based on room type GenerateMockObjects(room, room_id); return room; } private: static void GenerateMockObjects(Room& room, int room_id) { // Generate different object sets based on room ID if (room_id == 0x0000) { // Ganon's room - special objects room.AddTileObject(RoomObject(0x10, 8, 8, 0x12, 0)); room.AddTileObject(RoomObject(0x20, 12, 12, 0x22, 0)); room.AddTileObject(RoomObject(0x30, 16, 16, 0x12, 1)); } else if (room_id == 0x0002 || room_id == 0x0012) { // Sewer rooms - water and pipes room.AddTileObject(RoomObject(0x20, 5, 5, 0x22, 0)); room.AddTileObject(RoomObject(0x40, 10, 10, 0x12, 0)); room.AddTileObject(RoomObject(0x50, 15, 15, 0x32, 1)); } else { // Standard rooms - basic objects room.AddTileObject(RoomObject(0x10, 5, 5, 0x12, 0)); room.AddTileObject(RoomObject(0x20, 10, 10, 0x22, 0)); if (room_id % 3 == 0) { room.AddTileObject(RoomObject(0xF9, 15, 15, 0x12, 1)); // Chest } if (room_id % 5 == 0) { room.AddTileObject(RoomObject(0x13, 20, 20, 0x32, 2)); // Stairs } } } }; class DungeonObjectRendererMockTest : public ::testing::Test { protected: void SetUp() override { // Create mock ROM mock_rom_ = std::make_unique(); // Initialize dungeon editor system with mock ROM dungeon_editor_system_ = std::make_unique(mock_rom_.get()); ASSERT_TRUE(dungeon_editor_system_->Initialize().ok()); // Initialize object editor object_editor_ = std::make_shared(mock_rom_.get()); // Note: InitializeEditor() is private, so we skip this in mock tests // Initialize object renderer object_renderer_ = std::make_unique(mock_rom_.get()); // Generate mock room data ASSERT_TRUE(GenerateMockRoomData().ok()); } void TearDown() override { object_renderer_.reset(); object_editor_.reset(); dungeon_editor_system_.reset(); mock_rom_.reset(); } absl::Status GenerateMockRoomData() { // Generate mock rooms for testing std::vector test_rooms = {0x0000, 0x0001, 0x0002, 0x0010, 0x0012, 0x0020}; for (int room_id : test_rooms) { auto mock_room = MockRoomGenerator::GenerateMockRoom(room_id, mock_rom_.get()); rooms_[room_id] = mock_room; std::cout << "Generated mock room 0x" << std::hex << room_id << std::dec << " with " << mock_room.GetTileObjects().size() << " objects" << std::endl; } // Get mock palettes auto palette_group = mock_rom_->palette_group().palettes; test_palettes_ = {palette_group[0], palette_group[1], palette_group[2]}; return absl::OkStatus(); } // Helper methods RoomObject CreateMockObject(int object_id, int x, int y, int size = 0x12, int layer = 0) { RoomObject obj(object_id, x, y, size, layer); obj.set_rom(mock_rom_.get()); obj.EnsureTilesLoaded(); return obj; } std::vector CreateMockObjectSet() { std::vector objects; objects.push_back(CreateMockObject(0x10, 5, 5, 0x12, 0)); // Wall objects.push_back(CreateMockObject(0x20, 10, 10, 0x22, 0)); // Floor objects.push_back(CreateMockObject(0xF9, 15, 15, 0x12, 1)); // Chest return objects; } std::unique_ptr mock_rom_; std::unique_ptr dungeon_editor_system_; std::shared_ptr object_editor_; std::unique_ptr object_renderer_; std::map rooms_; std::vector test_palettes_; }; // Test basic mock ROM functionality TEST_F(DungeonObjectRendererMockTest, MockROMBasicFunctionality) { EXPECT_TRUE(mock_rom_->is_loaded()); EXPECT_GT(mock_rom_->size(), 0); // Test ROM header validation auto header_result = mock_rom_->ValidateHeader(); EXPECT_TRUE(header_result.ok()); // Test reading ROM data auto byte_result = mock_rom_->ReadByte(0x7FC0); EXPECT_TRUE(byte_result.ok()); EXPECT_EQ(byte_result.value(), 'Z'); auto word_result = mock_rom_->ReadWord(0x1F8000); EXPECT_TRUE(word_result.ok()); EXPECT_GT(word_result.value(), 0); } // Test mock room generation TEST_F(DungeonObjectRendererMockTest, MockRoomGeneration) { EXPECT_GT(rooms_.size(), 0); for (const auto& [room_id, room] : rooms_) { // Note: room_id_ is private, so we can't directly access it in tests EXPECT_GT(room.GetTileObjects().size(), 0); std::cout << "Mock room 0x" << std::hex << room_id << std::dec << " has " << room.GetTileObjects().size() << " objects" << std::endl; } } // Test object rendering with mock data TEST_F(DungeonObjectRendererMockTest, MockObjectRendering) { auto mock_objects = CreateMockObjectSet(); auto palette = test_palettes_[0]; auto result = object_renderer_->RenderObjects(mock_objects, palette); ASSERT_TRUE(result.ok()) << "Failed to render mock objects: " << result.status().message(); auto bitmap = std::move(result.value()); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); } // Test mock room object rendering TEST_F(DungeonObjectRendererMockTest, MockRoomObjectRendering) { for (const auto& [room_id, room] : rooms_) { const auto& objects = room.GetTileObjects(); auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]); ASSERT_TRUE(result.ok()) << "Failed to render mock room 0x" << std::hex << room_id << std::dec; auto bitmap = std::move(result.value()); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); std::cout << "Successfully rendered mock room 0x" << std::hex << room_id << std::dec << " with " << objects.size() << " objects" << std::endl; } } // Test mock object editor functionality TEST_F(DungeonObjectRendererMockTest, MockObjectEditorFunctionality) { // Load a mock room ASSERT_TRUE(object_editor_->LoadRoom(0x0000).ok()); // Add objects ASSERT_TRUE(object_editor_->InsertObject(5, 5, 0x10, 0x12, 0).ok()); ASSERT_TRUE(object_editor_->InsertObject(10, 10, 0x20, 0x22, 1).ok()); // Get objects and render them const auto& objects = object_editor_->GetObjects(); EXPECT_GT(objects.size(), 0); auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]); ASSERT_TRUE(result.ok()) << "Failed to render objects from mock editor"; auto bitmap = std::move(result.value()); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); } // Test mock object editor undo/redo TEST_F(DungeonObjectRendererMockTest, MockObjectEditorUndoRedo) { // Load a mock room and add objects ASSERT_TRUE(object_editor_->LoadRoom(0x0000).ok()); ASSERT_TRUE(object_editor_->InsertObject(5, 5, 0x10, 0x12, 0).ok()); ASSERT_TRUE(object_editor_->InsertObject(10, 10, 0x20, 0x22, 1).ok()); auto objects_before = object_editor_->GetObjects(); // Undo one operation ASSERT_TRUE(object_editor_->Undo().ok()); auto objects_after = object_editor_->GetObjects(); EXPECT_EQ(objects_after.size(), objects_before.size() - 1); // Redo the operation ASSERT_TRUE(object_editor_->Redo().ok()); auto objects_redo = object_editor_->GetObjects(); EXPECT_EQ(objects_redo.size(), objects_before.size()); } // Test mock dungeon editor system integration TEST_F(DungeonObjectRendererMockTest, MockDungeonEditorSystemIntegration) { // Set current room ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok()); // Get object editor from system auto system_object_editor = dungeon_editor_system_->GetObjectEditor(); ASSERT_NE(system_object_editor, nullptr); // Add objects through the system ASSERT_TRUE(system_object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok()); ASSERT_TRUE(system_object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok()); // Get objects and render them const auto& objects = system_object_editor->GetObjects(); ASSERT_GT(objects.size(), 0); auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]); ASSERT_TRUE(result.ok()) << "Failed to render objects from mock system"; auto bitmap = std::move(result.value()); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); } // Test mock performance TEST_F(DungeonObjectRendererMockTest, MockPerformanceTest) { auto mock_objects = CreateMockObjectSet(); auto palette = test_palettes_[0]; auto start_time = std::chrono::high_resolution_clock::now(); // Render objects multiple times for (int i = 0; i < 100; i++) { auto result = object_renderer_->RenderObjects(mock_objects, palette); ASSERT_TRUE(result.ok()); } auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); // Should complete in reasonable time (less than 1000ms for 100 renders) EXPECT_LT(duration.count(), 1000) << "Mock rendering too slow: " << duration.count() << "ms"; std::cout << "Mock performance test: 100 renders took " << duration.count() << "ms" << std::endl; } // Test mock error handling TEST_F(DungeonObjectRendererMockTest, MockErrorHandling) { // Test with empty object list std::vector empty_objects; auto result = object_renderer_->RenderObjects(empty_objects, test_palettes_[0]); // Should either succeed with empty bitmap or fail gracefully if (!result.ok()) { EXPECT_TRUE(absl::IsInvalidArgument(result.status()) || absl::IsFailedPrecondition(result.status())); } // Test with invalid object (no ROM set) RoomObject invalid_object(0x10, 5, 5, 0x12, 0); // Don't set ROM - this should cause an error std::vector invalid_objects = {invalid_object}; result = object_renderer_->RenderObjects(invalid_objects, test_palettes_[0]); // May succeed or fail depending on implementation - just ensure it doesn't crash // EXPECT_FALSE(result.ok()); } // Test mock object type validation TEST_F(DungeonObjectRendererMockTest, MockObjectTypeValidation) { std::vector object_types = {0x10, 0x20, 0x30, 0xF9, 0x13, 0x17}; for (int object_type : object_types) { auto object = CreateMockObject(object_type, 10, 10, 0x12, 0); std::vector objects = {object}; auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]); if (result.ok()) { auto bitmap = std::move(result.value()); EXPECT_GT(bitmap.width(), 0); EXPECT_GT(bitmap.height(), 0); std::cout << "Mock object type 0x" << std::hex << object_type << std::dec << " rendered successfully" << std::endl; } else { std::cout << "Mock object type 0x" << std::hex << object_type << std::dec << " failed to render: " << result.status().message() << std::endl; } } } // Test mock cache functionality TEST_F(DungeonObjectRendererMockTest, MockCacheFunctionality) { auto mock_objects = CreateMockObjectSet(); auto palette = test_palettes_[0]; // Reset performance stats object_renderer_->ResetPerformanceStats(); // First render (should miss cache) auto result1 = object_renderer_->RenderObjects(mock_objects, palette); ASSERT_TRUE(result1.ok()); auto stats1 = object_renderer_->GetPerformanceStats(); // Second render with same objects (should hit cache) auto result2 = object_renderer_->RenderObjects(mock_objects, palette); ASSERT_TRUE(result2.ok()); auto stats2 = object_renderer_->GetPerformanceStats(); EXPECT_GE(stats2.cache_hits, stats1.cache_hits); std::cout << "Mock cache test: " << stats2.cache_hits << " hits, " << stats2.cache_misses << " misses" << std::endl; } } // namespace zelda3 } // namespace yaze