backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
@@ -48,7 +48,7 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
for (int room_id : test_rooms_) {
|
||||
auto room_result = dungeon_editor_system_->GetRoom(room_id);
|
||||
if (room_result.ok()) {
|
||||
rooms_[room_id] = room_result.value();
|
||||
rooms_[room_id] = std::move(room_result.value());
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
@@ -79,9 +79,6 @@ TEST_F(DungeonEditorSystemIntegrationTest, RoomLoadingAndManagement) {
|
||||
ASSERT_TRUE(room_result.ok())
|
||||
<< "Failed to load room 0x0000: " << room_result.status().message();
|
||||
|
||||
const auto& room = room_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
|
||||
// Test setting current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
EXPECT_EQ(dungeon_editor_system_->GetCurrentRoom(), 0x0000);
|
||||
@@ -90,9 +87,6 @@ TEST_F(DungeonEditorSystemIntegrationTest, RoomLoadingAndManagement) {
|
||||
auto room2_result = dungeon_editor_system_->GetRoom(0x0001);
|
||||
ASSERT_TRUE(room2_result.ok())
|
||||
<< "Failed to load room 0x0001: " << room2_result.status().message();
|
||||
|
||||
const auto& room2 = room2_result.value();
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
}
|
||||
|
||||
// Test object editor integration
|
||||
@@ -121,335 +115,6 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
}
|
||||
|
||||
// Test sprite management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SpriteManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
// Create sprite data
|
||||
DungeonEditorSystem::SpriteData sprite_data;
|
||||
sprite_data.sprite_id = 1;
|
||||
sprite_data.name = "Test Sprite";
|
||||
sprite_data.type = DungeonEditorSystem::SpriteType::kEnemy;
|
||||
sprite_data.x = 100;
|
||||
sprite_data.y = 100;
|
||||
sprite_data.layer = 0;
|
||||
sprite_data.is_active = true;
|
||||
|
||||
// Add sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
// Get sprites for room
|
||||
auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_result.ok())
|
||||
<< "Failed to get sprites: " << sprites_result.status().message();
|
||||
|
||||
const auto& sprites = sprites_result.value();
|
||||
EXPECT_EQ(sprites.size(), 1);
|
||||
EXPECT_EQ(sprites[0].sprite_id, 1);
|
||||
EXPECT_EQ(sprites[0].name, "Test Sprite");
|
||||
|
||||
// Update sprite
|
||||
sprite_data.x = 150;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateSprite(1, sprite_data).ok());
|
||||
|
||||
// Get updated sprite
|
||||
auto sprite_result = dungeon_editor_system_->GetSprite(1);
|
||||
ASSERT_TRUE(sprite_result.ok());
|
||||
EXPECT_EQ(sprite_result.value().x, 150);
|
||||
|
||||
// Remove sprite
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveSprite(1).ok());
|
||||
|
||||
// Verify sprite was removed
|
||||
auto sprites_after = dungeon_editor_system_->GetSpritesByRoom(0x0000);
|
||||
ASSERT_TRUE(sprites_after.ok());
|
||||
EXPECT_EQ(sprites_after.value().size(), 0);
|
||||
}
|
||||
|
||||
// Test item management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ItemManagement) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
// Create item data
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = 1;
|
||||
item_data.type = DungeonEditorSystem::ItemType::kKey;
|
||||
item_data.name = "Small Key";
|
||||
item_data.x = 200;
|
||||
item_data.y = 200;
|
||||
item_data.room_id = 0x0000;
|
||||
item_data.is_hidden = false;
|
||||
|
||||
// Add item
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
|
||||
// Get items for room
|
||||
auto items_result = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_result.ok())
|
||||
<< "Failed to get items: " << items_result.status().message();
|
||||
|
||||
const auto& items = items_result.value();
|
||||
EXPECT_EQ(items.size(), 1);
|
||||
EXPECT_EQ(items[0].item_id, 1);
|
||||
EXPECT_EQ(items[0].name, "Small Key");
|
||||
|
||||
// Update item
|
||||
item_data.is_hidden = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateItem(1, item_data).ok());
|
||||
|
||||
// Get updated item
|
||||
auto item_result = dungeon_editor_system_->GetItem(1);
|
||||
ASSERT_TRUE(item_result.ok());
|
||||
EXPECT_TRUE(item_result.value().is_hidden);
|
||||
|
||||
// Remove item
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveItem(1).ok());
|
||||
|
||||
// Verify item was removed
|
||||
auto items_after = dungeon_editor_system_->GetItemsByRoom(0x0000);
|
||||
ASSERT_TRUE(items_after.ok());
|
||||
EXPECT_EQ(items_after.value().size(), 0);
|
||||
}
|
||||
|
||||
// Test entrance management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, EntranceManagement) {
|
||||
// Create entrance data
|
||||
DungeonEditorSystem::EntranceData entrance_data;
|
||||
entrance_data.entrance_id = 1;
|
||||
entrance_data.type = DungeonEditorSystem::EntranceType::kDoor;
|
||||
entrance_data.name = "Test Entrance";
|
||||
entrance_data.source_room_id = 0x0000;
|
||||
entrance_data.target_room_id = 0x0001;
|
||||
entrance_data.source_x = 100;
|
||||
entrance_data.source_y = 100;
|
||||
entrance_data.target_x = 200;
|
||||
entrance_data.target_y = 200;
|
||||
entrance_data.is_bidirectional = true;
|
||||
|
||||
// Add entrance
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddEntrance(entrance_data).ok());
|
||||
|
||||
// Get entrances for room
|
||||
auto entrances_result = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_result.ok())
|
||||
<< "Failed to get entrances: " << entrances_result.status().message();
|
||||
|
||||
const auto& entrances = entrances_result.value();
|
||||
EXPECT_EQ(entrances.size(), 1);
|
||||
EXPECT_EQ(entrances[0].name, "Test Entrance");
|
||||
|
||||
// Store the entrance ID for later removal
|
||||
int entrance_id = entrances[0].entrance_id;
|
||||
|
||||
// Test room connection
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->ConnectRooms(0x0000, 0x0001, 150, 150, 250, 250)
|
||||
.ok());
|
||||
|
||||
// Get updated entrances
|
||||
auto entrances_after = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_after.ok());
|
||||
EXPECT_GE(entrances_after.value().size(), 1);
|
||||
|
||||
// Remove entrance using the correct ID
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveEntrance(entrance_id).ok());
|
||||
|
||||
// Verify entrance was removed
|
||||
auto entrances_final = dungeon_editor_system_->GetEntrancesByRoom(0x0000);
|
||||
ASSERT_TRUE(entrances_final.ok());
|
||||
EXPECT_EQ(entrances_final.value().size(), 0);
|
||||
}
|
||||
|
||||
// Test door management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, DoorManagement) {
|
||||
// Create door data
|
||||
DungeonEditorSystem::DoorData door_data;
|
||||
door_data.door_id = 1;
|
||||
door_data.name = "Test Door";
|
||||
door_data.room_id = 0x0000;
|
||||
door_data.x = 100;
|
||||
door_data.y = 100;
|
||||
door_data.direction = 0; // up
|
||||
door_data.target_room_id = 0x0001;
|
||||
door_data.target_x = 200;
|
||||
door_data.target_y = 200;
|
||||
door_data.requires_key = false;
|
||||
door_data.key_type = 0;
|
||||
door_data.is_locked = false;
|
||||
|
||||
// Add door
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddDoor(door_data).ok());
|
||||
|
||||
// Get doors for room
|
||||
auto doors_result = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_result.ok())
|
||||
<< "Failed to get doors: " << doors_result.status().message();
|
||||
|
||||
const auto& doors = doors_result.value();
|
||||
EXPECT_EQ(doors.size(), 1);
|
||||
EXPECT_EQ(doors[0].door_id, 1);
|
||||
EXPECT_EQ(doors[0].name, "Test Door");
|
||||
|
||||
// Update door
|
||||
door_data.is_locked = true;
|
||||
ASSERT_TRUE(dungeon_editor_system_->UpdateDoor(1, door_data).ok());
|
||||
|
||||
// Get updated door
|
||||
auto door_result = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_result.ok());
|
||||
EXPECT_TRUE(door_result.value().is_locked);
|
||||
|
||||
// Set door key requirement
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDoorKeyRequirement(1, true, 1).ok());
|
||||
|
||||
// Get door with key requirement
|
||||
auto door_with_key = dungeon_editor_system_->GetDoor(1);
|
||||
ASSERT_TRUE(door_with_key.ok());
|
||||
EXPECT_TRUE(door_with_key.value().requires_key);
|
||||
EXPECT_EQ(door_with_key.value().key_type, 1);
|
||||
|
||||
// Remove door
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveDoor(1).ok());
|
||||
|
||||
// Verify door was removed
|
||||
auto doors_after = dungeon_editor_system_->GetDoorsByRoom(0x0000);
|
||||
ASSERT_TRUE(doors_after.ok());
|
||||
EXPECT_EQ(doors_after.value().size(), 0);
|
||||
}
|
||||
|
||||
// Test chest management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ChestManagement) {
|
||||
// Create chest data
|
||||
DungeonEditorSystem::ChestData chest_data;
|
||||
chest_data.chest_id = 1;
|
||||
chest_data.room_id = 0x0000;
|
||||
chest_data.x = 100;
|
||||
chest_data.y = 100;
|
||||
chest_data.is_big_chest = false;
|
||||
chest_data.item_id = 10;
|
||||
chest_data.item_quantity = 1;
|
||||
chest_data.is_opened = false;
|
||||
|
||||
// Add chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddChest(chest_data).ok());
|
||||
|
||||
// Get chests for room
|
||||
auto chests_result = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_result.ok())
|
||||
<< "Failed to get chests: " << chests_result.status().message();
|
||||
|
||||
const auto& chests = chests_result.value();
|
||||
EXPECT_EQ(chests.size(), 1);
|
||||
EXPECT_EQ(chests[0].chest_id, 1);
|
||||
EXPECT_EQ(chests[0].item_id, 10);
|
||||
|
||||
// Update chest item
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestItem(1, 20, 5).ok());
|
||||
|
||||
// Get updated chest
|
||||
auto chest_result = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(chest_result.ok());
|
||||
EXPECT_EQ(chest_result.value().item_id, 20);
|
||||
EXPECT_EQ(chest_result.value().item_quantity, 5);
|
||||
|
||||
// Set chest as opened
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetChestOpened(1, true).ok());
|
||||
|
||||
// Get opened chest
|
||||
auto opened_chest = dungeon_editor_system_->GetChest(1);
|
||||
ASSERT_TRUE(opened_chest.ok());
|
||||
EXPECT_TRUE(opened_chest.value().is_opened);
|
||||
|
||||
// Remove chest
|
||||
ASSERT_TRUE(dungeon_editor_system_->RemoveChest(1).ok());
|
||||
|
||||
// Verify chest was removed
|
||||
auto chests_after = dungeon_editor_system_->GetChestsByRoom(0x0000);
|
||||
ASSERT_TRUE(chests_after.ok());
|
||||
EXPECT_EQ(chests_after.value().size(), 0);
|
||||
}
|
||||
|
||||
// Test room properties management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, RoomPropertiesManagement) {
|
||||
// Create room properties
|
||||
DungeonEditorSystem::RoomProperties properties;
|
||||
properties.room_id = 0x0000;
|
||||
properties.name = "Test Room";
|
||||
properties.description = "A test room for integration testing";
|
||||
properties.dungeon_id = 1;
|
||||
properties.floor_level = 0;
|
||||
properties.is_boss_room = false;
|
||||
properties.is_save_room = false;
|
||||
properties.is_shop_room = false;
|
||||
properties.music_id = 1;
|
||||
properties.ambient_sound_id = 0;
|
||||
|
||||
// Set room properties
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Get room properties
|
||||
auto properties_result = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(properties_result.ok()) << "Failed to get room properties: "
|
||||
<< properties_result.status().message();
|
||||
|
||||
const auto& retrieved_properties = properties_result.value();
|
||||
EXPECT_EQ(retrieved_properties.room_id, 0x0000);
|
||||
EXPECT_EQ(retrieved_properties.name, "Test Room");
|
||||
EXPECT_EQ(retrieved_properties.description,
|
||||
"A test room for integration testing");
|
||||
EXPECT_EQ(retrieved_properties.dungeon_id, 1);
|
||||
|
||||
// Update properties
|
||||
properties.name = "Updated Test Room";
|
||||
properties.is_boss_room = true;
|
||||
ASSERT_TRUE(
|
||||
dungeon_editor_system_->SetRoomProperties(0x0000, properties).ok());
|
||||
|
||||
// Verify update
|
||||
auto updated_properties = dungeon_editor_system_->GetRoomProperties(0x0000);
|
||||
ASSERT_TRUE(updated_properties.ok());
|
||||
EXPECT_EQ(updated_properties.value().name, "Updated Test Room");
|
||||
EXPECT_TRUE(updated_properties.value().is_boss_room);
|
||||
}
|
||||
|
||||
// Test dungeon settings management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, DungeonSettingsManagement) {
|
||||
// Create dungeon settings
|
||||
DungeonEditorSystem::DungeonSettings settings;
|
||||
settings.dungeon_id = 1;
|
||||
settings.name = "Test Dungeon";
|
||||
settings.description = "A test dungeon for integration testing";
|
||||
settings.total_rooms = 10;
|
||||
settings.starting_room_id = 0x0000;
|
||||
settings.boss_room_id = 0x0001;
|
||||
settings.music_theme_id = 1;
|
||||
settings.color_palette_id = 0;
|
||||
settings.has_map = true;
|
||||
settings.has_compass = true;
|
||||
settings.has_big_key = true;
|
||||
|
||||
// Set dungeon settings
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetDungeonSettings(settings).ok());
|
||||
|
||||
// Get dungeon settings
|
||||
auto settings_result = dungeon_editor_system_->GetDungeonSettings();
|
||||
ASSERT_TRUE(settings_result.ok()) << "Failed to get dungeon settings: "
|
||||
<< settings_result.status().message();
|
||||
|
||||
const auto& retrieved_settings = settings_result.value();
|
||||
EXPECT_EQ(retrieved_settings.dungeon_id, 1);
|
||||
EXPECT_EQ(retrieved_settings.name, "Test Dungeon");
|
||||
EXPECT_EQ(retrieved_settings.total_rooms, 10);
|
||||
EXPECT_EQ(retrieved_settings.starting_room_id, 0x0000);
|
||||
EXPECT_EQ(retrieved_settings.boss_room_id, 0x0001);
|
||||
EXPECT_TRUE(retrieved_settings.has_map);
|
||||
EXPECT_TRUE(retrieved_settings.has_compass);
|
||||
EXPECT_TRUE(retrieved_settings.has_big_key);
|
||||
}
|
||||
|
||||
// Test undo/redo functionality
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
// Set current room
|
||||
@@ -485,22 +150,6 @@ TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
}
|
||||
|
||||
// Test validation functionality
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ValidationFunctionality) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
// Validate room
|
||||
auto room_validation = dungeon_editor_system_->ValidateRoom(0x0000);
|
||||
ASSERT_TRUE(room_validation.ok())
|
||||
<< "Room validation failed: " << room_validation.message();
|
||||
|
||||
// Validate dungeon
|
||||
auto dungeon_validation = dungeon_editor_system_->ValidateDungeon();
|
||||
ASSERT_TRUE(dungeon_validation.ok())
|
||||
<< "Dungeon validation failed: " << dungeon_validation.message();
|
||||
}
|
||||
|
||||
// Test save/load functionality
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
// Set current room and add some objects
|
||||
@@ -526,45 +175,6 @@ TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveDungeon().ok());
|
||||
}
|
||||
|
||||
// Test performance with multiple operations
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, PerformanceTest) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Perform many operations
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Add sprite
|
||||
DungeonEditorSystem::SpriteData sprite_data;
|
||||
sprite_data.sprite_id = i;
|
||||
sprite_data.type = DungeonEditorSystem::SpriteType::kEnemy;
|
||||
sprite_data.x = i * 10;
|
||||
sprite_data.y = i * 10;
|
||||
sprite_data.layer = 0;
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddSprite(sprite_data).ok());
|
||||
|
||||
// Add item
|
||||
DungeonEditorSystem::ItemData item_data;
|
||||
item_data.item_id = i;
|
||||
item_data.type = DungeonEditorSystem::ItemType::kKey;
|
||||
item_data.x = i * 15;
|
||||
item_data.y = i * 15;
|
||||
item_data.room_id = 0x0000;
|
||||
|
||||
ASSERT_TRUE(dungeon_editor_system_->AddItem(item_data).ok());
|
||||
}
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
|
||||
// Should complete in reasonable time (less than 5 seconds for 200 operations)
|
||||
EXPECT_LT(duration.count(), 5000)
|
||||
<< "Performance test too slow: " << duration.count() << "ms";
|
||||
|
||||
std::cout << "Performance test: 200 operations took " << duration.count()
|
||||
<< "ms" << std::endl;
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ErrorHandling) {
|
||||
// Test with invalid room ID
|
||||
@@ -574,25 +184,25 @@ TEST_F(DungeonEditorSystemIntegrationTest, ErrorHandling) {
|
||||
auto invalid_room_large = dungeon_editor_system_->GetRoom(10000);
|
||||
EXPECT_FALSE(invalid_room_large.ok());
|
||||
|
||||
// Test with invalid sprite ID
|
||||
auto invalid_sprite = dungeon_editor_system_->GetSprite(-1);
|
||||
EXPECT_FALSE(invalid_sprite.ok());
|
||||
// Test setting invalid room ID
|
||||
auto invalid_set = dungeon_editor_system_->SetCurrentRoom(-1);
|
||||
EXPECT_FALSE(invalid_set.ok());
|
||||
|
||||
// Test with invalid item ID
|
||||
auto invalid_item = dungeon_editor_system_->GetItem(-1);
|
||||
EXPECT_FALSE(invalid_item.ok());
|
||||
auto invalid_set_large = dungeon_editor_system_->SetCurrentRoom(10000);
|
||||
EXPECT_FALSE(invalid_set_large.ok());
|
||||
}
|
||||
|
||||
// Test with invalid entrance ID
|
||||
auto invalid_entrance = dungeon_editor_system_->GetEntrance(-1);
|
||||
EXPECT_FALSE(invalid_entrance.ok());
|
||||
// Test editor state
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, EditorState) {
|
||||
// Get initial state
|
||||
auto state = dungeon_editor_system_->GetEditorState();
|
||||
EXPECT_EQ(state.current_room_id, 0);
|
||||
EXPECT_FALSE(state.is_dirty);
|
||||
|
||||
// Test with invalid door ID
|
||||
auto invalid_door = dungeon_editor_system_->GetDoor(-1);
|
||||
EXPECT_FALSE(invalid_door.ok());
|
||||
|
||||
// Test with invalid chest ID
|
||||
auto invalid_chest = dungeon_editor_system_->GetChest(-1);
|
||||
EXPECT_FALSE(invalid_chest.ok());
|
||||
// Change room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0010).ok());
|
||||
state = dungeon_editor_system_->GetEditorState();
|
||||
EXPECT_EQ(state.current_room_id, 0x0010);
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
|
||||
337
test/integration/zelda3/dungeon_graphics_transparency_test.cc
Normal file
337
test/integration/zelda3/dungeon_graphics_transparency_test.cc
Normal file
@@ -0,0 +1,337 @@
|
||||
// Integration tests for Dungeon Graphics Buffer Transparency
|
||||
// Verifies that 3BPP→8BPP conversion preserves transparent pixels (value 0)
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/game_data.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace test {
|
||||
|
||||
class DungeonGraphicsTransparencyTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
rom_ = std::make_unique<Rom>();
|
||||
|
||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
||||
if (!rom_path) {
|
||||
rom_path = "zelda3.sfc";
|
||||
}
|
||||
|
||||
auto status = rom_->LoadFromFile(rom_path);
|
||||
if (!status.ok()) {
|
||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||
}
|
||||
|
||||
// Load all Zelda3 game data (metadata, palettes, gfx groups, graphics)
|
||||
auto load_status = LoadGameData(*rom_, game_data_);
|
||||
if (!load_status.ok()) {
|
||||
GTEST_SKIP() << "Graphics loading failed: " << load_status.message();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
GameData game_data_;
|
||||
};
|
||||
|
||||
// Test 1: Verify graphics buffer has transparent pixels
|
||||
TEST_F(DungeonGraphicsTransparencyTest, GraphicsBufferHasTransparentPixels) {
|
||||
// The graphics buffer should contain many 0s representing transparent pixels
|
||||
auto& gfx_buffer = game_data_.graphics_buffer;
|
||||
ASSERT_GT(gfx_buffer.size(), 0);
|
||||
|
||||
// Count zeros in first 10 sheets (dungeon graphics)
|
||||
int zero_count = 0;
|
||||
int total_pixels = 0;
|
||||
const int sheets_to_check = 10;
|
||||
const int pixels_per_sheet = 4096;
|
||||
|
||||
for (int sheet = 0; sheet < sheets_to_check; sheet++) {
|
||||
int offset = sheet * pixels_per_sheet;
|
||||
if (offset + pixels_per_sheet > static_cast<int>(gfx_buffer.size())) break;
|
||||
|
||||
for (int i = 0; i < pixels_per_sheet; i++) {
|
||||
if (gfx_buffer[offset + i] == 0) zero_count++;
|
||||
total_pixels++;
|
||||
}
|
||||
}
|
||||
|
||||
float zero_percent = 100.0f * zero_count / total_pixels;
|
||||
printf("[GraphicsBuffer] Zeros: %d / %d (%.1f%%)\n", zero_count, total_pixels,
|
||||
zero_percent);
|
||||
|
||||
// In 3BPP graphics, we expect significant transparent pixels (10%+)
|
||||
// If this is near 0%, something is wrong with the 8BPP conversion
|
||||
EXPECT_GT(zero_percent, 5.0f)
|
||||
<< "Graphics buffer should have at least 5% transparent pixels. "
|
||||
<< "Got " << zero_percent << "%. This indicates the 3BPP→8BPP "
|
||||
<< "conversion may not be preserving transparency correctly.";
|
||||
}
|
||||
|
||||
// Test 2: Verify room graphics buffer after CopyRoomGraphicsToBuffer
|
||||
TEST_F(DungeonGraphicsTransparencyTest, RoomGraphicsBufferHasTransparentPixels) {
|
||||
// Create room 0 (Ganon's room - known to have walls)
|
||||
Room room(0x00, rom_.get());
|
||||
room.LoadRoomGraphics(0xFF);
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
// Access the room's current_gfx16_ buffer
|
||||
const auto& gfx16 = room.get_gfx_buffer();
|
||||
ASSERT_GT(gfx16.size(), 0);
|
||||
|
||||
// Count zeros in the room's graphics buffer
|
||||
int zero_count = 0;
|
||||
for (size_t i = 0; i < gfx16.size(); i++) {
|
||||
if (gfx16[i] == 0) zero_count++;
|
||||
}
|
||||
|
||||
float zero_percent = 100.0f * zero_count / gfx16.size();
|
||||
printf("[RoomGraphics] Room 0: Zeros: %d / %zu (%.1f%%)\n", zero_count,
|
||||
gfx16.size(), zero_percent);
|
||||
|
||||
// Log first 64 bytes (one tile's worth) to see actual values
|
||||
printf("[RoomGraphics] First 64 bytes:\n");
|
||||
for (int row = 0; row < 8; row++) {
|
||||
printf(" Row %d: ", row);
|
||||
for (int col = 0; col < 8; col++) {
|
||||
printf("%02X ", gfx16[row * 128 + col]); // 128 = sheet width stride
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Print value distribution
|
||||
int value_counts[8] = {0};
|
||||
int other_count = 0;
|
||||
for (size_t i = 0; i < gfx16.size(); i++) {
|
||||
if (gfx16[i] < 8) {
|
||||
value_counts[gfx16[i]]++;
|
||||
} else {
|
||||
other_count++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("[RoomGraphics] Value distribution:\n");
|
||||
for (int v = 0; v < 8; v++) {
|
||||
printf(" Value %d: %d (%.1f%%)\n", v, value_counts[v],
|
||||
100.0f * value_counts[v] / gfx16.size());
|
||||
}
|
||||
if (other_count > 0) {
|
||||
printf(" Values >7: %d (%.1f%%) - UNEXPECTED for 3BPP!\n", other_count,
|
||||
100.0f * other_count / gfx16.size());
|
||||
}
|
||||
|
||||
EXPECT_GT(zero_percent, 5.0f)
|
||||
<< "Room graphics buffer should have transparent pixels. "
|
||||
<< "Got " << zero_percent << "%. Check CopyRoomGraphicsToBuffer().";
|
||||
|
||||
// All values should be 0-7 for 3BPP graphics
|
||||
EXPECT_EQ(other_count, 0)
|
||||
<< "Found " << other_count << " pixels with values > 7. "
|
||||
<< "3BPP graphics should only have values 0-7.";
|
||||
}
|
||||
|
||||
// Test 3: Verify specific tile has expected mix of transparent/opaque
|
||||
TEST_F(DungeonGraphicsTransparencyTest, SpecificTileTransparency) {
|
||||
Room room(0x00, rom_.get());
|
||||
room.LoadRoomGraphics(0xFF);
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
const auto& gfx16 = room.get_gfx_buffer();
|
||||
|
||||
// Check tile 0 in block 0 (should be typical dungeon graphics)
|
||||
// Tile layout: 16 tiles per row, each tile 8x8 pixels
|
||||
// Row stride: 128 bytes (16 tiles * 8 pixels)
|
||||
int tile_id = 0;
|
||||
int tile_col = tile_id % 16;
|
||||
int tile_row = tile_id / 16;
|
||||
int tile_base_x = tile_col * 8;
|
||||
int tile_base_y = tile_row * 1024; // 8 rows * 128 bytes per row
|
||||
|
||||
int zeros_in_tile = 0;
|
||||
int total_in_tile = 64; // 8x8
|
||||
|
||||
printf("[Tile %d] Pixel values:\n", tile_id);
|
||||
for (int py = 0; py < 8; py++) {
|
||||
printf(" ");
|
||||
for (int px = 0; px < 8; px++) {
|
||||
int src_index = (py * 128) + px + tile_base_x + tile_base_y;
|
||||
uint8_t pixel = gfx16[src_index];
|
||||
printf("%d ", pixel);
|
||||
if (pixel == 0) zeros_in_tile++;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
float tile_zero_percent = 100.0f * zeros_in_tile / total_in_tile;
|
||||
printf("[Tile %d] Transparent pixels: %d / %d (%.1f%%)\n", tile_id,
|
||||
zeros_in_tile, total_in_tile, tile_zero_percent);
|
||||
|
||||
// Check a wall tile (ID 0x90 is commonly a wall tile)
|
||||
tile_id = 0x90;
|
||||
tile_col = tile_id % 16;
|
||||
tile_row = tile_id / 16;
|
||||
tile_base_x = tile_col * 8;
|
||||
tile_base_y = tile_row * 1024;
|
||||
|
||||
zeros_in_tile = 0;
|
||||
printf("\n[Tile 0x%02X] Pixel values:\n", tile_id);
|
||||
for (int py = 0; py < 8; py++) {
|
||||
printf(" ");
|
||||
for (int px = 0; px < 8; px++) {
|
||||
int src_index = (py * 128) + px + tile_base_x + tile_base_y;
|
||||
if (src_index < static_cast<int>(gfx16.size())) {
|
||||
uint8_t pixel = gfx16[src_index];
|
||||
printf("%d ", pixel);
|
||||
if (pixel == 0) zeros_in_tile++;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("[Tile 0x%02X] Transparent pixels: %d / %d\n", tile_id, zeros_in_tile,
|
||||
total_in_tile);
|
||||
}
|
||||
|
||||
// Test 4: Verify wall objects have tiles loaded
|
||||
TEST_F(DungeonGraphicsTransparencyTest, WallObjectsHaveTiles) {
|
||||
Room room(0x00, rom_.get());
|
||||
room.LoadRoomGraphics(0xFF);
|
||||
room.LoadObjects(); // Load objects from ROM!
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
// Get the room's objects
|
||||
auto& objects = room.GetTileObjects();
|
||||
printf("[Objects] Room 0 has %zu objects\n", objects.size());
|
||||
|
||||
// Count objects by type and check tiles
|
||||
int walls_0x00 = 0, walls_0x01_02 = 0, walls_0x60_plus = 0, other = 0;
|
||||
int missing_tiles = 0;
|
||||
|
||||
for (size_t i = 0; i < objects.size() && i < 20; i++) { // First 20 objects
|
||||
auto& obj = objects[i];
|
||||
obj.SetRom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
printf("[Object %zu] id=0x%03X pos=(%d,%d) size=%d tiles=%zu\n", i, obj.id_,
|
||||
obj.x(), obj.y(), obj.size(), obj.tiles().size());
|
||||
|
||||
if (obj.id_ == 0x00) {
|
||||
walls_0x00++;
|
||||
} else if (obj.id_ >= 0x01 && obj.id_ <= 0x02) {
|
||||
walls_0x01_02++;
|
||||
} else if (obj.id_ >= 0x60 && obj.id_ <= 0x6F) {
|
||||
walls_0x60_plus++;
|
||||
} else {
|
||||
other++;
|
||||
}
|
||||
|
||||
if (obj.tiles().empty()) {
|
||||
missing_tiles++;
|
||||
printf(" WARNING: Object 0x%03X has NO tiles!\n", obj.id_);
|
||||
} else {
|
||||
// Note: Some objects only need 1 tile (e.g., 0xC0) per ZScream's lookup table
|
||||
// This is valid behavior, not a bug
|
||||
// Print first 4 tile IDs
|
||||
printf(" Tile IDs: ");
|
||||
for (size_t t = 0; t < std::min(obj.tiles().size(), size_t(4)); t++) {
|
||||
printf("0x%03X ", obj.tiles()[t].id_);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n[Summary] walls_0x00=%d walls_0x01_02=%d walls_0x60+=%d other=%d\n",
|
||||
walls_0x00, walls_0x01_02, walls_0x60_plus, other);
|
||||
printf("[Summary] missing_tiles=%d\n", missing_tiles);
|
||||
|
||||
// Every object should have tiles loaded (tile count varies per object type)
|
||||
EXPECT_EQ(missing_tiles, 0)
|
||||
<< "Some objects have no tiles loaded - check EnsureTilesLoaded()";
|
||||
}
|
||||
|
||||
// Test 5: Verify objects are actually drawn to bitmaps
|
||||
TEST_F(DungeonGraphicsTransparencyTest, ObjectsDrawToBitmap) {
|
||||
Room room(0x00, rom_.get());
|
||||
room.LoadRoomGraphics(0xFF);
|
||||
room.LoadObjects();
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
// Get background buffers - they create their own bitmaps when needed
|
||||
auto& bg1 = room.bg1_buffer();
|
||||
auto& bg2 = room.bg2_buffer();
|
||||
|
||||
// DON'T manually create bitmaps - let DrawFloor/DrawBackground create them
|
||||
// with the correct size (512*512 = 262144 bytes)
|
||||
// The DrawFloor call initializes the bitmap properly
|
||||
bg1.DrawFloor(rom_->vector(), zelda3::tile_address, zelda3::tile_address_floor,
|
||||
room.floor1());
|
||||
bg2.DrawFloor(rom_->vector(), zelda3::tile_address, zelda3::tile_address_floor,
|
||||
room.floor2());
|
||||
|
||||
// Get objects
|
||||
auto& objects = room.GetTileObjects();
|
||||
printf("[DrawTest] Room 0 has %zu objects\n", objects.size());
|
||||
|
||||
// Create ObjectDrawer with room's graphics buffer
|
||||
ObjectDrawer drawer(rom_.get(), 0, room.get_gfx_buffer().data());
|
||||
|
||||
// Create a palette group (needed for draw)
|
||||
gfx::PaletteGroup palette_group;
|
||||
auto& dungeon_pal = game_data_.palette_groups.dungeon_main;
|
||||
if (!dungeon_pal.empty()) {
|
||||
palette_group.AddPalette(dungeon_pal[0]);
|
||||
}
|
||||
|
||||
// Draw objects
|
||||
auto status = drawer.DrawObjectList(objects, bg1, bg2, palette_group);
|
||||
if (!status.ok()) {
|
||||
printf("[DrawTest] DrawObjectList failed: %s\n",
|
||||
std::string(status.message()).c_str());
|
||||
}
|
||||
|
||||
// Check if any pixels were written to bg1
|
||||
int nonzero_pixels_bg1 = 0;
|
||||
int nonzero_pixels_bg2 = 0;
|
||||
size_t bg1_size = 512 * 512;
|
||||
size_t bg2_size = 512 * 512;
|
||||
auto bg1_data = bg1.bitmap().data();
|
||||
auto bg2_data = bg2.bitmap().data();
|
||||
|
||||
for (size_t i = 0; i < bg1_size; i++) {
|
||||
if (bg1_data[i] != 0) nonzero_pixels_bg1++;
|
||||
}
|
||||
for (size_t i = 0; i < bg2_size; i++) {
|
||||
if (bg2_data[i] != 0) nonzero_pixels_bg2++;
|
||||
}
|
||||
|
||||
printf("[DrawTest] BG1 non-zero pixels: %d / %zu (%.2f%%)\n",
|
||||
nonzero_pixels_bg1, bg1_size,
|
||||
100.0f * nonzero_pixels_bg1 / bg1_size);
|
||||
printf("[DrawTest] BG2 non-zero pixels: %d / %zu (%.2f%%)\n",
|
||||
nonzero_pixels_bg2, bg2_size,
|
||||
100.0f * nonzero_pixels_bg2 / bg2_size);
|
||||
|
||||
// We should have SOME pixels drawn
|
||||
EXPECT_GT(nonzero_pixels_bg1 + nonzero_pixels_bg2, 0)
|
||||
<< "No pixels were drawn to either background!";
|
||||
|
||||
// Print first few rows of bg1 to see the pattern
|
||||
printf("[DrawTest] BG1 first 16x4 pixels:\n");
|
||||
for (int y = 0; y < 4; y++) {
|
||||
printf(" Row %d: ", y);
|
||||
for (int x = 0; x < 16; x++) {
|
||||
printf("%02X ", bg1_data[y * 512 + x]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
@@ -35,13 +35,19 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
void SetUp() override {
|
||||
BoundRomTest::SetUp();
|
||||
|
||||
// Create drawer
|
||||
drawer_ = std::make_unique<zelda3::ObjectDrawer>(rom());
|
||||
// Create dummy graphics buffer
|
||||
gfx_buffer_.resize(0x10000, 1); // Fill with 1s so we see something
|
||||
drawer_ = std::make_unique<zelda3::ObjectDrawer>(rom(), 0, gfx_buffer_.data());
|
||||
|
||||
// Create background buffers
|
||||
bg1_ = std::make_unique<gfx::BackgroundBuffer>(512, 512);
|
||||
bg2_ = std::make_unique<gfx::BackgroundBuffer>(512, 512);
|
||||
|
||||
// Initialize bitmaps
|
||||
std::vector<uint8_t> empty_data(512 * 512, 0);
|
||||
bg1_->bitmap().Create(512, 512, 8, empty_data);
|
||||
bg2_->bitmap().Create(512, 512, 8, empty_data);
|
||||
|
||||
// Setup test palette
|
||||
palette_group_ = CreateTestPaletteGroup();
|
||||
}
|
||||
@@ -70,8 +76,17 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.SetRom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
// Force add a tile if none loaded (for testing without real ROM data)
|
||||
if (obj.tiles().empty()) {
|
||||
gfx::TileInfo tile;
|
||||
tile.id_ = 0;
|
||||
tile.palette_ = 0;
|
||||
obj.mutable_tiles().push_back(tile);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -79,6 +94,7 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
std::unique_ptr<gfx::BackgroundBuffer> bg1_;
|
||||
std::unique_ptr<gfx::BackgroundBuffer> bg2_;
|
||||
gfx::PaletteGroup palette_group_;
|
||||
std::vector<uint8_t> gfx_buffer_;
|
||||
};
|
||||
|
||||
// Test basic object drawing
|
||||
@@ -124,6 +140,12 @@ TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
|
||||
|
||||
gfx::BackgroundBuffer preview_bg(64, 64);
|
||||
gfx::BackgroundBuffer preview_bg2(64, 64);
|
||||
|
||||
// Initialize bitmaps
|
||||
std::vector<uint8_t> empty_data(64 * 64, 0);
|
||||
preview_bg.bitmap().Create(64, 64, 8, empty_data);
|
||||
preview_bg2.bitmap().Create(64, 64, 8, empty_data);
|
||||
|
||||
preview_bg.ClearBuffer();
|
||||
preview_bg2.ClearBuffer();
|
||||
|
||||
@@ -133,9 +155,9 @@ TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
|
||||
|
||||
auto& bitmap = preview_bg.bitmap();
|
||||
EXPECT_TRUE(bitmap.is_active());
|
||||
const auto data = bitmap.data();
|
||||
const auto& data = bitmap.vector();
|
||||
size_t non_zero = 0;
|
||||
for (size_t i = 0; i < bitmap.size(); i += 16) {
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
if (data[i] != 0) {
|
||||
non_zero++;
|
||||
}
|
||||
@@ -229,7 +251,7 @@ TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
|
||||
// Test error handling
|
||||
TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
|
||||
// Test with null ROM
|
||||
zelda3::ObjectDrawer null_drawer(nullptr);
|
||||
zelda3::ObjectDrawer null_drawer(nullptr, 0);
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5));
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
@@ -32,7 +32,7 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
BoundRomTest::SetUp();
|
||||
|
||||
// Create drawer
|
||||
drawer_ = std::make_unique<zelda3::ObjectDrawer>(rom());
|
||||
drawer_ = std::make_unique<zelda3::ObjectDrawer>(rom(), 0);
|
||||
|
||||
// Create background buffers
|
||||
bg1_ = std::make_unique<gfx::BackgroundBuffer>(512, 512);
|
||||
@@ -66,7 +66,7 @@ class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
|
||||
int layer = 0) {
|
||||
zelda3::RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom());
|
||||
obj.SetRom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
return obj;
|
||||
}
|
||||
|
||||
513
test/integration/zelda3/dungeon_object_rom_validation_test.cc
Normal file
513
test/integration/zelda3/dungeon_object_rom_validation_test.cc
Normal file
@@ -0,0 +1,513 @@
|
||||
// ROM Validation Tests for Dungeon Object System
|
||||
// These tests verify that our object parsing and rendering code correctly
|
||||
// interprets actual ALTTP ROM data.
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "rom/rom.h"
|
||||
#include "test_utils.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
#include "zelda3/game_data.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief ROM validation tests for dungeon object system
|
||||
*
|
||||
* These tests verify that our code correctly reads and interprets
|
||||
* actual data from the ALTTP ROM. They validate:
|
||||
* - Object tile pointer tables
|
||||
* - Tile count lookup tables
|
||||
* - Object decoding from room data
|
||||
* - Known room object layouts
|
||||
*/
|
||||
class DungeonObjectRomValidationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
rom_ = std::make_shared<Rom>();
|
||||
std::string rom_path = TestRomManager::GetTestRomPath();
|
||||
auto status = rom_->LoadFromFile(rom_path);
|
||||
if (!status.ok()) {
|
||||
GTEST_SKIP() << "ROM not available: " << rom_path;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Rom> rom_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Subtype 1 Object Tile Pointer Validation
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Subtype1TilePointerTable_ValidAddresses) {
|
||||
// The subtype 1 tile pointer table is at kRoomObjectSubtype1 (0x8000)
|
||||
// Each entry is 2 bytes pointing to tile data offset from 0x1B52
|
||||
|
||||
constexpr int kSubtype1TableBase = 0x8000;
|
||||
constexpr int kTileDataBase = 0x1B52;
|
||||
|
||||
// Verify first few entries have valid pointers
|
||||
for (int obj_id = 0; obj_id < 16; ++obj_id) {
|
||||
int table_addr = kSubtype1TableBase + (obj_id * 2);
|
||||
uint8_t lo = rom_->data()[table_addr];
|
||||
uint8_t hi = rom_->data()[table_addr + 1];
|
||||
uint16_t offset = lo | (hi << 8);
|
||||
|
||||
int tile_data_addr = kTileDataBase + offset;
|
||||
|
||||
// Tile data should be within ROM bounds and reasonable range
|
||||
EXPECT_LT(tile_data_addr, rom_->size())
|
||||
<< "Object 0x" << std::hex << obj_id << " tile pointer out of bounds";
|
||||
EXPECT_GT(tile_data_addr, 0x1B52)
|
||||
<< "Object 0x" << std::hex << obj_id << " tile pointer too low";
|
||||
EXPECT_LT(tile_data_addr, 0x10000)
|
||||
<< "Object 0x" << std::hex << obj_id << " tile pointer too high";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Subtype1TilePointerTable_Object0x00) {
|
||||
// Object 0x00 (floor) should have valid tile data pointer
|
||||
constexpr int kSubtype1TableBase = 0x8000;
|
||||
constexpr int kTileDataBase = 0x1B52;
|
||||
|
||||
uint8_t lo = rom_->data()[kSubtype1TableBase];
|
||||
uint8_t hi = rom_->data()[kSubtype1TableBase + 1];
|
||||
uint16_t offset = lo | (hi << 8);
|
||||
|
||||
// Object 0x00 offset should be within reasonable bounds
|
||||
// The ROM stores offset 984 (0x03D8) for Object 0x00
|
||||
EXPECT_GT(offset, 0) << "Object 0x00 should have non-zero tile pointer";
|
||||
EXPECT_LT(offset, 0x4000) << "Object 0x00 tile pointer should be in valid range";
|
||||
|
||||
// Read first tile at that address
|
||||
int tile_addr = kTileDataBase + offset;
|
||||
uint16_t first_tile = rom_->data()[tile_addr] | (rom_->data()[tile_addr + 1] << 8);
|
||||
|
||||
// Should have valid tile info (non-zero)
|
||||
EXPECT_NE(first_tile, 0) << "Object 0x00 should have valid tile data";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tile Count Lookup Table Validation
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, TileCountTable_KnownValues) {
|
||||
// Verify tile counts match kSubtype1TileLengths from room_object.h
|
||||
// These values are extracted from the game's ROM
|
||||
|
||||
zelda3::ObjectParser parser(rom_.get());
|
||||
|
||||
// Test known tile counts for common objects
|
||||
struct TileCountTest {
|
||||
int object_id;
|
||||
int expected_tiles;
|
||||
const char* description;
|
||||
};
|
||||
|
||||
// Expected values from kSubtype1TileLengths in object_parser.cc:
|
||||
// 0x00-0x0F: 4, 8, 8, 8, 8, 8, 8, 4, 4, 5, 5, 5, 5, 5, 5, 5
|
||||
// 0x10-0x1F: 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
|
||||
// 0x20-0x2F: 5, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6
|
||||
// 0x30-0x3F: 6, 1, 1, 16, 1, 1, 16, 16, 6, 8, 12, 12, 4, 8, 4, 3
|
||||
std::vector<TileCountTest> tests = {
|
||||
{0x00, 4, "Floor object"},
|
||||
{0x01, 8, "Wall rightwards 2x4"},
|
||||
{0x10, 5, "Diagonal wall acute"},
|
||||
{0x21, 9, "Edge rightwards 1x2+2"}, // kSubtype1TileLengths[0x21] = 9
|
||||
{0x22, 3, "Edge rightwards has edge"}, // 3 tiles
|
||||
{0x34, 1, "Solid 1x1 block"},
|
||||
{0x33, 16, "4x4 block"}, // kSubtype1TileLengths[0x33] = 16
|
||||
};
|
||||
|
||||
for (const auto& test : tests) {
|
||||
auto info = parser.GetObjectSubtype(test.object_id);
|
||||
ASSERT_TRUE(info.ok()) << "Failed to get subtype for 0x" << std::hex << test.object_id;
|
||||
|
||||
EXPECT_EQ(info->max_tile_count, test.expected_tiles)
|
||||
<< test.description << " (0x" << std::hex << test.object_id << ")"
|
||||
<< " expected " << std::dec << test.expected_tiles
|
||||
<< " tiles, got " << info->max_tile_count;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Object Decoding Validation
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, ObjectDecoding_Type1_TileDataLoads) {
|
||||
// Create a Type 1 object and verify its tiles load correctly
|
||||
zelda3::RoomObject obj(0x10, 5, 5, 0x12, 0); // Diagonal wall
|
||||
obj.SetRom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
EXPECT_FALSE(obj.tiles().empty())
|
||||
<< "Object 0x10 should have tiles loaded from ROM";
|
||||
|
||||
// Diagonal walls (0x10) should have 5 tiles
|
||||
EXPECT_EQ(obj.tiles().size(), 5)
|
||||
<< "Object 0x10 should have exactly 5 tiles";
|
||||
|
||||
// Verify tiles have valid IDs (non-zero, within range)
|
||||
for (size_t i = 0; i < obj.tiles().size(); ++i) {
|
||||
const auto& tile = obj.tiles()[i];
|
||||
EXPECT_LT(tile.id_, 1024)
|
||||
<< "Tile " << i << " ID should be within valid range";
|
||||
EXPECT_LT(tile.palette_, 8)
|
||||
<< "Tile " << i << " palette should be 0-7";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, ObjectDecoding_Type2_TileDataLoads) {
|
||||
// Create a Type 2 object (0x100-0x1FF range)
|
||||
zelda3::RoomObject obj(0x100, 5, 5, 0, 0); // First Type 2 object
|
||||
obj.SetRom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
// Type 2 objects should have some tiles
|
||||
EXPECT_FALSE(obj.tiles().empty())
|
||||
<< "Type 2 object 0x100 should have tiles loaded from ROM";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, ObjectDecoding_Type3_TileDataLoads) {
|
||||
// Create a Type 3 object (0xF80-0xFFF range)
|
||||
zelda3::RoomObject obj(0xF80, 5, 5, 0, 0); // First Type 3 object (Water Face)
|
||||
obj.SetRom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
// Type 3 objects should have some tiles
|
||||
EXPECT_FALSE(obj.tiles().empty())
|
||||
<< "Type 3 object 0xF80 should have tiles loaded from ROM";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Draw Routine Mapping Validation
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, DrawRoutineMapping_AllType1ObjectsHaveRoutines) {
|
||||
zelda3::ObjectDrawer drawer(rom_.get(), 0);
|
||||
|
||||
// All Type 1 objects (0x00-0xF7) should have valid draw routines
|
||||
for (int id = 0x00; id <= 0xF7; ++id) {
|
||||
int routine = drawer.GetDrawRoutineId(id);
|
||||
EXPECT_GE(routine, 0)
|
||||
<< "Object 0x" << std::hex << id << " should have a valid draw routine";
|
||||
EXPECT_LT(routine, 40)
|
||||
<< "Object 0x" << std::hex << id << " routine ID should be < 40";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, DrawRoutineMapping_Type3ObjectsHaveRoutines) {
|
||||
zelda3::ObjectDrawer drawer(rom_.get(), 0);
|
||||
|
||||
// Key Type 3 objects should have valid draw routines
|
||||
std::vector<int> type3_ids = {0xF80, 0xF81, 0xF82, // Water Face
|
||||
0xF83, 0xF84, // Somaria Line
|
||||
0xF97, 0xF98}; // Chests
|
||||
|
||||
for (int id : type3_ids) {
|
||||
int routine = drawer.GetDrawRoutineId(id);
|
||||
EXPECT_GE(routine, 0)
|
||||
<< "Type 3 object 0x" << std::hex << id << " should have a valid draw routine";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Room Data Validation (Known Rooms)
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Room0_LinksHouse_HasExpectedStructure) {
|
||||
// Room 0 is Link's House - verify we can load it
|
||||
zelda3::Room room = zelda3::LoadRoomFromRom(rom_.get(), 0);
|
||||
|
||||
// Link's House should have some objects
|
||||
const auto& objects = room.GetTileObjects();
|
||||
|
||||
// Room should have reasonable number of objects (not empty, not absurdly large)
|
||||
EXPECT_GT(objects.size(), 0u) << "Room 0 should have objects";
|
||||
EXPECT_LT(objects.size(), 200u) << "Room 0 should have reasonable object count";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Room1_LinksHouseBasement_LoadsCorrectly) {
|
||||
// Room 1 is typically basement/cellar
|
||||
zelda3::Room room = zelda3::LoadRoomFromRom(rom_.get(), 1);
|
||||
|
||||
// Should have loaded successfully
|
||||
EXPECT_GE(room.GetTileObjects().size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, HyruleCastleRoom_HasWallObjects) {
|
||||
// Room 0x50 is a Hyrule Castle room
|
||||
zelda3::Room room = zelda3::LoadRoomFromRom(rom_.get(), 0x50);
|
||||
|
||||
// Hyrule Castle rooms typically have wall objects
|
||||
bool has_wall_objects = false;
|
||||
for (const auto& obj : room.GetTileObjects()) {
|
||||
// Wall objects are typically in 0x00-0x20 range
|
||||
if (obj.id_ >= 0x00 && obj.id_ <= 0x30) {
|
||||
has_wall_objects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(has_wall_objects || room.GetTileObjects().empty())
|
||||
<< "Hyrule Castle room should have wall/floor objects";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Object Dimension Calculations with Real Data
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, ObjectDimensions_MatchesROMTileCount) {
|
||||
zelda3::ObjectDrawer drawer(rom_.get(), 0);
|
||||
zelda3::ObjectParser parser(rom_.get());
|
||||
|
||||
// Test objects and verify dimensions are consistent with tile counts
|
||||
std::vector<int> test_objects = {0x00, 0x01, 0x10, 0x21, 0x34};
|
||||
|
||||
for (int obj_id : test_objects) {
|
||||
zelda3::RoomObject obj(obj_id, 0, 0, 0, 0);
|
||||
obj.SetRom(rom_.get());
|
||||
|
||||
auto dims = drawer.CalculateObjectDimensions(obj);
|
||||
auto info = parser.GetObjectSubtype(obj_id);
|
||||
|
||||
// Dimensions should be positive
|
||||
EXPECT_GT(dims.first, 0)
|
||||
<< "Object 0x" << std::hex << obj_id << " width should be positive";
|
||||
EXPECT_GT(dims.second, 0)
|
||||
<< "Object 0x" << std::hex << obj_id << " height should be positive";
|
||||
|
||||
// Dimensions should be reasonable (not absurdly large)
|
||||
EXPECT_LE(dims.first, 512)
|
||||
<< "Object 0x" << std::hex << obj_id << " width should be <= 512";
|
||||
EXPECT_LE(dims.second, 512)
|
||||
<< "Object 0x" << std::hex << obj_id << " height should be <= 512";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Graphics Buffer Validation
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, ObjectDrawing_ProducesNonEmptyOutput) {
|
||||
// Create a graphics buffer (dummy for now since we don't have real room gfx)
|
||||
std::vector<uint8_t> gfx_buffer(0x10000, 1); // Fill with non-zero
|
||||
|
||||
zelda3::ObjectDrawer drawer(rom_.get(), 0, gfx_buffer.data());
|
||||
|
||||
// Create a simple object
|
||||
zelda3::RoomObject obj(0x10, 5, 5, 0x12, 0);
|
||||
obj.SetRom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
|
||||
// Create background buffer
|
||||
gfx::BackgroundBuffer bg1(512, 512);
|
||||
gfx::BackgroundBuffer bg2(512, 512);
|
||||
|
||||
std::vector<uint8_t> empty_data(512 * 512, 0);
|
||||
bg1.bitmap().Create(512, 512, 8, empty_data);
|
||||
bg2.bitmap().Create(512, 512, 8, empty_data);
|
||||
|
||||
// Create palette
|
||||
gfx::PaletteGroup palette_group;
|
||||
gfx::SnesPalette palette;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
palette.AddColor(gfx::SnesColor(i * 16, i * 16, i * 16));
|
||||
}
|
||||
palette_group.AddPalette(palette);
|
||||
|
||||
// Draw object
|
||||
auto status = drawer.DrawObject(obj, bg1, bg2, palette_group);
|
||||
EXPECT_TRUE(status.ok()) << "DrawObject failed: " << status.message();
|
||||
|
||||
// Check that some pixels were written (non-zero in bitmap)
|
||||
const auto& data = bg1.bitmap().vector();
|
||||
int non_zero_count = 0;
|
||||
for (uint8_t pixel : data) {
|
||||
if (pixel != 0) non_zero_count++;
|
||||
}
|
||||
|
||||
EXPECT_GT(non_zero_count, 0)
|
||||
<< "Drawing should produce some non-zero pixels";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GameData Graphics Buffer Validation (Critical for Editor)
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, GameData_GraphicsBufferPopulated) {
|
||||
// Load GameData - this is what the editor does on ROM load
|
||||
zelda3::GameData game_data;
|
||||
auto status = zelda3::LoadGameData(*rom_, game_data);
|
||||
ASSERT_TRUE(status.ok()) << "LoadGameData failed: " << status.message();
|
||||
|
||||
// Graphics buffer should be populated (223 sheets * 4096 bytes = 913408 bytes)
|
||||
EXPECT_GT(game_data.graphics_buffer.size(), 0u)
|
||||
<< "Graphics buffer should not be empty";
|
||||
EXPECT_GE(game_data.graphics_buffer.size(), 223u * 4096u)
|
||||
<< "Graphics buffer should have all 223 sheets";
|
||||
|
||||
// Count non-zero bytes in graphics buffer
|
||||
int non_zero_count = 0;
|
||||
for (uint8_t byte : game_data.graphics_buffer) {
|
||||
if (byte != 0 && byte != 0xFF) non_zero_count++;
|
||||
}
|
||||
|
||||
EXPECT_GT(non_zero_count, 100000)
|
||||
<< "Graphics buffer should have significant non-zero data, got "
|
||||
<< non_zero_count << " non-zero bytes";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, GameData_GfxBitmapsPopulated) {
|
||||
// Load GameData
|
||||
zelda3::GameData game_data;
|
||||
auto status = zelda3::LoadGameData(*rom_, game_data);
|
||||
ASSERT_TRUE(status.ok()) << "LoadGameData failed: " << status.message();
|
||||
|
||||
// Check that gfx_bitmaps are populated
|
||||
int populated_count = 0;
|
||||
int content_count = 0;
|
||||
for (size_t i = 0; i < 223; ++i) {
|
||||
auto& bitmap = game_data.gfx_bitmaps[i];
|
||||
if (bitmap.is_active() && bitmap.width() > 0 && bitmap.height() > 0) {
|
||||
populated_count++;
|
||||
|
||||
// Check entire bitmap for non-zero/non-0xFF data (not just first 100 bytes)
|
||||
// Some tiles are legitimately empty at the start
|
||||
bool has_content = false;
|
||||
for (size_t j = 0; j < bitmap.size(); ++j) {
|
||||
if (bitmap.data()[j] != 0 && bitmap.data()[j] != 0xFF) {
|
||||
has_content = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_content) {
|
||||
content_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have a reasonable number populated (not all 223 due to 2BPP sheets)
|
||||
EXPECT_GT(populated_count, 200)
|
||||
<< "Most of 223 gfx_bitmaps should be populated, got " << populated_count;
|
||||
|
||||
// Check that most populated sheets have actual content (some may be genuinely empty)
|
||||
EXPECT_GT(content_count, 180)
|
||||
<< "Most populated sheets should have content, got " << content_count
|
||||
<< " out of " << populated_count;
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Room_GraphicsBufferCopy) {
|
||||
// Load GameData first
|
||||
zelda3::GameData game_data;
|
||||
auto status = zelda3::LoadGameData(*rom_, game_data);
|
||||
ASSERT_TRUE(status.ok()) << "LoadGameData failed: " << status.message();
|
||||
|
||||
// Create a room with GameData
|
||||
zelda3::Room room(0, rom_.get(), &game_data);
|
||||
|
||||
// Load room graphics
|
||||
room.LoadRoomGraphics(room.blockset);
|
||||
|
||||
// Copy graphics to room buffer
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
// Get the current_gfx16 buffer
|
||||
auto& gfx16 = room.get_gfx_buffer();
|
||||
|
||||
// Count non-zero bytes
|
||||
int non_zero_count = 0;
|
||||
for (size_t i = 0; i < gfx16.size(); ++i) {
|
||||
if (gfx16[i] != 0) non_zero_count++;
|
||||
}
|
||||
|
||||
EXPECT_GT(non_zero_count, 1000)
|
||||
<< "Room's current_gfx16 buffer should have graphics data, got "
|
||||
<< non_zero_count << " non-zero bytes out of " << gfx16.size();
|
||||
|
||||
// Verify specific blocks are loaded
|
||||
auto blocks = room.blocks();
|
||||
EXPECT_EQ(blocks.size(), 16u) << "Room should have 16 graphics blocks";
|
||||
|
||||
for (size_t i = 0; i < blocks.size() && i < 4; ++i) {
|
||||
int block_start = i * 4096;
|
||||
int block_non_zero = 0;
|
||||
for (int j = 0; j < 4096; ++j) {
|
||||
if (gfx16[block_start + j] != 0) block_non_zero++;
|
||||
}
|
||||
|
||||
EXPECT_GT(block_non_zero, 100)
|
||||
<< "Block " << i << " (sheet " << blocks[i]
|
||||
<< ") should have graphics data, got " << block_non_zero
|
||||
<< " non-zero bytes";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRomValidationTest, Room_LayoutLoading) {
|
||||
// Load GameData first
|
||||
zelda3::GameData game_data;
|
||||
auto status = zelda3::LoadGameData(*rom_, game_data);
|
||||
ASSERT_TRUE(status.ok()) << "LoadGameData failed: " << status.message();
|
||||
|
||||
// Create a room with GameData
|
||||
zelda3::Room room(0, rom_.get(), &game_data);
|
||||
|
||||
// Load room graphics
|
||||
room.LoadRoomGraphics(room.blockset);
|
||||
room.CopyRoomGraphicsToBuffer();
|
||||
|
||||
// Check that layout_ is set up
|
||||
int layout_id = room.layout;
|
||||
std::cout << "Room 0 layout ID: " << layout_id << std::endl;
|
||||
|
||||
// Render room graphics (which calls LoadLayoutTilesToBuffer)
|
||||
room.RenderRoomGraphics();
|
||||
|
||||
// Check bg1_buffer bitmap has data
|
||||
auto& bg1_bmp = room.bg1_buffer().bitmap();
|
||||
auto& bg2_bmp = room.bg2_buffer().bitmap();
|
||||
|
||||
std::cout << "BG1 bitmap: active=" << bg1_bmp.is_active()
|
||||
<< " w=" << bg1_bmp.width()
|
||||
<< " h=" << bg1_bmp.height()
|
||||
<< " size=" << bg1_bmp.size() << std::endl;
|
||||
|
||||
std::cout << "BG2 bitmap: active=" << bg2_bmp.is_active()
|
||||
<< " w=" << bg2_bmp.width()
|
||||
<< " h=" << bg2_bmp.height()
|
||||
<< " size=" << bg2_bmp.size() << std::endl;
|
||||
|
||||
EXPECT_TRUE(bg1_bmp.is_active()) << "BG1 bitmap should be active";
|
||||
EXPECT_GT(bg1_bmp.width(), 0) << "BG1 bitmap should have width";
|
||||
EXPECT_GT(bg1_bmp.height(), 0) << "BG1 bitmap should have height";
|
||||
|
||||
// Count non-zero pixels in BG1
|
||||
if (bg1_bmp.is_active() && bg1_bmp.size() > 0) {
|
||||
int non_zero = 0;
|
||||
for (size_t i = 0; i < bg1_bmp.size(); ++i) {
|
||||
if (bg1_bmp.data()[i] != 0) non_zero++;
|
||||
}
|
||||
std::cout << "BG1 non-zero pixels: " << non_zero
|
||||
<< " / " << bg1_bmp.size()
|
||||
<< " (" << (100.0f * non_zero / bg1_bmp.size()) << "%)"
|
||||
<< std::endl;
|
||||
|
||||
EXPECT_GT(non_zero, 1000)
|
||||
<< "BG1 should have significant non-zero pixel data";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
176
test/integration/zelda3/dungeon_palette_test.cc
Normal file
176
test/integration/zelda3/dungeon_palette_test.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <vector>
|
||||
#include "app/gfx/core/bitmap.h"
|
||||
#include "app/gfx/types/snes_tile.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/game_data.h"
|
||||
#include "rom/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace test {
|
||||
|
||||
class DungeonPaletteTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Mock ROM is not strictly needed for DrawTileToBitmap if we pass tiledata
|
||||
// but ObjectDrawer constructor needs it.
|
||||
rom_ = std::make_unique<Rom>();
|
||||
game_data_ = std::make_unique<GameData>(rom_.get());
|
||||
drawer_ = std::make_unique<ObjectDrawer>(rom_.get(), 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<GameData> game_data_;
|
||||
std::unique_ptr<ObjectDrawer> drawer_;
|
||||
};
|
||||
|
||||
TEST_F(DungeonPaletteTest, PaletteOffsetIsCorrectFor8BPP) {
|
||||
// Create a bitmap
|
||||
gfx::Bitmap bitmap;
|
||||
bitmap.Create(8, 8, 8, std::vector<uint8_t>(64, 0));
|
||||
|
||||
// Create dummy tile data (128x128 pixels worth, but we only need enough for one tile)
|
||||
// 128 pixels wide = 16 tiles.
|
||||
// We will use tile ID 0.
|
||||
// Tile 0 is at (0,0) in sheet.
|
||||
// src_index = (0 + py) * 128 + (0 + px)
|
||||
// We need a buffer of size 128 * 8 at least.
|
||||
std::vector<uint8_t> tiledata(128 * 8, 0);
|
||||
|
||||
// Set some pixels in the tile data
|
||||
// Row 0, Col 0: Index 1
|
||||
tiledata[0] = 1;
|
||||
// Row 0, Col 1: Index 2
|
||||
tiledata[1] = 2;
|
||||
|
||||
// Create TileInfo with palette index 1
|
||||
gfx::TileInfo tile_info;
|
||||
tile_info.id_ = 0;
|
||||
tile_info.palette_ = 1; // Palette 1
|
||||
tile_info.horizontal_mirror_ = false;
|
||||
tile_info.vertical_mirror_ = false;
|
||||
tile_info.over_ = false;
|
||||
|
||||
// Draw
|
||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||
|
||||
// Check pixels
|
||||
// Dungeon tiles use 15-color sub-palettes (not 8 like overworld).
|
||||
// Formula: final_color = (pixel - 1) + (palette * 15)
|
||||
// For palette 1, offset is 15.
|
||||
// Pixel at (0,0) was 1. Result should be (1-1) + 15 = 15.
|
||||
// Pixel at (1,0) was 2. Result should be (2-1) + 15 = 16.
|
||||
|
||||
const auto& data = bitmap.vector();
|
||||
// Bitmap data is row-major.
|
||||
// (0,0) is index 0.
|
||||
EXPECT_EQ(data[0], 15); // (1-1) + 15 = 15
|
||||
EXPECT_EQ(data[1], 16); // (2-1) + 15 = 16
|
||||
|
||||
// Test with palette 0
|
||||
tile_info.palette_ = 0;
|
||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||
// Offset 0 * 15 = 0.
|
||||
// Pixel 1 -> (1-1) + 0 = 0
|
||||
// Pixel 2 -> (2-1) + 0 = 1
|
||||
EXPECT_EQ(data[0], 0);
|
||||
EXPECT_EQ(data[1], 1);
|
||||
|
||||
// Test with palette 7 (wraps to palette 1 due to 6 sub-palette limit)
|
||||
tile_info.palette_ = 7;
|
||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||
// Palette 7 wraps to 7 % 6 = 1, offset 1 * 15 = 15.
|
||||
EXPECT_EQ(data[0], 15); // (1-1) + 15 = 15
|
||||
EXPECT_EQ(data[1], 16); // (2-1) + 15 = 16
|
||||
}
|
||||
|
||||
TEST_F(DungeonPaletteTest, PaletteOffsetWorksWithConvertedData) {
|
||||
gfx::Bitmap bitmap;
|
||||
bitmap.Create(8, 8, 8, std::vector<uint8_t>(64, 0));
|
||||
|
||||
// Create 8BPP unpacked tile data (simulating converted buffer)
|
||||
// Layout: 128 bytes per tile row, 8 bytes per tile
|
||||
// For tile 0: base_x=0, base_y=0
|
||||
std::vector<uint8_t> tiledata(128 * 8, 0);
|
||||
|
||||
// Set pixel pair at row 0: pixel 0 = 3, pixel 1 = 5
|
||||
tiledata[0] = 3;
|
||||
tiledata[1] = 5;
|
||||
|
||||
gfx::TileInfo tile_info;
|
||||
tile_info.id_ = 0;
|
||||
tile_info.palette_ = 2; // Palette 2 → offset 30 (2 * 15)
|
||||
tile_info.horizontal_mirror_ = false;
|
||||
tile_info.vertical_mirror_ = false;
|
||||
tile_info.over_ = false;
|
||||
|
||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||
|
||||
const auto& data = bitmap.vector();
|
||||
// Dungeon tiles use 15-color sub-palettes.
|
||||
// Formula: final_color = (pixel - 1) + (palette * 15)
|
||||
// Pixel 3: (3-1) + 30 = 32
|
||||
// Pixel 5: (5-1) + 30 = 34
|
||||
EXPECT_EQ(data[0], 32);
|
||||
EXPECT_EQ(data[1], 34);
|
||||
}
|
||||
|
||||
TEST_F(DungeonPaletteTest, InspectActualPaletteColors) {
|
||||
// Load actual ROM file
|
||||
auto load_result = rom_->LoadFromFile("zelda3.sfc");
|
||||
if (!load_result.ok()) {
|
||||
GTEST_SKIP() << "ROM file not found, skipping";
|
||||
}
|
||||
|
||||
// Load game data (palettes, etc.)
|
||||
auto game_data_result = LoadGameData(*rom_, *game_data_);
|
||||
if (!game_data_result.ok()) {
|
||||
GTEST_SKIP() << "Failed to load game data: " << game_data_result.message();
|
||||
}
|
||||
|
||||
// Get dungeon main palette group
|
||||
const auto& dungeon_pal_group = game_data_->palette_groups.dungeon_main;
|
||||
|
||||
ASSERT_FALSE(dungeon_pal_group.empty()) << "Dungeon palette group is empty!";
|
||||
|
||||
// Get first palette (palette 0)
|
||||
const auto& palette0 = dungeon_pal_group[0];
|
||||
|
||||
printf("\n=== Dungeon Palette 0 - First 16 colors ===\n");
|
||||
for (size_t i = 0; i < std::min(size_t(16), palette0.size()); ++i) {
|
||||
const auto& color = palette0[i];
|
||||
auto rgb = color.rgb();
|
||||
printf("Color %02zu: R=%03d G=%03d B=%03d (0x%02X%02X%02X)\n",
|
||||
i,
|
||||
static_cast<int>(rgb.x),
|
||||
static_cast<int>(rgb.y),
|
||||
static_cast<int>(rgb.z),
|
||||
static_cast<int>(rgb.x),
|
||||
static_cast<int>(rgb.y),
|
||||
static_cast<int>(rgb.z));
|
||||
}
|
||||
|
||||
// Total palette size
|
||||
printf("\nTotal palette size: %zu colors\n", palette0.size());
|
||||
EXPECT_EQ(palette0.size(), 90) << "Expected 90 colors for dungeon palette";
|
||||
|
||||
// Colors 56-63 (palette 7 offset: 7*8=56)
|
||||
printf("\n=== Colors 56-63 (pal=7 range) ===\n");
|
||||
for (size_t i = 56; i < std::min(size_t(64), palette0.size()); ++i) {
|
||||
const auto& color = palette0[i];
|
||||
auto rgb = color.rgb();
|
||||
printf("Color %02zu: R=%03d G=%03d B=%03d (0x%02X%02X%02X)\n",
|
||||
i,
|
||||
static_cast<int>(rgb.x),
|
||||
static_cast<int>(rgb.y),
|
||||
static_cast<int>(rgb.z),
|
||||
static_cast<int>(rgb.x),
|
||||
static_cast<int>(rgb.y),
|
||||
static_cast<int>(rgb.z));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/render/background_buffer.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "zelda3/dungeon/object_drawer.h"
|
||||
#include "zelda3/dungeon/object_parser.h"
|
||||
@@ -77,7 +77,7 @@ class DungeonRenderingIntegrationTest : public ::testing::Test {
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
obj.SetRom(rom_.get());
|
||||
}
|
||||
|
||||
// Add objects to room (this would normally be done by LoadObjects)
|
||||
@@ -122,7 +122,7 @@ TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
EXPECT_GT(test_room.GetTileObjects().size(), 0);
|
||||
|
||||
// Test ObjectDrawer can render the room
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
auto status =
|
||||
@@ -135,7 +135,7 @@ TEST_F(DungeonRenderingIntegrationTest, FullRoomRenderingWorks) {
|
||||
// Test room rendering with different palette configurations
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithDifferentPalettes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
|
||||
// Test with different palette configurations
|
||||
std::vector<gfx::PaletteGroup> palette_groups;
|
||||
@@ -157,7 +157,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithDifferentPalettes) {
|
||||
// Test room rendering with objects on different layers
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Separate objects by layer
|
||||
@@ -190,7 +190,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMultipleLayers) {
|
||||
// Test room rendering with various object sizes
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithVariousObjectSizes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Group objects by size
|
||||
@@ -222,11 +222,11 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingPerformance) {
|
||||
int layer = i % 2; // Alternate layers
|
||||
|
||||
RoomObject obj(id, x, y, size, layer);
|
||||
obj.set_rom(rom_.get());
|
||||
obj.SetRom(rom_.get());
|
||||
large_room.AddObject(obj);
|
||||
}
|
||||
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Time the rendering operation
|
||||
@@ -252,7 +252,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingPerformance) {
|
||||
// Test room rendering with edge case coordinates
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithEdgeCaseCoordinates) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Add objects at edge coordinates
|
||||
@@ -266,7 +266,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithEdgeCaseCoordinates) {
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : edge_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
obj.SetRom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(edge_objects, test_room.bg1_buffer(),
|
||||
@@ -278,7 +278,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithEdgeCaseCoordinates) {
|
||||
// Test room rendering with mixed object types
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMixedObjectTypes) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Add various object types
|
||||
@@ -306,7 +306,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithMixedObjectTypes) {
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : mixed_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
obj.SetRom(rom_.get());
|
||||
}
|
||||
|
||||
auto status = drawer.DrawObjectList(mixed_objects, test_room.bg1_buffer(),
|
||||
@@ -334,7 +334,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingErrorHandling) {
|
||||
// Test room rendering with invalid object data
|
||||
TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithInvalidObjectData) {
|
||||
Room test_room = CreateTestRoom(0x00);
|
||||
ObjectDrawer drawer(rom_.get());
|
||||
ObjectDrawer drawer(rom_.get(), 0);
|
||||
auto palette_group = CreateTestPaletteGroup();
|
||||
|
||||
// Create objects with invalid data
|
||||
@@ -348,7 +348,7 @@ TEST_F(DungeonRenderingIntegrationTest, RoomRenderingWithInvalidObjectData) {
|
||||
|
||||
// Set ROM for all objects
|
||||
for (auto& obj : invalid_objects) {
|
||||
obj.set_rom(rom_.get());
|
||||
obj.SetRom(rom_.get());
|
||||
}
|
||||
|
||||
// Should handle gracefully
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
683
test/integration/zelda3/music_integration_test.cc
Normal file
683
test/integration/zelda3/music_integration_test.cc
Normal file
@@ -0,0 +1,683 @@
|
||||
// Integration tests for Music Editor with real ROM data
|
||||
// Tests song loading, parsing, and emulator audio stability
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/emu/emulator.h"
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/music/music_bank.h"
|
||||
#include "zelda3/music/song_data.h"
|
||||
#include "zelda3/music/spc_parser.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace test {
|
||||
|
||||
using namespace yaze::zelda3::music;
|
||||
|
||||
// =============================================================================
|
||||
// Test Fixture
|
||||
// =============================================================================
|
||||
|
||||
class MusicIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
rom_ = std::make_unique<Rom>();
|
||||
|
||||
// Check if ROM file exists
|
||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
||||
if (!rom_path) {
|
||||
rom_path = "zelda3.sfc";
|
||||
}
|
||||
|
||||
auto status = rom_->LoadFromFile(rom_path);
|
||||
if (!status.ok()) {
|
||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||
}
|
||||
|
||||
// Verify it's an ALTTP ROM
|
||||
if (rom_->title().find("ZELDA") == std::string::npos &&
|
||||
rom_->title().find("zelda") == std::string::npos) {
|
||||
GTEST_SKIP() << "ROM is not ALTTP: " << rom_->title();
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override { rom_.reset(); }
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
MusicBank music_bank_;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Song Loading Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, LoadVanillaSongsFromRom) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << "Failed to load music: " << status.message();
|
||||
|
||||
// Should load all 34 vanilla songs
|
||||
size_t song_count = music_bank_.GetSongCount();
|
||||
EXPECT_GE(song_count, 34) << "Expected at least 34 vanilla songs";
|
||||
|
||||
// Verify some known vanilla songs exist
|
||||
const MusicSong* title_song = music_bank_.GetSong(0); // Song ID 1 (index 0)
|
||||
ASSERT_NE(title_song, nullptr) << "Title song should exist";
|
||||
EXPECT_EQ(title_song->name, "Title");
|
||||
|
||||
const MusicSong* light_world = music_bank_.GetSong(1); // Song ID 2 (index 1)
|
||||
ASSERT_NE(light_world, nullptr) << "Light World song should exist";
|
||||
EXPECT_EQ(light_world->name, "Light World");
|
||||
|
||||
const MusicSong* dark_world = music_bank_.GetSong(8); // Song ID 9 (index 8)
|
||||
ASSERT_NE(dark_world, nullptr) << "Dark World song should exist";
|
||||
EXPECT_EQ(dark_world->name, "Dark World");
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, VerifySongStructure) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Check each vanilla song has valid structure
|
||||
for (int i = 0; i < 34; ++i) {
|
||||
SCOPED_TRACE("Song index: " + std::to_string(i));
|
||||
|
||||
const MusicSong* song = music_bank_.GetSong(i);
|
||||
ASSERT_NE(song, nullptr) << "Song " << i << " should exist";
|
||||
|
||||
// Song should have at least one segment
|
||||
EXPECT_GE(song->segments.size(), 1)
|
||||
<< "Song '" << song->name << "' should have at least one segment";
|
||||
|
||||
// Each segment should have 8 tracks
|
||||
for (size_t seg_idx = 0; seg_idx < song->segments.size(); ++seg_idx) {
|
||||
SCOPED_TRACE("Segment: " + std::to_string(seg_idx));
|
||||
|
||||
const auto& segment = song->segments[seg_idx];
|
||||
EXPECT_EQ(segment.tracks.size(), 8) << "Segment should have 8 tracks";
|
||||
|
||||
// At least one track should have content (not all empty)
|
||||
bool has_content = false;
|
||||
for (const auto& track : segment.tracks) {
|
||||
if (!track.is_empty && !track.events.empty()) {
|
||||
has_content = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Some songs may have empty segments for intro/loop purposes
|
||||
// but most should have content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, VerifyBankAssignment) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Songs 1-11 should be Overworld bank
|
||||
for (int i = 0; i < 11; ++i) {
|
||||
const MusicSong* song = music_bank_.GetSong(i);
|
||||
ASSERT_NE(song, nullptr);
|
||||
EXPECT_EQ(song->bank, static_cast<uint8_t>(MusicBank::Bank::Overworld))
|
||||
<< "Song " << i << " (" << song->name << ") should be Overworld bank";
|
||||
}
|
||||
|
||||
// Songs 12-31 should be Dungeon bank
|
||||
for (int i = 11; i < 31; ++i) {
|
||||
const MusicSong* song = music_bank_.GetSong(i);
|
||||
ASSERT_NE(song, nullptr);
|
||||
EXPECT_EQ(song->bank, static_cast<uint8_t>(MusicBank::Bank::Dungeon))
|
||||
<< "Song " << i << " (" << song->name << ") should be Dungeon bank";
|
||||
}
|
||||
|
||||
// Songs 32-34 should be Credits bank
|
||||
for (int i = 31; i < 34; ++i) {
|
||||
const MusicSong* song = music_bank_.GetSong(i);
|
||||
ASSERT_NE(song, nullptr);
|
||||
EXPECT_EQ(song->bank, static_cast<uint8_t>(MusicBank::Bank::Credits))
|
||||
<< "Song " << i << " (" << song->name << ") should be Credits bank";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, VerifyTrackEvents) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Check Light World song has valid events
|
||||
const MusicSong* light_world = music_bank_.GetSong(1);
|
||||
ASSERT_NE(light_world, nullptr);
|
||||
ASSERT_GE(light_world->segments.size(), 1);
|
||||
|
||||
int total_events = 0;
|
||||
int note_count = 0;
|
||||
int command_count = 0;
|
||||
|
||||
for (const auto& segment : light_world->segments) {
|
||||
for (const auto& track : segment.tracks) {
|
||||
if (track.is_empty)
|
||||
continue;
|
||||
|
||||
for (const auto& event : track.events) {
|
||||
total_events++;
|
||||
switch (event.type) {
|
||||
case TrackEvent::Type::Note:
|
||||
note_count++;
|
||||
// Verify note is in valid range
|
||||
EXPECT_TRUE(SpcParser::IsNotePitch(event.note.pitch) ||
|
||||
event.note.pitch == kNoteTie ||
|
||||
event.note.pitch == kNoteRest)
|
||||
<< "Invalid note pitch: 0x" << std::hex
|
||||
<< static_cast<int>(event.note.pitch);
|
||||
break;
|
||||
case TrackEvent::Type::Command:
|
||||
command_count++;
|
||||
// Verify command opcode is valid
|
||||
EXPECT_TRUE(SpcParser::IsCommand(event.command.opcode))
|
||||
<< "Invalid command opcode: 0x" << std::hex
|
||||
<< static_cast<int>(event.command.opcode);
|
||||
break;
|
||||
case TrackEvent::Type::End:
|
||||
// End marker is always valid
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light World should have significant content
|
||||
EXPECT_GT(total_events, 100) << "Light World should have many events";
|
||||
EXPECT_GT(note_count, 50) << "Light World should have many notes";
|
||||
EXPECT_GT(command_count, 10) << "Light World should have setup commands";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Space Calculation Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, CalculateVanillaBankUsage) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Check Overworld bank usage
|
||||
auto ow_space = music_bank_.CalculateSpaceUsage(MusicBank::Bank::Overworld);
|
||||
EXPECT_GT(ow_space.used_bytes, 0) << "Overworld bank should have content";
|
||||
EXPECT_LE(ow_space.used_bytes, ow_space.total_bytes)
|
||||
<< "Overworld usage should not exceed limit";
|
||||
EXPECT_LT(ow_space.usage_percent, 100.0f)
|
||||
<< "Overworld should not be over capacity";
|
||||
|
||||
// Check Dungeon bank usage
|
||||
auto dg_space = music_bank_.CalculateSpaceUsage(MusicBank::Bank::Dungeon);
|
||||
EXPECT_GT(dg_space.used_bytes, 0) << "Dungeon bank should have content";
|
||||
EXPECT_LE(dg_space.used_bytes, dg_space.total_bytes)
|
||||
<< "Dungeon usage should not exceed limit";
|
||||
|
||||
// Check Credits bank usage
|
||||
auto cr_space = music_bank_.CalculateSpaceUsage(MusicBank::Bank::Credits);
|
||||
EXPECT_GT(cr_space.used_bytes, 0) << "Credits bank should have content";
|
||||
EXPECT_LE(cr_space.used_bytes, cr_space.total_bytes)
|
||||
<< "Credits usage should not exceed limit";
|
||||
|
||||
// All songs should fit
|
||||
EXPECT_TRUE(music_bank_.AllSongsFit()) << "All vanilla songs should fit";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Emulator Integration Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, EmulatorInitializesWithRom) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
// Try to initialize the emulator
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
EXPECT_TRUE(initialized) << "Emulator should initialize with valid ROM";
|
||||
EXPECT_TRUE(emulator.is_snes_initialized())
|
||||
<< "SNES core should be initialized";
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, EmulatorCanRunFrames) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized) << "Emulator must initialize for this test";
|
||||
|
||||
emulator.set_running(true);
|
||||
|
||||
// Run a few frames without crashing
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
emulator.RunFrameOnly();
|
||||
}
|
||||
|
||||
// Should still be running
|
||||
EXPECT_TRUE(emulator.running());
|
||||
EXPECT_TRUE(emulator.is_snes_initialized());
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, EmulatorGeneratesAudioSamples) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized) << "Emulator must initialize for this test";
|
||||
|
||||
emulator.set_running(true);
|
||||
|
||||
// Run several frames to generate audio
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
emulator.RunFrameOnly();
|
||||
}
|
||||
|
||||
// Check that DSP is producing samples
|
||||
auto& dsp = emulator.snes().apu().dsp();
|
||||
const int16_t* sample_buffer = dsp.GetSampleBuffer();
|
||||
ASSERT_NE(sample_buffer, nullptr) << "DSP should have sample buffer";
|
||||
|
||||
// Check for non-zero audio samples (some sound should be playing)
|
||||
// At startup, there might be silence, but the buffer should exist
|
||||
uint16_t sample_offset = dsp.GetSampleOffset();
|
||||
EXPECT_GT(sample_offset, 0) << "DSP should have processed samples";
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, MusicTriggerWritesToRam) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized);
|
||||
|
||||
emulator.set_running(true);
|
||||
|
||||
// Run some frames to let the game initialize
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
emulator.RunFrameOnly();
|
||||
}
|
||||
|
||||
// Write a music ID to the music register
|
||||
uint8_t song_id = 0x02; // Light World
|
||||
emulator.snes().Write(0x7E012C, song_id);
|
||||
|
||||
// Verify the write
|
||||
auto read_result = emulator.snes().Read(0x7E012C);
|
||||
EXPECT_EQ(read_result, song_id)
|
||||
<< "Music register should hold the written value";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Round-Trip Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, ParseSerializeRoundTrip) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Test round-trip for Light World
|
||||
const MusicSong* original = music_bank_.GetSong(1);
|
||||
ASSERT_NE(original, nullptr);
|
||||
|
||||
// Serialize the song
|
||||
auto serialize_result = SpcSerializer::SerializeSong(*original, 0xD100);
|
||||
ASSERT_TRUE(serialize_result.ok()) << serialize_result.status().message();
|
||||
|
||||
auto& serialized = serialize_result.value();
|
||||
EXPECT_GT(serialized.data.size(), 0) << "Serialized data should not be empty";
|
||||
|
||||
// The serialized size should be reasonable
|
||||
EXPECT_LT(serialized.data.size(), 10000)
|
||||
<< "Serialized size should be reasonable";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Vanilla Song Name Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, AllVanillaSongsHaveNames) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
std::vector<std::string> expected_names = {"Title",
|
||||
"Light World",
|
||||
"Beginning",
|
||||
"Rabbit",
|
||||
"Forest",
|
||||
"Intro",
|
||||
"Town",
|
||||
"Warp",
|
||||
"Dark World",
|
||||
"Master Sword",
|
||||
"File Select",
|
||||
"Soldier",
|
||||
"Mountain",
|
||||
"Shop",
|
||||
"Fanfare",
|
||||
"Castle",
|
||||
"Palace (Pendant)",
|
||||
"Cave",
|
||||
"Clear",
|
||||
"Church",
|
||||
"Boss",
|
||||
"Dungeon (Crystal)",
|
||||
"Psychic",
|
||||
"Secret Way",
|
||||
"Rescue",
|
||||
"Crystal",
|
||||
"Fountain",
|
||||
"Pyramid",
|
||||
"Kill Agahnim",
|
||||
"Ganon Room",
|
||||
"Last Boss",
|
||||
"Credits 1",
|
||||
"Credits 2",
|
||||
"Credits 3"};
|
||||
|
||||
for (size_t i = 0;
|
||||
i < expected_names.size() && i < music_bank_.GetSongCount(); ++i) {
|
||||
const MusicSong* song = music_bank_.GetSong(i);
|
||||
ASSERT_NE(song, nullptr) << "Song " << i << " should exist";
|
||||
EXPECT_EQ(song->name, expected_names[i])
|
||||
<< "Song " << i << " name mismatch";
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Instrument/Sample Loading Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, InstrumentsLoaded) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Should have default instruments
|
||||
EXPECT_GE(music_bank_.GetInstrumentCount(), 16)
|
||||
<< "Should have at least 16 instruments";
|
||||
|
||||
// Check first instrument exists
|
||||
const MusicInstrument* inst = music_bank_.GetInstrument(0);
|
||||
ASSERT_NE(inst, nullptr);
|
||||
EXPECT_FALSE(inst->name.empty()) << "Instrument should have a name";
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, SamplesLoaded) {
|
||||
auto status = music_bank_.LoadFromRom(*rom_);
|
||||
ASSERT_TRUE(status.ok()) << status.message();
|
||||
|
||||
// Should have samples
|
||||
EXPECT_GE(music_bank_.GetSampleCount(), 16)
|
||||
<< "Should have at least 16 samples";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Direct SPC Upload Tests
|
||||
// =============================================================================
|
||||
|
||||
TEST_F(MusicIntegrationTest, DirectSpcUploadCommonBank) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized) << "Emulator must initialize for this test";
|
||||
|
||||
auto& apu = emulator.snes().apu();
|
||||
|
||||
// Reset APU to clean state
|
||||
apu.Reset();
|
||||
|
||||
// Upload common bank (Bank 0) from ROM offset 0xC8000
|
||||
// This contains: driver code, sample pointers, instruments, BRR samples
|
||||
constexpr uint32_t kCommonBankOffset = 0xC8000;
|
||||
const uint8_t* rom_data = rom_->data();
|
||||
const size_t rom_size = rom_->size();
|
||||
|
||||
ASSERT_GT(rom_size, kCommonBankOffset + 4)
|
||||
<< "ROM should have common bank data";
|
||||
|
||||
// Parse and upload blocks: [size:2][aram_addr:2][data:size]
|
||||
uint32_t offset = kCommonBankOffset;
|
||||
int block_count = 0;
|
||||
int total_bytes_uploaded = 0;
|
||||
|
||||
while (offset + 4 < rom_size) {
|
||||
uint16_t block_size = rom_data[offset] | (rom_data[offset + 1] << 8);
|
||||
uint16_t aram_addr = rom_data[offset + 2] | (rom_data[offset + 3] << 8);
|
||||
|
||||
if (block_size == 0 || block_size > 0x10000) break;
|
||||
if (offset + 4 + block_size > rom_size) break;
|
||||
|
||||
apu.WriteDma(aram_addr, &rom_data[offset + 4], block_size);
|
||||
|
||||
std::cout << "[DirectSpcUpload] Block " << block_count
|
||||
<< ": " << block_size << " bytes -> ARAM $"
|
||||
<< std::hex << aram_addr << std::dec << std::endl;
|
||||
|
||||
offset += 4 + block_size;
|
||||
block_count++;
|
||||
total_bytes_uploaded += block_size;
|
||||
}
|
||||
|
||||
EXPECT_GT(block_count, 0) << "Should upload at least one block";
|
||||
EXPECT_GT(total_bytes_uploaded, 1000) << "Should upload significant data";
|
||||
|
||||
std::cout << "[DirectSpcUpload] Uploaded " << block_count
|
||||
<< " blocks, " << total_bytes_uploaded << " bytes total" << std::endl;
|
||||
|
||||
// Verify some data was written to ARAM
|
||||
// SPC driver should be at $0800
|
||||
uint8_t driver_check = apu.ram[0x0800];
|
||||
EXPECT_NE(driver_check, 0) << "SPC driver area should have data";
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, DirectSpcUploadSongBank) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized);
|
||||
|
||||
auto& apu = emulator.snes().apu();
|
||||
apu.Reset();
|
||||
|
||||
// First upload common bank
|
||||
constexpr uint32_t kCommonBankOffset = 0xC8000;
|
||||
const uint8_t* rom_data = rom_->data();
|
||||
const size_t rom_size = rom_->size();
|
||||
|
||||
uint32_t offset = kCommonBankOffset;
|
||||
while (offset + 4 < rom_size) {
|
||||
uint16_t block_size = rom_data[offset] | (rom_data[offset + 1] << 8);
|
||||
uint16_t aram_addr = rom_data[offset + 2] | (rom_data[offset + 3] << 8);
|
||||
if (block_size == 0 || block_size > 0x10000) break;
|
||||
if (offset + 4 + block_size > rom_size) break;
|
||||
apu.WriteDma(aram_addr, &rom_data[offset + 4], block_size);
|
||||
offset += 4 + block_size;
|
||||
}
|
||||
|
||||
// Now upload overworld song bank (ROM offset 0xD1EF5)
|
||||
constexpr uint32_t kOverworldBankOffset = 0xD1EF5;
|
||||
ASSERT_GT(rom_size, kOverworldBankOffset + 4)
|
||||
<< "ROM should have overworld bank data";
|
||||
|
||||
offset = kOverworldBankOffset;
|
||||
int song_block_count = 0;
|
||||
|
||||
while (offset + 4 < rom_size) {
|
||||
uint16_t block_size = rom_data[offset] | (rom_data[offset + 1] << 8);
|
||||
uint16_t aram_addr = rom_data[offset + 2] | (rom_data[offset + 3] << 8);
|
||||
if (block_size == 0 || block_size > 0x10000) break;
|
||||
if (offset + 4 + block_size > rom_size) break;
|
||||
apu.WriteDma(aram_addr, &rom_data[offset + 4], block_size);
|
||||
|
||||
std::cout << "[DirectSpcUpload] Song block " << song_block_count
|
||||
<< ": " << block_size << " bytes -> ARAM $"
|
||||
<< std::hex << aram_addr << std::dec << std::endl;
|
||||
|
||||
offset += 4 + block_size;
|
||||
song_block_count++;
|
||||
}
|
||||
|
||||
EXPECT_GT(song_block_count, 0) << "Should upload song bank blocks";
|
||||
|
||||
// Song pointers should be at ARAM $D000
|
||||
uint16_t song_ptr_0 = apu.ram[0xD000] | (apu.ram[0xD001] << 8);
|
||||
std::cout << "[DirectSpcUpload] Song 0 pointer: $"
|
||||
<< std::hex << song_ptr_0 << std::dec << std::endl;
|
||||
|
||||
// Should have valid pointer (non-zero, within song data range)
|
||||
EXPECT_GT(song_ptr_0, 0xD000) << "Song pointer should be valid";
|
||||
EXPECT_LT(song_ptr_0, 0xFFFF) << "Song pointer should be within ARAM range";
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, DirectSpcPortCommunication) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized);
|
||||
|
||||
auto& apu = emulator.snes().apu();
|
||||
|
||||
// Test port communication
|
||||
// Write to in_ports (CPU -> SPC)
|
||||
apu.in_ports_[0] = 0x42;
|
||||
apu.in_ports_[1] = 0x00;
|
||||
|
||||
EXPECT_EQ(apu.in_ports_[0], 0x42) << "Port 0 should hold written value";
|
||||
EXPECT_EQ(apu.in_ports_[1], 0x00) << "Port 1 should hold written value";
|
||||
|
||||
std::cout << "[DirectSpcPort] Wrote song index 0x42 to port 0" << std::endl;
|
||||
|
||||
// Run some cycles to let SPC process
|
||||
emulator.set_running(true);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
emulator.RunFrameOnly();
|
||||
}
|
||||
|
||||
// Check out_ports (SPC -> CPU) for acknowledgment
|
||||
std::cout << "[DirectSpcPort] Out ports: "
|
||||
<< std::hex
|
||||
<< (int)apu.out_ports_[0] << " "
|
||||
<< (int)apu.out_ports_[1] << " "
|
||||
<< (int)apu.out_ports_[2] << " "
|
||||
<< (int)apu.out_ports_[3] << std::dec << std::endl;
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, DirectSpcAudioGeneration) {
|
||||
emu::Emulator emulator;
|
||||
|
||||
bool initialized = emulator.EnsureInitialized(rom_.get());
|
||||
ASSERT_TRUE(initialized);
|
||||
|
||||
auto& apu = emulator.snes().apu();
|
||||
apu.Reset();
|
||||
|
||||
// Upload common bank
|
||||
const uint8_t* rom_data = rom_->data();
|
||||
const size_t rom_size = rom_->size();
|
||||
|
||||
auto upload_bank = [&](uint32_t bank_offset) {
|
||||
uint32_t offset = bank_offset;
|
||||
while (offset + 4 < rom_size) {
|
||||
uint16_t block_size = rom_data[offset] | (rom_data[offset + 1] << 8);
|
||||
uint16_t aram_addr = rom_data[offset + 2] | (rom_data[offset + 3] << 8);
|
||||
if (block_size == 0 || block_size > 0x10000) break;
|
||||
if (offset + 4 + block_size > rom_size) break;
|
||||
apu.WriteDma(aram_addr, &rom_data[offset + 4], block_size);
|
||||
offset += 4 + block_size;
|
||||
}
|
||||
};
|
||||
|
||||
// Upload common bank (driver, samples, instruments)
|
||||
upload_bank(0xC8000);
|
||||
|
||||
// Upload overworld song bank
|
||||
upload_bank(0xD1EF5);
|
||||
|
||||
// Send play command for song 0 (Title)
|
||||
apu.in_ports_[0] = 0x00; // Song index 0
|
||||
apu.in_ports_[1] = 0x00; // Play command
|
||||
|
||||
std::cout << "[DirectSpcAudio] Starting playback test..." << std::endl;
|
||||
|
||||
emulator.set_running(true);
|
||||
|
||||
// Run frames and check for audio generation
|
||||
auto& dsp = apu.dsp();
|
||||
int frames_with_audio = 0;
|
||||
|
||||
for (int frame = 0; frame < 120; ++frame) {
|
||||
emulator.RunFrameOnly();
|
||||
|
||||
if (frame % 30 == 0) {
|
||||
const int16_t* samples = dsp.GetSampleBuffer();
|
||||
uint16_t sample_offset = dsp.GetSampleOffset();
|
||||
|
||||
// Check if any samples are non-zero
|
||||
bool has_audio = false;
|
||||
for (int i = 0; i < std::min(256, (int)sample_offset * 2); ++i) {
|
||||
if (samples[i] != 0) {
|
||||
has_audio = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_audio) {
|
||||
frames_with_audio++;
|
||||
}
|
||||
|
||||
std::cout << "[DirectSpcAudio] Frame " << frame
|
||||
<< ": sample_offset=" << sample_offset
|
||||
<< ", has_audio=" << (has_audio ? "yes" : "no") << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Check DSP channel states
|
||||
for (int ch = 0; ch < 8; ++ch) {
|
||||
const auto& channel = dsp.GetChannel(ch);
|
||||
std::cout << "[DirectSpcAudio] Ch" << ch
|
||||
<< ": vol=" << (int)channel.volumeL << "/" << (int)channel.volumeR
|
||||
<< ", pitch=$" << std::hex << channel.pitch << std::dec
|
||||
<< ", keyOn=" << channel.keyOn << std::endl;
|
||||
}
|
||||
|
||||
// We may or may not get audio depending on SPC driver state
|
||||
// But the test verifies the upload and port communication work
|
||||
std::cout << "[DirectSpcAudio] Frames with detected audio: "
|
||||
<< frames_with_audio << "/4 checks" << std::endl;
|
||||
}
|
||||
|
||||
TEST_F(MusicIntegrationTest, VerifyAllBankUploadOffsets) {
|
||||
// Verify the ROM has valid block headers at all bank offsets
|
||||
const uint8_t* rom_data = rom_->data();
|
||||
const size_t rom_size = rom_->size();
|
||||
|
||||
struct BankInfo {
|
||||
const char* name;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
BankInfo banks[] = {
|
||||
{"Common", 0xC8000},
|
||||
{"Overworld", 0xD1EF5},
|
||||
{"Dungeon", 0xD8000},
|
||||
{"Credits", 0xD5380}
|
||||
};
|
||||
|
||||
for (const auto& bank : banks) {
|
||||
SCOPED_TRACE(bank.name);
|
||||
ASSERT_GT(rom_size, bank.offset + 4)
|
||||
<< bank.name << " bank offset should be within ROM";
|
||||
|
||||
// Read first block header
|
||||
uint16_t block_size = rom_data[bank.offset] | (rom_data[bank.offset + 1] << 8);
|
||||
uint16_t aram_addr = rom_data[bank.offset + 2] | (rom_data[bank.offset + 3] << 8);
|
||||
|
||||
std::cout << "[BankVerify] " << bank.name
|
||||
<< " (0x" << std::hex << bank.offset << "): "
|
||||
<< "size=" << std::dec << block_size
|
||||
<< ", aram=$" << std::hex << aram_addr << std::dec << std::endl;
|
||||
|
||||
// Block should have valid size and address
|
||||
EXPECT_GT(block_size, 0) << bank.name << " should have non-zero first block";
|
||||
EXPECT_LT(block_size, 0x10000) << bank.name << " block size should be reasonable";
|
||||
EXPECT_GT(aram_addr, 0) << bank.name << " should have non-zero ARAM address";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "testing.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
@@ -108,15 +108,17 @@ class OverworldIntegrationTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
// Test Tile32 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile32ExpansionDetection) {
|
||||
TEST_F(OverworldIntegrationTest, DISABLED_Tile32ExpansionDetection) {
|
||||
mock_rom_data_[0x01772E] = 0x04;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
rom_->LoadFromData(mock_rom_data_); // Update ROM
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x01772E] = 0x05;
|
||||
rom_->LoadFromData(mock_rom_data_); // Update ROM
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
@@ -124,15 +126,17 @@ TEST_F(OverworldIntegrationTest, Tile32ExpansionDetection) {
|
||||
}
|
||||
|
||||
// Test Tile16 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
TEST_F(OverworldIntegrationTest, DISABLED_Tile16ExpansionDetection) {
|
||||
mock_rom_data_[0x017D28] = 0x0F;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
rom_->LoadFromData(mock_rom_data_); // Update ROM
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x017D28] = 0x10;
|
||||
rom_->LoadFromData(mock_rom_data_); // Update ROM
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
@@ -140,7 +144,7 @@ TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
}
|
||||
|
||||
// Test entrance loading matches ZScream coordinate calculation
|
||||
TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
TEST_F(OverworldIntegrationTest, DISABLED_EntranceCoordinateCalculation) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
@@ -192,7 +196,7 @@ TEST_F(OverworldIntegrationTest, ExitDataLoading) {
|
||||
}
|
||||
|
||||
// Test ASM version detection affects item loading
|
||||
TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
TEST_F(OverworldIntegrationTest, DISABLED_ASMVersionItemLoading) {
|
||||
// Test vanilla ASM (should limit to 0x80 maps)
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "rom/rom.h"
|
||||
#include "zelda3/overworld/overworld.h"
|
||||
#include "zelda3/overworld/overworld_map.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user