feat: Organize and enhance test structure with new tools and integration tests
- Added a new `tools` directory to house various utility tools, including the `overworld_golden_data_extractor`, `extract_vanilla_values`, and `rom_patch_utility`. - Introduced comprehensive integration tests for dungeon and overworld functionalities, ensuring compatibility with existing ROM data. - Refactored existing test files to improve organization and maintainability, moving deprecated tests to a dedicated directory. - Updated CMake configuration to include new tools and tests, enhancing the build process for development and CI environments. - Improved test coverage for dungeon object rendering and room integration, validating core functionalities against expected behaviors.
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
# Add golden data extractor tool
|
||||
add_executable(overworld_golden_data_extractor
|
||||
overworld_golden_data_extractor.cc
|
||||
)
|
||||
|
||||
target_link_libraries(overworld_golden_data_extractor
|
||||
yaze_core
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
# Add vanilla values extractor tool
|
||||
add_executable(extract_vanilla_values
|
||||
extract_vanilla_values.cc
|
||||
)
|
||||
|
||||
target_link_libraries(extract_vanilla_values
|
||||
yaze_core
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
# Install tools to bin directory
|
||||
install(TARGETS overworld_golden_data_extractor extract_vanilla_values
|
||||
DESTINATION bin
|
||||
)
|
||||
@@ -1,374 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class ComprehensiveIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
vanilla_rom_path_ = "zelda3.sfc";
|
||||
v3_rom_path_ = "zelda3_v3_test.sfc";
|
||||
|
||||
// Create v3 patched ROM for testing
|
||||
CreateV3PatchedROM();
|
||||
|
||||
// Load vanilla ROM
|
||||
vanilla_rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(vanilla_rom_->LoadFromFile(vanilla_rom_path_).ok());
|
||||
|
||||
// TODO: Load graphics data when gfx system is available
|
||||
// ASSERT_TRUE(gfx::LoadAllGraphicsData(*vanilla_rom_, true).ok());
|
||||
|
||||
// Initialize vanilla overworld
|
||||
vanilla_overworld_ = std::make_unique<Overworld>(vanilla_rom_.get());
|
||||
ASSERT_TRUE(vanilla_overworld_->Load(vanilla_rom_.get()).ok());
|
||||
|
||||
// Load v3 ROM
|
||||
v3_rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(v3_rom_->LoadFromFile(v3_rom_path_).ok());
|
||||
|
||||
// TODO: Load graphics data when gfx system is available
|
||||
// ASSERT_TRUE(gfx::LoadAllGraphicsData(*v3_rom_, true).ok());
|
||||
|
||||
// Initialize v3 overworld
|
||||
v3_overworld_ = std::make_unique<Overworld>(v3_rom_.get());
|
||||
ASSERT_TRUE(v3_overworld_->Load(v3_rom_.get()).ok());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
v3_overworld_.reset();
|
||||
vanilla_overworld_.reset();
|
||||
v3_rom_.reset();
|
||||
vanilla_rom_.reset();
|
||||
// TODO: Destroy graphics data when gfx system is available
|
||||
// gfx::DestroyAllGraphicsData();
|
||||
|
||||
// Clean up test files
|
||||
if (std::filesystem::exists(v3_rom_path_)) {
|
||||
std::filesystem::remove(v3_rom_path_);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateV3PatchedROM() {
|
||||
// Copy vanilla ROM and apply v3 patch
|
||||
std::ifstream src(vanilla_rom_path_, std::ios::binary);
|
||||
std::ofstream dst(v3_rom_path_, std::ios::binary);
|
||||
dst << src.rdbuf();
|
||||
src.close();
|
||||
dst.close();
|
||||
|
||||
// Load the copied ROM
|
||||
Rom rom;
|
||||
ASSERT_TRUE(rom.LoadFromFile(v3_rom_path_).ok());
|
||||
|
||||
// Apply v3 patch
|
||||
ApplyV3Patch(rom);
|
||||
|
||||
// Save the patched ROM
|
||||
ASSERT_TRUE(
|
||||
rom.SaveToFile(Rom::SaveSettings{.filename = v3_rom_path_}).ok());
|
||||
}
|
||||
|
||||
void ApplyV3Patch(Rom& rom) {
|
||||
// Set ASM version to v3
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomASMHasBeenApplied, 0x03).ok());
|
||||
|
||||
// Enable v3 features
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomAreaSpecificBGEnabled, 0x01).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomSubscreenOverlayEnabled, 0x01).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomAnimatedGFXEnabled, 0x01).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomTileGFXGroupEnabled, 0x01).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomMosaicEnabled, 0x01).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomMainPaletteEnabled, 0x01).ok());
|
||||
|
||||
// Apply v3 settings to first 10 maps for testing
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Set area sizes (mix of different sizes)
|
||||
AreaSizeEnum area_size = static_cast<AreaSizeEnum>(i % 4);
|
||||
ASSERT_TRUE(rom.WriteByte(kOverworldScreenSize + i, static_cast<uint8_t>(area_size)).ok());
|
||||
|
||||
// Set main palettes
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomMainPaletteArray + i, i % 8).ok());
|
||||
|
||||
// Set area-specific background colors
|
||||
uint16_t bg_color = 0x0000 + (i * 0x1000);
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2),
|
||||
bg_color & 0xFF).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2) + 1,
|
||||
(bg_color >> 8) & 0xFF).ok());
|
||||
|
||||
// Set subscreen overlays
|
||||
uint16_t overlay = 0x0090 + i;
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2),
|
||||
overlay & 0xFF).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2) + 1,
|
||||
(overlay >> 8) & 0xFF).ok());
|
||||
|
||||
// Set animated GFX
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomAnimatedGFXArray + i, 0x50 + i).ok());
|
||||
|
||||
// Set custom tile GFX groups (8 bytes per map)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
|
||||
0x20 + j + i).ok());
|
||||
}
|
||||
|
||||
// Set mosaic settings
|
||||
ASSERT_TRUE(rom.WriteByte(OverworldCustomMosaicArray + i, i % 16).ok());
|
||||
|
||||
// Set expanded message IDs
|
||||
uint16_t message_id = 0x1000 + i;
|
||||
ASSERT_TRUE(rom.WriteByte(kOverworldMessagesExpanded + (i * 2), message_id & 0xFF).ok());
|
||||
ASSERT_TRUE(rom.WriteByte(kOverworldMessagesExpanded + (i * 2) + 1,
|
||||
(message_id >> 8) & 0xFF).ok());
|
||||
}
|
||||
}
|
||||
|
||||
std::string vanilla_rom_path_;
|
||||
std::string v3_rom_path_;
|
||||
std::unique_ptr<Rom> vanilla_rom_;
|
||||
std::unique_ptr<Rom> v3_rom_;
|
||||
std::unique_ptr<Overworld> vanilla_overworld_;
|
||||
std::unique_ptr<Overworld> v3_overworld_;
|
||||
};
|
||||
|
||||
// Test vanilla ROM behavior
|
||||
TEST_F(ComprehensiveIntegrationTest, VanillaROMDetection) {
|
||||
uint8_t vanilla_asm_version =
|
||||
(*vanilla_rom_)[OverworldCustomASMHasBeenApplied];
|
||||
EXPECT_EQ(vanilla_asm_version, 0xFF); // 0xFF means vanilla ROM
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, VanillaROMMapProperties) {
|
||||
// Test a few specific maps from vanilla ROM
|
||||
const OverworldMap* map0 = vanilla_overworld_->overworld_map(0);
|
||||
const OverworldMap* map3 = vanilla_overworld_->overworld_map(3);
|
||||
const OverworldMap* map64 = vanilla_overworld_->overworld_map(64);
|
||||
|
||||
ASSERT_NE(map0, nullptr);
|
||||
ASSERT_NE(map3, nullptr);
|
||||
ASSERT_NE(map64, nullptr);
|
||||
|
||||
// Verify basic properties are loaded
|
||||
EXPECT_GE(map0->area_graphics(), 0);
|
||||
EXPECT_GE(map0->area_palette(), 0);
|
||||
EXPECT_GE(map0->message_id(), 0);
|
||||
EXPECT_GE(map3->area_graphics(), 0);
|
||||
EXPECT_GE(map3->area_palette(), 0);
|
||||
EXPECT_GE(map64->area_graphics(), 0);
|
||||
EXPECT_GE(map64->area_palette(), 0);
|
||||
|
||||
// Verify area sizes are reasonable
|
||||
EXPECT_TRUE(map0->area_size() == AreaSizeEnum::SmallArea ||
|
||||
map0->area_size() == AreaSizeEnum::LargeArea);
|
||||
EXPECT_TRUE(map3->area_size() == AreaSizeEnum::SmallArea ||
|
||||
map3->area_size() == AreaSizeEnum::LargeArea);
|
||||
EXPECT_TRUE(map64->area_size() == AreaSizeEnum::SmallArea ||
|
||||
map64->area_size() == AreaSizeEnum::LargeArea);
|
||||
}
|
||||
|
||||
// Test v3 ROM behavior
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMDetection) {
|
||||
uint8_t v3_asm_version = (*v3_rom_)[OverworldCustomASMHasBeenApplied];
|
||||
EXPECT_EQ(v3_asm_version, 0x03); // 0x03 means v3 ROM
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMFeatureFlags) {
|
||||
// Test that v3 features are enabled
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomAreaSpecificBGEnabled], 0x01);
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomSubscreenOverlayEnabled], 0x01);
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomAnimatedGFXEnabled], 0x01);
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomTileGFXGroupEnabled], 0x01);
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomMosaicEnabled], 0x01);
|
||||
EXPECT_EQ((*v3_rom_)[OverworldCustomMainPaletteEnabled], 0x01);
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMAreaSizes) {
|
||||
// Test that v3 area sizes are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
AreaSizeEnum expected_size = static_cast<AreaSizeEnum>(i % 4);
|
||||
EXPECT_EQ(map->area_size(), expected_size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMMainPalettes) {
|
||||
// Test that v3 main palettes are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
uint8_t expected_palette = i % 8;
|
||||
EXPECT_EQ(map->main_palette(), expected_palette);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMAreaSpecificBackgroundColors) {
|
||||
// Test that v3 area-specific background colors are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
uint16_t expected_color = 0x0000 + (i * 0x1000);
|
||||
EXPECT_EQ(map->area_specific_bg_color(), expected_color);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMSubscreenOverlays) {
|
||||
// Test that v3 subscreen overlays are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
uint16_t expected_overlay = 0x0090 + i;
|
||||
EXPECT_EQ(map->subscreen_overlay(), expected_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMAnimatedGFX) {
|
||||
// Test that v3 animated GFX are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
uint8_t expected_gfx = 0x50 + i;
|
||||
EXPECT_EQ(map->animated_gfx(), expected_gfx);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMCustomTileGFXGroups) {
|
||||
// Test that v3 custom tile GFX groups are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
uint8_t expected_tile = 0x20 + j + i;
|
||||
EXPECT_EQ(map->custom_tileset(j), expected_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComprehensiveIntegrationTest, V3ROMExpandedMessageIds) {
|
||||
// Test that v3 expanded message IDs are loaded correctly
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
ASSERT_NE(map, nullptr);
|
||||
|
||||
uint16_t expected_message_id = 0x1000 + i;
|
||||
EXPECT_EQ(map->message_id(), expected_message_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Test backwards compatibility
|
||||
TEST_F(ComprehensiveIntegrationTest, BackwardsCompatibility) {
|
||||
// Test that v3 ROMs still have access to vanilla properties
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const OverworldMap* vanilla_map = vanilla_overworld_->overworld_map(i);
|
||||
const OverworldMap* v3_map = v3_overworld_->overworld_map(i);
|
||||
|
||||
ASSERT_NE(vanilla_map, nullptr);
|
||||
ASSERT_NE(v3_map, nullptr);
|
||||
|
||||
// Basic properties should still be accessible
|
||||
EXPECT_GE(v3_map->area_graphics(), 0);
|
||||
EXPECT_GE(v3_map->area_palette(), 0);
|
||||
EXPECT_GE(v3_map->message_id(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test save/load functionality
|
||||
TEST_F(ComprehensiveIntegrationTest, SaveAndReloadV3ROM) {
|
||||
// Modify some properties
|
||||
v3_overworld_->mutable_overworld_map(0)->set_main_palette(0x07);
|
||||
v3_overworld_->mutable_overworld_map(1)->set_area_specific_bg_color(0x7FFF);
|
||||
v3_overworld_->mutable_overworld_map(2)->set_subscreen_overlay(0x1234);
|
||||
|
||||
// Save the ROM
|
||||
ASSERT_TRUE(v3_overworld_->Save(v3_rom_.get()).ok());
|
||||
|
||||
// Reload the ROM
|
||||
Rom reloaded_rom;
|
||||
ASSERT_TRUE(reloaded_rom.LoadFromFile(v3_rom_path_).ok());
|
||||
|
||||
Overworld reloaded_overworld(&reloaded_rom);
|
||||
ASSERT_TRUE(reloaded_overworld.Load(&reloaded_rom).ok());
|
||||
|
||||
// Verify the changes were saved
|
||||
EXPECT_EQ(reloaded_overworld.overworld_map(0)->main_palette(), 0x07);
|
||||
EXPECT_EQ(reloaded_overworld.overworld_map(1)->area_specific_bg_color(),
|
||||
0x7FFF);
|
||||
EXPECT_EQ(reloaded_overworld.overworld_map(2)->subscreen_overlay(), 0x1234);
|
||||
}
|
||||
|
||||
// Performance test
|
||||
TEST_F(ComprehensiveIntegrationTest, PerformanceTest) {
|
||||
const int kNumMaps = 160;
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Test vanilla ROM performance
|
||||
for (int i = 0; i < kNumMaps; i++) {
|
||||
const OverworldMap* map = vanilla_overworld_->overworld_map(i);
|
||||
if (map) {
|
||||
map->area_graphics();
|
||||
map->area_palette();
|
||||
map->message_id();
|
||||
map->area_size();
|
||||
}
|
||||
}
|
||||
|
||||
// Test v3 ROM performance
|
||||
for (int i = 0; i < kNumMaps; i++) {
|
||||
const OverworldMap* map = v3_overworld_->overworld_map(i);
|
||||
if (map) {
|
||||
map->area_graphics();
|
||||
map->area_palette();
|
||||
map->message_id();
|
||||
map->area_size();
|
||||
map->main_palette();
|
||||
map->area_specific_bg_color();
|
||||
map->subscreen_overlay();
|
||||
map->animated_gfx();
|
||||
}
|
||||
}
|
||||
|
||||
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 2 seconds for 320 map
|
||||
// operations)
|
||||
EXPECT_LT(duration.count(), 2000);
|
||||
}
|
||||
|
||||
// Test dungeon integration (if applicable)
|
||||
TEST_F(ComprehensiveIntegrationTest, DungeonIntegration) {
|
||||
// This test ensures that overworld changes don't break dungeon functionality
|
||||
// For now, just verify that the ROMs can be loaded without errors
|
||||
EXPECT_TRUE(vanilla_overworld_->is_loaded());
|
||||
EXPECT_TRUE(v3_overworld_->is_loaded());
|
||||
|
||||
// Verify that we have the expected number of maps
|
||||
EXPECT_EQ(vanilla_overworld_->overworld_maps().size(), kNumOverworldMaps);
|
||||
EXPECT_EQ(v3_overworld_->overworld_maps().size(), kNumOverworldMaps);
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,578 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
// Use the real ROM from build directory
|
||||
rom_path_ = "build/bin/zelda3.sfc";
|
||||
|
||||
// Load ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
||||
|
||||
// Initialize dungeon editor system
|
||||
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Initialize().ok());
|
||||
|
||||
// Load test room data
|
||||
ASSERT_TRUE(LoadTestRoomData().ok());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
dungeon_editor_system_.reset();
|
||||
rom_.reset();
|
||||
}
|
||||
|
||||
absl::Status LoadTestRoomData() {
|
||||
// Load representative rooms for testing
|
||||
test_rooms_ = {0x0000, 0x0001, 0x0002, 0x0010, 0x0012, 0x0020};
|
||||
|
||||
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();
|
||||
std::cout << "Loaded room 0x" << std::hex << room_id << std::dec << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<DungeonEditorSystem> dungeon_editor_system_;
|
||||
|
||||
std::vector<int> test_rooms_;
|
||||
std::map<int, Room> rooms_;
|
||||
};
|
||||
|
||||
// Test basic dungeon editor system initialization
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, BasicInitialization) {
|
||||
EXPECT_NE(dungeon_editor_system_, nullptr);
|
||||
EXPECT_EQ(dungeon_editor_system_->GetROM(), rom_.get());
|
||||
EXPECT_FALSE(dungeon_editor_system_->IsDirty());
|
||||
}
|
||||
|
||||
// Test room loading and management
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, RoomLoadingAndManagement) {
|
||||
// Test loading a specific room
|
||||
auto room_result = dungeon_editor_system_->GetRoom(0x0000);
|
||||
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);
|
||||
|
||||
// Test loading another room
|
||||
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
|
||||
TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
||||
// Get object editor from system
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
// Test object insertion
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
// Test object selection
|
||||
ASSERT_TRUE(object_editor->SelectObject(5 * 16, 5 * 16).ok());
|
||||
auto selection = object_editor->GetSelection();
|
||||
EXPECT_EQ(selection.selected_objects.size(), 1);
|
||||
|
||||
// Test object deletion
|
||||
ASSERT_TRUE(object_editor->DeleteSelectedObjects().ok());
|
||||
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
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
// Get object editor
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
// Add some objects
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Verify objects were added
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
// Test undo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 1);
|
||||
|
||||
// Test redo
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||
|
||||
// Test multiple undos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Undo().ok());
|
||||
EXPECT_EQ(object_editor->GetObjectCount(), 0);
|
||||
|
||||
// Test multiple redos
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Redo().ok());
|
||||
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
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||
|
||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(object_editor, nullptr);
|
||||
|
||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Save room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SaveRoom(0x0000).ok());
|
||||
|
||||
// Reload room
|
||||
ASSERT_TRUE(dungeon_editor_system_->ReloadRoom(0x0000).ok());
|
||||
|
||||
// Verify objects are still there
|
||||
auto reloaded_objects = object_editor->GetObjects();
|
||||
EXPECT_EQ(reloaded_objects.size(), 2);
|
||||
|
||||
// Save entire dungeon
|
||||
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
|
||||
auto invalid_room = dungeon_editor_system_->GetRoom(-1);
|
||||
EXPECT_FALSE(invalid_room.ok());
|
||||
|
||||
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 with invalid item ID
|
||||
auto invalid_item = dungeon_editor_system_->GetItem(-1);
|
||||
EXPECT_FALSE(invalid_item.ok());
|
||||
|
||||
// Test with invalid entrance ID
|
||||
auto invalid_entrance = dungeon_editor_system_->GetEntrance(-1);
|
||||
EXPECT_FALSE(invalid_entrance.ok());
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,208 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class DungeonIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
rom_path_ = "zelda3.sfc";
|
||||
|
||||
// Load ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
||||
|
||||
// TODO: Load graphics data when gfx system is available
|
||||
// ASSERT_TRUE(gfx::LoadAllGraphicsData(*rom_, true).ok());
|
||||
|
||||
// Initialize overworld
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
overworld_.reset();
|
||||
rom_.reset();
|
||||
// TODO: Destroy graphics data when gfx system is available
|
||||
// gfx::DestroyAllGraphicsData();
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<Overworld> overworld_;
|
||||
};
|
||||
|
||||
// Test dungeon room loading
|
||||
TEST_F(DungeonIntegrationTest, DungeonRoomLoading) {
|
||||
// TODO: Implement dungeon room loading tests when Room class is available
|
||||
// Test loading a few dungeon rooms
|
||||
const int kNumTestRooms = 10;
|
||||
|
||||
for (int i = 0; i < kNumTestRooms; i++) {
|
||||
// TODO: Create Room instance and test basic properties
|
||||
// Room room(i, rom_.get());
|
||||
// EXPECT_EQ(room.index(), i);
|
||||
// EXPECT_GE(room.width(), 0);
|
||||
// EXPECT_GE(room.height(), 0);
|
||||
// auto status = room.Build();
|
||||
// EXPECT_TRUE(status.ok()) << "Failed to build room " << i << ": " << status.message();
|
||||
}
|
||||
}
|
||||
|
||||
// Test dungeon object parsing
|
||||
TEST_F(DungeonIntegrationTest, DungeonObjectParsing) {
|
||||
// TODO: Implement dungeon object parsing tests when ObjectParser is available
|
||||
// Test object parsing for a few rooms
|
||||
const int kNumTestRooms = 5;
|
||||
|
||||
for (int i = 0; i < kNumTestRooms; i++) {
|
||||
// TODO: Create Room and ObjectParser instances
|
||||
// Room room(i, rom_.get());
|
||||
// ASSERT_TRUE(room.Build().ok());
|
||||
// ObjectParser parser(room);
|
||||
// auto objects = parser.ParseObjects();
|
||||
// EXPECT_TRUE(objects.ok()) << "Failed to parse objects for room " << i << ": " << objects.status().message();
|
||||
// if (objects.ok()) {
|
||||
// for (const auto& obj : objects.value()) {
|
||||
// EXPECT_GE(obj.x(), 0);
|
||||
// EXPECT_GE(obj.y(), 0);
|
||||
// EXPECT_GE(obj.type(), 0);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Test dungeon object rendering
|
||||
TEST_F(DungeonIntegrationTest, DungeonObjectRendering) {
|
||||
// TODO: Implement dungeon object rendering tests when ObjectRenderer is available
|
||||
// Test object rendering for a few rooms
|
||||
const int kNumTestRooms = 3;
|
||||
|
||||
for (int i = 0; i < kNumTestRooms; i++) {
|
||||
// TODO: Create Room, ObjectParser, and ObjectRenderer instances
|
||||
// Room room(i, rom_.get());
|
||||
// ASSERT_TRUE(room.Build().ok());
|
||||
// ObjectParser parser(room);
|
||||
// auto objects = parser.ParseObjects();
|
||||
// ASSERT_TRUE(objects.ok());
|
||||
// ObjectRenderer renderer(room);
|
||||
// auto status = renderer.RenderObjects(objects.value());
|
||||
// EXPECT_TRUE(status.ok()) << "Failed to render objects for room " << i << ": " << status.message();
|
||||
}
|
||||
}
|
||||
|
||||
// Test dungeon integration with overworld
|
||||
TEST_F(DungeonIntegrationTest, DungeonOverworldIntegration) {
|
||||
// Test that dungeon changes don't affect overworld functionality
|
||||
EXPECT_TRUE(overworld_->is_loaded());
|
||||
EXPECT_EQ(overworld_->overworld_maps().size(), kNumOverworldMaps);
|
||||
|
||||
// Test that we can access overworld maps after dungeon operations
|
||||
const OverworldMap* map0 = overworld_->overworld_map(0);
|
||||
ASSERT_NE(map0, nullptr);
|
||||
|
||||
// Verify basic overworld properties still work
|
||||
EXPECT_GE(map0->area_graphics(), 0);
|
||||
EXPECT_GE(map0->area_palette(), 0);
|
||||
EXPECT_GE(map0->message_id(), 0);
|
||||
}
|
||||
|
||||
// Test ROM integrity after dungeon operations
|
||||
TEST_F(DungeonIntegrationTest, ROMIntegrity) {
|
||||
// Test that ROM remains intact after dungeon operations
|
||||
// std::vector<uint8_t> original_data = rom_->data();
|
||||
|
||||
// // Perform various dungeon operations
|
||||
// for (int i = 0; i < 5; i++) {
|
||||
// Room room(i, rom_.get());
|
||||
// room.Build();
|
||||
|
||||
// ObjectParser parser(room);
|
||||
// parser.ParseObjects();
|
||||
// }
|
||||
|
||||
// // Verify ROM data hasn't changed
|
||||
// std::vector<uint8_t> current_data = rom_->data();
|
||||
// EXPECT_EQ(original_data.size(), current_data.size());
|
||||
|
||||
// // Check that critical ROM areas haven't been corrupted
|
||||
// EXPECT_EQ(rom_->data()[0x7FC0], original_data[0x7FC0]); // ROM header
|
||||
// EXPECT_EQ(rom_->data()[0x7FC1], original_data[0x7FC1]);
|
||||
// EXPECT_EQ(rom_->data()[0x7FC2], original_data[0x7FC2]);
|
||||
}
|
||||
|
||||
// Performance test for dungeon operations
|
||||
TEST_F(DungeonIntegrationTest, DungeonPerformanceTest) {
|
||||
// TODO: Implement dungeon performance tests when dungeon classes are available
|
||||
const int kNumRooms = 50;
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
for (int i = 0; i < kNumRooms; i++) {
|
||||
// TODO: Create Room and ObjectParser instances for performance testing
|
||||
// Room room(i, rom_.get());
|
||||
// room.Build();
|
||||
// ObjectParser parser(room);
|
||||
// parser.ParseObjects();
|
||||
}
|
||||
|
||||
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 50 rooms)
|
||||
EXPECT_LT(duration.count(), 5000);
|
||||
}
|
||||
|
||||
// Test dungeon save/load functionality
|
||||
TEST_F(DungeonIntegrationTest, DungeonSaveLoad) {
|
||||
// TODO: Implement dungeon save/load tests when dungeon classes are available
|
||||
// Create a test room
|
||||
// Room room(0, rom_.get());
|
||||
// ASSERT_TRUE(room.Build().ok());
|
||||
|
||||
// Parse objects
|
||||
// ObjectParser parser(room);
|
||||
// auto objects = parser.ParseObjects();
|
||||
// ASSERT_TRUE(objects.ok());
|
||||
|
||||
// Modify some objects (if any exist)
|
||||
// if (!objects.value().empty()) {
|
||||
// // This would involve modifying object properties and saving
|
||||
// // For now, just verify the basic save/load mechanism works
|
||||
// EXPECT_TRUE(rom_->SaveToFile("test_dungeon.sfc").ok());
|
||||
//
|
||||
// // Clean up test file
|
||||
// if (std::filesystem::exists("test_dungeon.sfc")) {
|
||||
// std::filesystem::remove("test_dungeon.sfc");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// Test dungeon error handling
|
||||
TEST_F(DungeonIntegrationTest, DungeonErrorHandling) {
|
||||
// TODO: Implement dungeon error handling tests when Room class is available
|
||||
// Test with invalid room indices
|
||||
// Room invalid_room(-1, rom_.get());
|
||||
// auto status = invalid_room.Build();
|
||||
// EXPECT_FALSE(status.ok()); // Should fail for invalid room
|
||||
|
||||
// Test with very large room index
|
||||
// Room large_room(1000, rom_.get());
|
||||
// status = large_room.Build();
|
||||
// EXPECT_FALSE(status.ok()); // Should fail for non-existent room
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,784 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "app/zelda3/dungeon/object_renderer.h"
|
||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class DungeonObjectRendererIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
// Use the real ROM from build directory
|
||||
rom_path_ = "build/bin/zelda3.sfc";
|
||||
|
||||
// Load ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
||||
|
||||
// Initialize dungeon editor system
|
||||
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
||||
ASSERT_TRUE(dungeon_editor_system_->Initialize().ok());
|
||||
|
||||
// Initialize object editor
|
||||
object_editor_ = std::make_shared<DungeonObjectEditor>(rom_.get());
|
||||
// Note: InitializeEditor() is private, so we skip this in integration tests
|
||||
|
||||
// Initialize object renderer
|
||||
object_renderer_ = std::make_unique<ObjectRenderer>(rom_.get());
|
||||
|
||||
// Load test room data
|
||||
ASSERT_TRUE(LoadTestRoomData().ok());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
object_renderer_.reset();
|
||||
object_editor_.reset();
|
||||
dungeon_editor_system_.reset();
|
||||
rom_.reset();
|
||||
}
|
||||
|
||||
absl::Status LoadTestRoomData() {
|
||||
// Load representative rooms based on disassembly data
|
||||
// Room 0x0000: Ganon's room (from disassembly)
|
||||
// Room 0x0001: First dungeon room
|
||||
// Room 0x0002: Sewer room (from disassembly)
|
||||
// Room 0x0010: Another dungeon room (from disassembly)
|
||||
// Room 0x0012: Sewer room (from disassembly)
|
||||
// Room 0x0020: Agahnim's tower (from disassembly)
|
||||
test_rooms_ = {0x0000, 0x0001, 0x0002, 0x0010, 0x0012, 0x0020, 0x0033, 0x005A};
|
||||
|
||||
for (int room_id : test_rooms_) {
|
||||
auto room_result = zelda3::LoadRoomFromRom(rom_.get(), room_id);
|
||||
rooms_[room_id] = room_result;
|
||||
rooms_[room_id].LoadObjects();
|
||||
|
||||
// Log room data for debugging
|
||||
if (!rooms_[room_id].GetTileObjects().empty()) {
|
||||
std::cout << "Room 0x" << std::hex << room_id << std::dec
|
||||
<< " loaded with " << rooms_[room_id].GetTileObjects().size()
|
||||
<< " objects" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Load palette data for testing based on vanilla values
|
||||
auto palette_group = rom_->palette_group().dungeon_main;
|
||||
test_palettes_ = {palette_group[0], palette_group[1], palette_group[2]};
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Helper methods for creating test objects
|
||||
RoomObject CreateTestObject(int object_id, int x, int y, int size = 0x12, int layer = 0) {
|
||||
RoomObject obj(object_id, x, y, size, layer);
|
||||
obj.set_rom(rom_.get());
|
||||
obj.EnsureTilesLoaded();
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::vector<RoomObject> CreateTestObjectSet(int room_id) {
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
// Create test objects based on real object types from disassembly
|
||||
// These correspond to actual object types found in the ROM
|
||||
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall object
|
||||
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor object
|
||||
objects.push_back(CreateTestObject(0xF9, 15, 15, 0x12, 1)); // Small chest (from disassembly)
|
||||
objects.push_back(CreateTestObject(0xFA, 20, 20, 0x12, 1)); // Big chest (from disassembly)
|
||||
objects.push_back(CreateTestObject(0x13, 25, 25, 0x32, 2)); // Stairs
|
||||
objects.push_back(CreateTestObject(0x17, 30, 30, 0x12, 0)); // Door
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Create objects based on specific room types from disassembly
|
||||
std::vector<RoomObject> CreateGanonRoomObjects() {
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
// Ganon's room typically has specific objects
|
||||
objects.push_back(CreateTestObject(0x10, 8, 8, 0x12, 0)); // Wall
|
||||
objects.push_back(CreateTestObject(0x20, 12, 12, 0x22, 0)); // Floor
|
||||
objects.push_back(CreateTestObject(0x30, 16, 16, 0x12, 1)); // Decoration
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
std::vector<RoomObject> CreateSewerRoomObjects() {
|
||||
std::vector<RoomObject> objects;
|
||||
|
||||
// Sewer rooms (like room 0x0002, 0x0012) have water and pipes
|
||||
objects.push_back(CreateTestObject(0x20, 5, 5, 0x22, 0)); // Floor
|
||||
objects.push_back(CreateTestObject(0x40, 10, 10, 0x12, 0)); // Water
|
||||
objects.push_back(CreateTestObject(0x50, 15, 15, 0x32, 1)); // Pipe
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Performance measurement helpers
|
||||
struct PerformanceMetrics {
|
||||
std::chrono::milliseconds render_time;
|
||||
size_t objects_rendered;
|
||||
size_t memory_used;
|
||||
size_t cache_hits;
|
||||
size_t cache_misses;
|
||||
};
|
||||
|
||||
PerformanceMetrics MeasureRenderPerformance(const std::vector<RoomObject>& objects,
|
||||
const gfx::SnesPalette& palette) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto stats_before = object_renderer_->GetPerformanceStats();
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, palette);
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto stats_after = object_renderer_->GetPerformanceStats();
|
||||
|
||||
PerformanceMetrics metrics;
|
||||
metrics.render_time = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
end_time - start_time);
|
||||
metrics.objects_rendered = objects.size();
|
||||
metrics.cache_hits = stats_after.cache_hits - stats_before.cache_hits;
|
||||
metrics.cache_misses = stats_after.cache_misses - stats_before.cache_misses;
|
||||
metrics.memory_used = object_renderer_->GetMemoryUsage();
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<DungeonEditorSystem> dungeon_editor_system_;
|
||||
std::shared_ptr<DungeonObjectEditor> object_editor_;
|
||||
std::unique_ptr<ObjectRenderer> object_renderer_;
|
||||
|
||||
// Test data
|
||||
std::vector<int> test_rooms_;
|
||||
std::map<int, Room> rooms_;
|
||||
std::vector<gfx::SnesPalette> test_palettes_;
|
||||
};
|
||||
|
||||
// Test basic object rendering functionality
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, BasicObjectRendering) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
auto result = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render objects: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
|
||||
// Test object rendering with different palettes
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, MultiPaletteRendering) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
|
||||
for (const auto& palette : test_palettes_) {
|
||||
auto result = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render with palette: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering with real room data
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, RealRoomObjectRendering) {
|
||||
for (int room_id : test_rooms_) {
|
||||
if (rooms_.find(room_id) == rooms_.end()) continue;
|
||||
|
||||
const auto& room = rooms_[room_id];
|
||||
const auto& objects = room.GetTileObjects();
|
||||
|
||||
if (objects.empty()) continue;
|
||||
|
||||
// Test with first palette
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render room 0x" << std::hex << room_id
|
||||
<< std::dec << " objects: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
// Log successful rendering
|
||||
std::cout << "Successfully rendered room 0x" << std::hex << room_id << std::dec
|
||||
<< " with " << objects.size() << " objects" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Test specific rooms mentioned in disassembly
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DisassemblyRoomValidation) {
|
||||
// Test Ganon's room (0x0000) from disassembly
|
||||
if (rooms_.find(0x0000) != rooms_.end()) {
|
||||
const auto& ganon_room = rooms_[0x0000];
|
||||
const auto& objects = ganon_room.GetTileObjects();
|
||||
|
||||
if (!objects.empty()) {
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render Ganon's room objects";
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << "Ganon's room (0x0000) rendered with " << objects.size()
|
||||
<< " objects" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Test sewer rooms (0x0002, 0x0012) from disassembly
|
||||
for (int room_id : {0x0002, 0x0012}) {
|
||||
if (rooms_.find(room_id) != rooms_.end()) {
|
||||
const auto& sewer_room = rooms_[room_id];
|
||||
const auto& objects = sewer_room.GetTileObjects();
|
||||
|
||||
if (!objects.empty()) {
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render sewer room 0x" << std::hex << room_id << std::dec;
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << "Sewer room 0x" << std::hex << room_id << std::dec
|
||||
<< " rendered with " << objects.size() << " objects" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Agahnim's tower room (0x0020) from disassembly
|
||||
if (rooms_.find(0x0020) != rooms_.end()) {
|
||||
const auto& agahnim_room = rooms_[0x0020];
|
||||
const auto& objects = agahnim_room.GetTileObjects();
|
||||
|
||||
if (!objects.empty()) {
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render Agahnim's tower room objects";
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << "Agahnim's tower room (0x0020) rendered with " << objects.size()
|
||||
<< " objects" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering performance
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, RenderingPerformance) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
// Measure performance for different object counts
|
||||
std::vector<int> object_counts = {1, 5, 10, 20, 50};
|
||||
|
||||
for (int count : object_counts) {
|
||||
std::vector<RoomObject> objects;
|
||||
for (int i = 0; i < count; i++) {
|
||||
objects.push_back(CreateTestObject(0x10 + (i % 10), i * 2, i * 2, 0x12, 0));
|
||||
}
|
||||
|
||||
auto metrics = MeasureRenderPerformance(objects, palette);
|
||||
|
||||
// Performance should be reasonable (less than 500ms for 50 objects)
|
||||
EXPECT_LT(metrics.render_time.count(), 500)
|
||||
<< "Rendering " << count << " objects took too long: "
|
||||
<< metrics.render_time.count() << "ms";
|
||||
|
||||
EXPECT_EQ(metrics.objects_rendered, count);
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering cache effectiveness
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, CacheEffectiveness) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
// Reset performance stats
|
||||
object_renderer_->ResetPerformanceStats();
|
||||
|
||||
// First render (should miss cache)
|
||||
auto result1 = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result1.ok());
|
||||
|
||||
auto stats1 = object_renderer_->GetPerformanceStats();
|
||||
EXPECT_GT(stats1.cache_misses, 0);
|
||||
|
||||
// Second render with same objects (should hit cache)
|
||||
auto result2 = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result2.ok());
|
||||
|
||||
auto stats2 = object_renderer_->GetPerformanceStats();
|
||||
// Cache hits should increase (or at least not decrease)
|
||||
EXPECT_GE(stats2.cache_hits, stats1.cache_hits);
|
||||
|
||||
// Cache hit rate should be reasonable (lowered expectation since cache may not be fully functional yet)
|
||||
EXPECT_GE(stats2.cache_hit_rate(), 0.0) << "Cache hit rate: "
|
||||
<< stats2.cache_hit_rate();
|
||||
}
|
||||
|
||||
// Test object rendering with different object types
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DifferentObjectTypes) {
|
||||
// Object types based on disassembly analysis
|
||||
std::vector<int> object_types = {
|
||||
0x10, // Wall objects
|
||||
0x20, // Floor objects
|
||||
0x30, // Decoration objects
|
||||
0xF9, // Small chest (from disassembly)
|
||||
0xFA, // Big chest (from disassembly)
|
||||
0x13, // Stairs
|
||||
0x17, // Door
|
||||
0x18, // Door variant
|
||||
0x40, // Water objects
|
||||
0x50 // Pipe objects
|
||||
};
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
for (int object_type : object_types) {
|
||||
auto object = CreateTestObject(object_type, 10, 10, 0x12, 0);
|
||||
std::vector<RoomObject> objects = {object};
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, palette);
|
||||
|
||||
// Some object types might not render (invalid IDs), that's okay
|
||||
if (result.ok()) {
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << "Object type 0x" << std::hex << object_type << std::dec
|
||||
<< " rendered successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "Object type 0x" << std::hex << object_type << std::dec
|
||||
<< " failed to render: " << result.status().message() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test object types found in real ROM rooms
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, RealRoomObjectTypes) {
|
||||
auto palette = test_palettes_[0];
|
||||
std::set<int> found_object_types;
|
||||
|
||||
// Collect all object types from real rooms
|
||||
for (const auto& [room_id, room] : rooms_) {
|
||||
const auto& objects = room.GetTileObjects();
|
||||
for (const auto& obj : objects) {
|
||||
found_object_types.insert(obj.id_);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Found " << found_object_types.size()
|
||||
<< " unique object types in real rooms:" << std::endl;
|
||||
|
||||
// Test rendering each unique object type
|
||||
for (int object_type : found_object_types) {
|
||||
auto object = CreateTestObject(object_type, 10, 10, 0x12, 0);
|
||||
std::vector<RoomObject> objects = {object};
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, palette);
|
||||
|
||||
if (result.ok()) {
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << " Object type 0x" << std::hex << object_type << std::dec
|
||||
<< " - rendered successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << " Object type 0x" << std::hex << object_type << std::dec
|
||||
<< " - failed: " << result.status().message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// We should find at least some object types
|
||||
EXPECT_GT(found_object_types.size(), 0) << "No object types found in real rooms";
|
||||
}
|
||||
|
||||
// Test object rendering with different sizes
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DifferentObjectSizes) {
|
||||
std::vector<int> object_sizes = {0x12, 0x22, 0x32, 0x42, 0x52};
|
||||
auto palette = test_palettes_[0];
|
||||
int object_type = 0x10; // Wall
|
||||
|
||||
for (int size : object_sizes) {
|
||||
auto object = CreateTestObject(object_type, 10, 10, size, 0);
|
||||
std::vector<RoomObject> objects = {object};
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, palette);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render object with size 0x"
|
||||
<< std::hex << size << std::dec;
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering with different layers
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DifferentLayers) {
|
||||
std::vector<int> layers = {0, 1, 2};
|
||||
auto palette = test_palettes_[0];
|
||||
int object_type = 0x10; // Wall
|
||||
|
||||
for (int layer : layers) {
|
||||
auto object = CreateTestObject(object_type, 10, 10, 0x12, layer);
|
||||
std::vector<RoomObject> objects = {object};
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, palette);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render object on layer " << layer;
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering memory usage
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, MemoryUsage) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
size_t initial_memory = object_renderer_->GetMemoryUsage();
|
||||
|
||||
// Render objects multiple times
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto result = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result.ok());
|
||||
}
|
||||
|
||||
size_t final_memory = object_renderer_->GetMemoryUsage();
|
||||
|
||||
// Memory usage should be reasonable (less than 100MB)
|
||||
EXPECT_LT(final_memory, 100 * 1024 * 1024) << "Memory usage too high: "
|
||||
<< final_memory / (1024 * 1024) << "MB";
|
||||
|
||||
// Memory usage shouldn't grow excessively
|
||||
EXPECT_LT(final_memory - initial_memory, 50 * 1024 * 1024)
|
||||
<< "Memory growth too high: "
|
||||
<< (final_memory - initial_memory) / (1024 * 1024) << "MB";
|
||||
}
|
||||
|
||||
// Test object rendering error handling
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, ErrorHandling) {
|
||||
// Test with empty object list
|
||||
std::vector<RoomObject> empty_objects;
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
auto result = object_renderer_->RenderObjects(empty_objects, palette);
|
||||
// Should either succeed with empty bitmap or fail gracefully
|
||||
if (!result.ok()) {
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(result.status()) ||
|
||||
absl::IsFailedPrecondition(result.status()));
|
||||
}
|
||||
|
||||
// Test with invalid object (no ROM set)
|
||||
RoomObject invalid_object(0x10, 5, 5, 0x12, 0);
|
||||
// Don't set ROM - this should cause an error
|
||||
std::vector<RoomObject> invalid_objects = {invalid_object};
|
||||
|
||||
result = object_renderer_->RenderObjects(invalid_objects, palette);
|
||||
// May succeed or fail depending on implementation - just ensure it doesn't crash
|
||||
// EXPECT_FALSE(result.ok());
|
||||
}
|
||||
|
||||
// Test object rendering with large object sets
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, LargeObjectSetRendering) {
|
||||
std::vector<RoomObject> large_object_set;
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
// Create a large set of objects (100 objects)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int object_type = 0x10 + (i % 20); // Vary object types
|
||||
int x = (i % 10) * 16; // Spread across 10x10 grid
|
||||
int y = (i / 10) * 16;
|
||||
int size = 0x12 + (i % 4) * 0x10; // Vary sizes
|
||||
|
||||
large_object_set.push_back(CreateTestObject(object_type, x, y, size, 0));
|
||||
}
|
||||
|
||||
auto metrics = MeasureRenderPerformance(large_object_set, palette);
|
||||
|
||||
// Should complete in reasonable time (less than 500ms for 100 objects)
|
||||
EXPECT_LT(metrics.render_time.count(), 500)
|
||||
<< "Rendering 100 objects took too long: "
|
||||
<< metrics.render_time.count() << "ms";
|
||||
|
||||
EXPECT_EQ(metrics.objects_rendered, 100);
|
||||
}
|
||||
|
||||
// Test object rendering consistency
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, RenderingConsistency) {
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto palette = test_palettes_[0];
|
||||
|
||||
// Render the same objects multiple times
|
||||
std::vector<gfx::Bitmap> results;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
auto result = object_renderer_->RenderObjects(test_objects, palette);
|
||||
ASSERT_TRUE(result.ok()) << "Failed on iteration " << i;
|
||||
results.push_back(std::move(result.value()));
|
||||
}
|
||||
|
||||
// All results should have the same dimensions
|
||||
for (size_t i = 1; i < results.size(); i++) {
|
||||
EXPECT_EQ(results[0].width(), results[i].width());
|
||||
EXPECT_EQ(results[0].height(), results[i].height());
|
||||
}
|
||||
}
|
||||
|
||||
// Test object rendering with dungeon editor integration
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DungeonEditorIntegration) {
|
||||
// Load a room into the object editor
|
||||
ASSERT_TRUE(object_editor_->LoadRoom(0).ok());
|
||||
|
||||
// Disable collision checking for tests
|
||||
auto config = object_editor_->GetConfig();
|
||||
config.validate_objects = false;
|
||||
object_editor_->SetConfig(config);
|
||||
|
||||
// Add some objects
|
||||
ASSERT_TRUE(object_editor_->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor_->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Get the objects from the editor
|
||||
const auto& objects = object_editor_->GetObjects();
|
||||
ASSERT_EQ(objects.size(), 2);
|
||||
|
||||
// Render the objects
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render objects from editor: "
|
||||
<< result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
|
||||
// Test object rendering with dungeon editor system integration
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, DungeonEditorSystemIntegration) {
|
||||
// Set current room
|
||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0).ok());
|
||||
|
||||
// Get object editor from system
|
||||
auto system_object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||
ASSERT_NE(system_object_editor, nullptr);
|
||||
|
||||
// Disable collision checking for tests
|
||||
auto config = system_object_editor->GetConfig();
|
||||
config.validate_objects = false;
|
||||
system_object_editor->SetConfig(config);
|
||||
|
||||
// Add objects through the system
|
||||
ASSERT_TRUE(system_object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(system_object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Get objects and render them
|
||||
const auto& objects = system_object_editor->GetObjects();
|
||||
ASSERT_EQ(objects.size(), 2);
|
||||
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Failed to render objects from system: "
|
||||
<< result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
|
||||
// Test object rendering with undo/redo functionality
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, UndoRedoIntegration) {
|
||||
// Load a room and add objects
|
||||
ASSERT_TRUE(object_editor_->LoadRoom(0).ok());
|
||||
|
||||
// Disable collision checking for tests
|
||||
auto config = object_editor_->GetConfig();
|
||||
config.validate_objects = false;
|
||||
object_editor_->SetConfig(config);
|
||||
|
||||
ASSERT_TRUE(object_editor_->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
||||
ASSERT_TRUE(object_editor_->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
||||
|
||||
// Render initial state
|
||||
auto objects_before = object_editor_->GetObjects();
|
||||
auto result_before = object_renderer_->RenderObjects(objects_before, test_palettes_[0]);
|
||||
ASSERT_TRUE(result_before.ok());
|
||||
|
||||
// Undo one operation
|
||||
ASSERT_TRUE(object_editor_->Undo().ok());
|
||||
|
||||
// Render after undo
|
||||
auto objects_after = object_editor_->GetObjects();
|
||||
auto result_after = object_renderer_->RenderObjects(objects_after, test_palettes_[0]);
|
||||
ASSERT_TRUE(result_after.ok());
|
||||
|
||||
// Should have one fewer object
|
||||
EXPECT_EQ(objects_after.size(), objects_before.size() - 1);
|
||||
|
||||
// Redo the operation
|
||||
ASSERT_TRUE(object_editor_->Redo().ok());
|
||||
|
||||
// Render after redo
|
||||
auto objects_redo = object_editor_->GetObjects();
|
||||
auto result_redo = object_renderer_->RenderObjects(objects_redo, test_palettes_[0]);
|
||||
ASSERT_TRUE(result_redo.ok());
|
||||
|
||||
// Should be back to original state
|
||||
EXPECT_EQ(objects_redo.size(), objects_before.size());
|
||||
}
|
||||
|
||||
// Test ROM integrity and validation
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, ROMIntegrityValidation) {
|
||||
// Verify ROM is loaded correctly
|
||||
EXPECT_TRUE(rom_->is_loaded());
|
||||
EXPECT_GT(rom_->size(), 0);
|
||||
|
||||
// Test ROM header validation (if method exists)
|
||||
// Note: ValidateHeader() may not be available in all ROM implementations
|
||||
// EXPECT_TRUE(rom_->ValidateHeader().ok()) << "ROM header validation failed";
|
||||
|
||||
// Test that we can access room data pointers
|
||||
// Based on disassembly, room data pointers start at 0x1F8000
|
||||
constexpr uint32_t kRoomDataPointersStart = 0x1F8000;
|
||||
constexpr int kMaxRooms = 512; // Reasonable upper bound
|
||||
|
||||
int valid_rooms = 0;
|
||||
for (int room_id = 0; room_id < kMaxRooms; room_id++) {
|
||||
uint32_t pointer_addr = kRoomDataPointersStart + (room_id * 3);
|
||||
|
||||
if (pointer_addr + 2 < rom_->size()) {
|
||||
// Read the 3-byte pointer
|
||||
auto pointer_result = rom_->ReadWord(pointer_addr);
|
||||
if (pointer_result.ok()) {
|
||||
uint32_t room_data_ptr = pointer_result.value();
|
||||
|
||||
// Check if pointer is reasonable (within ROM bounds)
|
||||
if (room_data_ptr >= 0x80000 && room_data_ptr < rom_->size()) {
|
||||
valid_rooms++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We should find many valid rooms (based on disassembly analysis)
|
||||
EXPECT_GT(valid_rooms, 50) << "Found too few valid rooms: " << valid_rooms;
|
||||
|
||||
std::cout << "ROM integrity validation: " << valid_rooms << " valid rooms found" << std::endl;
|
||||
}
|
||||
|
||||
// Test palette validation against vanilla values
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, PaletteValidation) {
|
||||
// Load palette data and validate against expected vanilla values
|
||||
auto palette_group = rom_->palette_group().dungeon_main;
|
||||
|
||||
EXPECT_GT(palette_group.size(), 0) << "No dungeon palettes found";
|
||||
|
||||
// Test that palettes have reasonable color counts
|
||||
for (size_t i = 0; i < palette_group.size() && i < 10; i++) {
|
||||
const auto& palette = palette_group[i];
|
||||
EXPECT_GT(palette.size(), 0) << "Palette " << i << " is empty";
|
||||
EXPECT_LE(palette.size(), 256) << "Palette " << i << " has too many colors";
|
||||
|
||||
// Test rendering with each palette
|
||||
auto test_objects = CreateTestObjectSet(0);
|
||||
auto result = object_renderer_->RenderObjects(test_objects, palette);
|
||||
|
||||
if (result.ok()) {
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
|
||||
std::cout << "Palette " << i << " rendered successfully with "
|
||||
<< palette.size() << " colors" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test comprehensive room loading and validation
|
||||
TEST_F(DungeonObjectRendererIntegrationTest, ComprehensiveRoomValidation) {
|
||||
int total_objects = 0;
|
||||
int rooms_with_objects = 0;
|
||||
std::map<int, int> object_type_counts;
|
||||
|
||||
// Test loading a larger set of rooms
|
||||
std::vector<int> extended_rooms = {
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0006, 0x0007, 0x0008, 0x0009,
|
||||
0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010, 0x0011, 0x0012, 0x0013,
|
||||
0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C,
|
||||
0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0026,
|
||||
0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002E, 0x002F, 0x0030,
|
||||
0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039,
|
||||
0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042,
|
||||
0x0043, 0x0044, 0x0045, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E,
|
||||
0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
|
||||
0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E
|
||||
};
|
||||
|
||||
for (int room_id : extended_rooms) {
|
||||
auto room_result = zelda3::LoadRoomFromRom(rom_.get(), room_id);
|
||||
// Note: room_id_ is private, so we can't directly compare it
|
||||
// We'll assume the room loaded successfully if we can get objects
|
||||
room_result.LoadObjects();
|
||||
const auto& objects = room_result.GetTileObjects();
|
||||
|
||||
if (!objects.empty()) {
|
||||
rooms_with_objects++;
|
||||
total_objects += objects.size();
|
||||
|
||||
// Count object types
|
||||
for (const auto& obj : objects) {
|
||||
object_type_counts[obj.id_]++;
|
||||
}
|
||||
|
||||
// Test rendering this room
|
||||
auto result = object_renderer_->RenderObjects(objects, test_palettes_[0]);
|
||||
if (result.ok()) {
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_GT(bitmap.width(), 0);
|
||||
EXPECT_GT(bitmap.height(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Comprehensive room validation results:" << std::endl;
|
||||
std::cout << " Rooms with objects: " << rooms_with_objects << std::endl;
|
||||
std::cout << " Total objects: " << total_objects << std::endl;
|
||||
std::cout << " Unique object types: " << object_type_counts.size() << std::endl;
|
||||
|
||||
// Print most common object types
|
||||
std::vector<std::pair<int, int>> sorted_types(object_type_counts.begin(), object_type_counts.end());
|
||||
std::sort(sorted_types.begin(), sorted_types.end(),
|
||||
[](const auto& a, const auto& b) { return a.second > b.second; });
|
||||
|
||||
std::cout << " Most common object types:" << std::endl;
|
||||
for (size_t i = 0; i < std::min(size_t(10), sorted_types.size()); i++) {
|
||||
std::cout << " 0x" << std::hex << sorted_types[i].first << std::dec
|
||||
<< ": " << sorted_types[i].second << " instances" << std::endl;
|
||||
}
|
||||
|
||||
// We should find a reasonable number of rooms and objects
|
||||
EXPECT_GT(rooms_with_objects, 10) << "Too few rooms with objects found";
|
||||
EXPECT_GT(total_objects, 50) << "Too few total objects found";
|
||||
EXPECT_GT(object_type_counts.size(), 5) << "Too few unique object types found";
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,96 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
|
||||
using namespace yaze::zelda3;
|
||||
using namespace yaze;
|
||||
|
||||
int main() {
|
||||
// Load the vanilla ROM
|
||||
Rom rom;
|
||||
if (!rom.LoadFromFile("zelda3.sfc").ok()) {
|
||||
std::cerr << "Failed to load ROM file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "// Vanilla ROM values extracted from zelda3.sfc" << std::endl;
|
||||
std::cout << "// Generated on " << __DATE__ << " " << __TIME__ << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract ASM version
|
||||
uint8_t asm_version = rom[OverworldCustomASMHasBeenApplied];
|
||||
std::cout << "constexpr uint8_t kVanillaASMVersion = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)asm_version << ";" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract area graphics for first 10 maps
|
||||
std::cout << "// Area graphics for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t area_gfx = rom[kAreaGfxIdPtr + i];
|
||||
std::cout << "constexpr uint8_t kVanillaAreaGraphics" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)area_gfx << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract area palettes for first 10 maps
|
||||
std::cout << "// Area palettes for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t area_pal = rom[kOverworldMapPaletteIds + i];
|
||||
std::cout << "constexpr uint8_t kVanillaAreaPalette" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)area_pal << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract message IDs for first 10 maps
|
||||
std::cout << "// Message IDs for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint16_t message_id = rom[kOverworldMessageIds + (i * 2)] | (rom[kOverworldMessageIds + (i * 2) + 1] << 8);
|
||||
std::cout << "constexpr uint16_t kVanillaMessageId" << i << " = 0x" << std::hex << std::setw(4) << std::setfill('0') << message_id << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract screen sizes for first 10 maps
|
||||
std::cout << "// Screen sizes for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t screen_size = rom[kOverworldScreenSize + i];
|
||||
std::cout << "constexpr uint8_t kVanillaScreenSize" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)screen_size << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract sprite sets for first 10 maps
|
||||
std::cout << "// Sprite sets for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t sprite_set = rom[kOverworldSpriteset + i];
|
||||
std::cout << "constexpr uint8_t kVanillaSpriteSet" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)sprite_set << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract sprite palettes for first 10 maps
|
||||
std::cout << "// Sprite palettes for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t sprite_pal = rom[kOverworldSpritePaletteIds + i];
|
||||
std::cout << "constexpr uint8_t kVanillaSpritePalette" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)sprite_pal << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract music for first 10 maps
|
||||
std::cout << "// Music for first 10 maps" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
uint8_t music = rom[kOverworldMusicBeginning + i];
|
||||
std::cout << "constexpr uint8_t kVanillaMusic" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)music << ";" << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Extract some special world values
|
||||
std::cout << "// Special world graphics and palettes" << std::endl;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
uint8_t special_gfx = rom[kOverworldSpecialGfxGroup + i];
|
||||
uint8_t special_pal = rom[kOverworldSpecialPalGroup + i];
|
||||
std::cout << "constexpr uint8_t kVanillaSpecialGfx" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)special_gfx << ";" << std::endl;
|
||||
std::cout << "constexpr uint8_t kVanillaSpecialPal" << i << " = 0x" << std::hex << std::setw(2) << std::setfill('0') << (int)special_pal << ";" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
|
||||
using namespace yaze::zelda3;
|
||||
using namespace yaze;
|
||||
|
||||
/**
|
||||
* @brief Comprehensive ROM value extraction tool for golden data testing
|
||||
*
|
||||
* This tool extracts all overworld-related values from a ROM to create
|
||||
* "golden" reference data for before/after edit validation and comprehensive
|
||||
* E2E testing. It supports both vanilla and ZSCustomOverworld ROMs.
|
||||
*/
|
||||
class OverworldGoldenDataExtractor {
|
||||
public:
|
||||
explicit OverworldGoldenDataExtractor(const std::string& rom_path)
|
||||
: rom_path_(rom_path) {}
|
||||
|
||||
absl::Status ExtractAllData(const std::string& output_path) {
|
||||
// Load ROM
|
||||
Rom rom;
|
||||
RETURN_IF_ERROR(rom.LoadFromFile(rom_path_));
|
||||
|
||||
// Load overworld data
|
||||
Overworld overworld(&rom);
|
||||
RETURN_IF_ERROR(overworld.Load(&rom));
|
||||
|
||||
std::ofstream out_file(output_path);
|
||||
if (!out_file.is_open()) {
|
||||
return absl::InternalError("Failed to open output file: " + output_path);
|
||||
}
|
||||
|
||||
// Write header
|
||||
WriteHeader(out_file);
|
||||
|
||||
// Extract basic ROM info
|
||||
WriteBasicROMInfo(out_file, rom);
|
||||
|
||||
// Extract ASM version info
|
||||
WriteASMVersionInfo(out_file, rom);
|
||||
|
||||
// Extract overworld maps data
|
||||
WriteOverworldMapsData(out_file, overworld);
|
||||
|
||||
// Extract tile data
|
||||
WriteTileData(out_file, overworld);
|
||||
|
||||
// Extract entrance/hole/exit data
|
||||
WriteEntranceData(out_file, overworld);
|
||||
WriteHoleData(out_file, overworld);
|
||||
WriteExitData(out_file, overworld);
|
||||
|
||||
// Extract item data
|
||||
WriteItemData(out_file, overworld);
|
||||
|
||||
// Extract sprite data
|
||||
WriteSpriteData(out_file, overworld);
|
||||
|
||||
// Extract map tiles (compressed data)
|
||||
WriteMapTilesData(out_file, overworld);
|
||||
|
||||
// Extract palette data
|
||||
WritePaletteData(out_file, rom);
|
||||
|
||||
// Extract music data
|
||||
WriteMusicData(out_file, rom);
|
||||
|
||||
// Extract overlay data
|
||||
WriteOverlayData(out_file, rom);
|
||||
|
||||
// Write footer
|
||||
WriteFooter(out_file);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteHeader(std::ofstream& out) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// YAZE Overworld Golden Data - Generated from: " << rom_path_ << std::endl;
|
||||
out << "// Generated on: " << __DATE__ << " " << __TIME__ << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
out << "#pragma once" << std::endl;
|
||||
out << std::endl;
|
||||
out << "#include <cstdint>" << std::endl;
|
||||
out << "#include <array>" << std::endl;
|
||||
out << "#include <vector>" << std::endl;
|
||||
out << "#include \"app/zelda3/overworld/overworld_map.h\"" << std::endl;
|
||||
out << std::endl;
|
||||
out << "namespace yaze {" << std::endl;
|
||||
out << "namespace test {" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteFooter(std::ofstream& out) {
|
||||
out << std::endl;
|
||||
out << "} // namespace test" << std::endl;
|
||||
out << "} // namespace yaze" << std::endl;
|
||||
}
|
||||
|
||||
void WriteBasicROMInfo(std::ofstream& out, Rom& rom) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Basic ROM Information" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::string_view kGoldenROMTitle = \"" << rom.title() << "\";" << std::endl;
|
||||
out << "constexpr size_t kGoldenROMSize = " << rom.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// ROM header validation
|
||||
auto header_checksum = rom.ReadWord(0x7FDC);
|
||||
auto header_checksum_complement = rom.ReadWord(0x7FDE);
|
||||
if (header_checksum.ok() && header_checksum_complement.ok()) {
|
||||
out << "constexpr uint16_t kGoldenHeaderChecksum = 0x"
|
||||
<< std::hex << std::setw(4) << std::setfill('0')
|
||||
<< *header_checksum << ";" << std::endl;
|
||||
out << "constexpr uint16_t kGoldenHeaderChecksumComplement = 0x"
|
||||
<< std::hex << std::setw(4) << std::setfill('0')
|
||||
<< *header_checksum_complement << ";" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteASMVersionInfo(std::ofstream& out, Rom& rom) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// ASM Version Information" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
auto asm_version = rom.ReadByte(0x140145);
|
||||
if (asm_version.ok()) {
|
||||
out << "constexpr uint8_t kGoldenASMVersion = 0x"
|
||||
<< std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(*asm_version) << ";" << std::endl;
|
||||
|
||||
if (*asm_version == 0xFF) {
|
||||
out << "constexpr bool kGoldenIsVanillaROM = true;" << std::endl;
|
||||
out << "constexpr bool kGoldenHasZSCustomOverworld = false;" << std::endl;
|
||||
} else {
|
||||
out << "constexpr bool kGoldenIsVanillaROM = false;" << std::endl;
|
||||
out << "constexpr bool kGoldenHasZSCustomOverworld = true;" << std::endl;
|
||||
out << "constexpr uint8_t kGoldenZSCustomOverworldVersion = "
|
||||
<< static_cast<int>(*asm_version) << ";" << std::endl;
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
// Feature flags for v3
|
||||
if (asm_version.ok() && *asm_version >= 0x03) {
|
||||
out << "// v3 Feature Flags" << std::endl;
|
||||
|
||||
auto main_palettes = rom.ReadByte(0x140146);
|
||||
auto area_bg = rom.ReadByte(0x140147);
|
||||
auto subscreen_overlay = rom.ReadByte(0x140148);
|
||||
auto animated_gfx = rom.ReadByte(0x140149);
|
||||
auto custom_tiles = rom.ReadByte(0x14014A);
|
||||
auto mosaic = rom.ReadByte(0x14014B);
|
||||
|
||||
if (main_palettes.ok()) {
|
||||
out << "constexpr bool kGoldenEnableMainPalettes = "
|
||||
<< (*main_palettes != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
if (area_bg.ok()) {
|
||||
out << "constexpr bool kGoldenEnableAreaSpecificBG = "
|
||||
<< (*area_bg != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
if (subscreen_overlay.ok()) {
|
||||
out << "constexpr bool kGoldenEnableSubscreenOverlay = "
|
||||
<< (*subscreen_overlay != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
if (animated_gfx.ok()) {
|
||||
out << "constexpr bool kGoldenEnableAnimatedGFX = "
|
||||
<< (*animated_gfx != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
if (custom_tiles.ok()) {
|
||||
out << "constexpr bool kGoldenEnableCustomTiles = "
|
||||
<< (*custom_tiles != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
if (mosaic.ok()) {
|
||||
out << "constexpr bool kGoldenEnableMosaic = "
|
||||
<< (*mosaic != 0 ? "true" : "false") << ";" << std::endl;
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteOverworldMapsData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Overworld Maps Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& maps = overworld.overworld_maps();
|
||||
out << "constexpr size_t kGoldenNumOverworldMaps = " << maps.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Extract map properties for first 20 maps (to keep file size manageable)
|
||||
out << "// Map properties for first 20 maps" << std::endl;
|
||||
out << "constexpr std::array<uint8_t, 20> kGoldenMapAreaGraphics = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(maps[i].area_graphics());
|
||||
if (i < 19) out << ",";
|
||||
out << " // Map " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::array<uint8_t, 20> kGoldenMapMainPalettes = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(maps[i].main_palette());
|
||||
if (i < 19) out << ",";
|
||||
out << " // Map " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::array<AreaSizeEnum, 20> kGoldenMapAreaSizes = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(20, static_cast<int>(maps.size())); i++) {
|
||||
out << " AreaSizeEnum::";
|
||||
switch (maps[i].area_size()) {
|
||||
case AreaSizeEnum::SmallArea: out << "SmallArea"; break;
|
||||
case AreaSizeEnum::LargeArea: out << "LargeArea"; break;
|
||||
case AreaSizeEnum::WideArea: out << "WideArea"; break;
|
||||
case AreaSizeEnum::TallArea: out << "TallArea"; break;
|
||||
}
|
||||
if (i < 19) out << ",";
|
||||
out << " // Map " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteTileData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Tile Data Information" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr bool kGoldenExpandedTile16 = "
|
||||
<< (overworld.expanded_tile16() ? "true" : "false") << ";" << std::endl;
|
||||
out << "constexpr bool kGoldenExpandedTile32 = "
|
||||
<< (overworld.expanded_tile32() ? "true" : "false") << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& tiles16 = overworld.tiles16();
|
||||
const auto& tiles32 = overworld.tiles32_unique();
|
||||
|
||||
out << "constexpr size_t kGoldenNumTiles16 = " << tiles16.size() << ";" << std::endl;
|
||||
out << "constexpr size_t kGoldenNumTiles32 = " << tiles32.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample some tile data for validation
|
||||
out << "// Sample Tile16 data (first 10 tiles)" << std::endl;
|
||||
out << "constexpr std::array<uint32_t, 10> kGoldenTile16Sample = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(tiles16.size())); i++) {
|
||||
// Extract tile data as uint32_t for sample using TileInfo values
|
||||
const auto& tile16 = tiles16[i];
|
||||
uint32_t sample = tile16.tile0_.id_ | (tile16.tile1_.id_ << 8) |
|
||||
(tile16.tile2_.id_ << 16) | (tile16.tile3_.id_ << 24);
|
||||
out << " 0x" << std::hex << std::setw(8) << std::setfill('0') << sample;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Tile16 " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteEntranceData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Entrance Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& entrances = overworld.entrances();
|
||||
out << "constexpr size_t kGoldenNumEntrances = " << entrances.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample entrance data for validation
|
||||
out << "// Sample entrance data (first 10 entrances)" << std::endl;
|
||||
out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapPos = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< entrances[i].map_pos_;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Entrance " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::array<uint16_t, 10> kGoldenEntranceMapId = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< entrances[i].map_id_;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Entrance " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::array<int, 10> kGoldenEntranceX = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
out << " " << std::dec << entrances[i].x_;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Entrance " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
out << "constexpr std::array<int, 10> kGoldenEntranceY = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
out << " " << std::dec << entrances[i].y_;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Entrance " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteHoleData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Hole Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& holes = overworld.holes();
|
||||
out << "constexpr size_t kGoldenNumHoles = " << holes.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample hole data for validation
|
||||
out << "// Sample hole data (first 5 holes)" << std::endl;
|
||||
out << "constexpr std::array<uint16_t, 5> kGoldenHoleMapPos = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< holes[i].map_pos_;
|
||||
if (i < 4) out << ",";
|
||||
out << " // Hole " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteExitData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Exit Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& exits = overworld.exits();
|
||||
out << "constexpr size_t kGoldenNumExits = " << exits->size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample exit data for validation
|
||||
out << "// Sample exit data (first 10 exits)" << std::endl;
|
||||
out << "constexpr std::array<uint16_t, 10> kGoldenExitRoomId = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(exits->size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< (*exits)[i].room_id_;
|
||||
if (i < 9) out << ",";
|
||||
out << " // Exit " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteItemData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Item Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& items = overworld.all_items();
|
||||
out << "constexpr size_t kGoldenNumItems = " << items.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample item data for validation
|
||||
if (!items.empty()) {
|
||||
out << "// Sample item data (first 10 items)" << std::endl;
|
||||
out << "constexpr std::array<uint8_t, 10> kGoldenItemIds = {{" << std::endl;
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(items.size())); i++) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(items[i].id_);
|
||||
if (i < 9) out << ",";
|
||||
out << " // Item " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteSpriteData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Sprite Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& sprites = overworld.all_sprites();
|
||||
out << "constexpr size_t kGoldenNumSpriteStates = " << sprites.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample sprite data for validation
|
||||
out << "// Sample sprite data (first 5 sprites from each state)" << std::endl;
|
||||
for (int state = 0; state < std::min(3, static_cast<int>(sprites.size())); state++) {
|
||||
out << "constexpr size_t kGoldenNumSpritesState" << state << " = "
|
||||
<< sprites[state].size() << ";" << std::endl;
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteMapTilesData(std::ofstream& out, Overworld& overworld) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Map Tiles Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
const auto& map_tiles = overworld.map_tiles();
|
||||
out << "// Map tile dimensions" << std::endl;
|
||||
out << "constexpr size_t kGoldenMapTileWidth = " << map_tiles.light_world[0].size() << ";" << std::endl;
|
||||
out << "constexpr size_t kGoldenMapTileHeight = " << map_tiles.light_world.size() << ";" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample map tile data for validation
|
||||
out << "// Sample map tile data (top-left 10x10 corner of Light World)" << std::endl;
|
||||
out << "constexpr std::array<std::array<uint16_t, 10>, 10> kGoldenMapTilesSample = {{" << std::endl;
|
||||
for (int row = 0; row < 10; row++) {
|
||||
out << " {{";
|
||||
for (int col = 0; col < 10; col++) {
|
||||
out << "0x" << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< map_tiles.light_world[row][col];
|
||||
if (col < 9) out << ", ";
|
||||
}
|
||||
out << "}}";
|
||||
if (row < 9) out << ",";
|
||||
out << " // Row " << row << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WritePaletteData(std::ofstream& out, Rom& rom) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Palette Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample palette data from ROM
|
||||
out << "// Sample palette data (first 10 bytes from overworld palette table)" << std::endl;
|
||||
out << "constexpr std::array<uint8_t, 10> kGoldenPaletteSample = {{" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto palette_byte = rom.ReadByte(0x7D1C + i); // overworldMapPalette
|
||||
if (palette_byte.ok()) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(*palette_byte);
|
||||
} else {
|
||||
out << " 0x00";
|
||||
}
|
||||
if (i < 9) out << ",";
|
||||
out << " // Palette " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteMusicData(std::ofstream& out, Rom& rom) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Music Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample music data from ROM
|
||||
out << "// Sample music data (first 10 bytes from overworld music table)" << std::endl;
|
||||
out << "constexpr std::array<uint8_t, 10> kGoldenMusicSample = {{" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto music_byte = rom.ReadByte(0x14303 + i); // overworldMusicBegining
|
||||
if (music_byte.ok()) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(*music_byte);
|
||||
} else {
|
||||
out << " 0x00";
|
||||
}
|
||||
if (i < 9) out << ",";
|
||||
out << " // Music " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void WriteOverlayData(std::ofstream& out, Rom& rom) {
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << "// Overlay Data" << std::endl;
|
||||
out << "// =============================================================================" << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
// Sample overlay data from ROM
|
||||
out << "// Sample overlay data (first 10 bytes from overlay pointers)" << std::endl;
|
||||
out << "constexpr std::array<uint8_t, 10> kGoldenOverlaySample = {{" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto overlay_byte = rom.ReadByte(0x77664 + i); // overlayPointers
|
||||
if (overlay_byte.ok()) {
|
||||
out << " 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(*overlay_byte);
|
||||
} else {
|
||||
out << " 0x00";
|
||||
}
|
||||
if (i < 9) out << ",";
|
||||
out << " // Overlay " << i << std::endl;
|
||||
}
|
||||
out << "}};" << std::endl;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
std::string rom_path_;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <rom_path> <output_path>" << std::endl;
|
||||
std::cerr << "Example: " << argv[0] << " zelda3.sfc golden_data.h" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string rom_path = argv[1];
|
||||
std::string output_path = argv[2];
|
||||
|
||||
if (!std::filesystem::exists(rom_path)) {
|
||||
std::cerr << "Error: ROM file not found: " << rom_path << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
OverworldGoldenDataExtractor extractor(rom_path);
|
||||
auto status = extractor.ExtractAllData(output_path);
|
||||
|
||||
if (status.ok()) {
|
||||
std::cout << "Successfully extracted golden data from " << rom_path
|
||||
<< " to " << output_path << std::endl;
|
||||
return 0;
|
||||
} else {
|
||||
std::cerr << "Error extracting golden data: " << status.message() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Comprehensive overworld integration test that validates YAZE C++
|
||||
* implementation against ZScream C# logic and existing test infrastructure
|
||||
*
|
||||
* This test suite:
|
||||
* 1. Validates overworld loading logic matches ZScream behavior
|
||||
* 2. Tests integration with ZSCustomOverworld versions (vanilla, v2, v3)
|
||||
* 3. Uses existing RomDependentTestSuite infrastructure when available
|
||||
* 4. Provides both mock data and real ROM testing capabilities
|
||||
*/
|
||||
class OverworldIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#endif
|
||||
|
||||
// Check if we should use real ROM or mock data
|
||||
const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
|
||||
const char* skip_rom_tests = getenv("YAZE_SKIP_ROM_TESTS");
|
||||
|
||||
if (skip_rom_tests) {
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
}
|
||||
|
||||
if (rom_path_env && std::filesystem::exists(rom_path_env)) {
|
||||
// Use real ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
auto status = rom_->LoadFromFile(rom_path_env);
|
||||
if (status.ok()) {
|
||||
use_real_rom_ = true;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to mock data
|
||||
use_real_rom_ = false;
|
||||
rom_ = std::make_unique<Rom>();
|
||||
SetupMockRomData();
|
||||
rom_->LoadFromData(mock_rom_data_);
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
overworld_.reset();
|
||||
rom_.reset();
|
||||
}
|
||||
|
||||
void SetupMockRomData() {
|
||||
mock_rom_data_.resize(0x200000, 0x00);
|
||||
|
||||
// Basic ROM structure
|
||||
mock_rom_data_[0x140145] = 0xFF; // Vanilla ASM
|
||||
|
||||
// Tile16 expansion flag
|
||||
mock_rom_data_[0x017D28] = 0x0F; // Vanilla
|
||||
|
||||
// Tile32 expansion flag
|
||||
mock_rom_data_[0x01772E] = 0x04; // Vanilla
|
||||
|
||||
// Basic map data
|
||||
for (int i = 0; i < 160; i++) {
|
||||
mock_rom_data_[0x012844 + i] = 0x00; // Small areas
|
||||
}
|
||||
|
||||
// Setup entrance data (matches ZScream Constants.OWEntranceMap/Pos/EntranceId)
|
||||
for (int i = 0; i < 129; i++) {
|
||||
mock_rom_data_[0x0DB96F + (i * 2)] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x0DB96F + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBA71 + (i * 2)] = (i * 16) & 0xFF; // Map Position
|
||||
mock_rom_data_[0x0DBA71 + (i * 2) + 1] = ((i * 16) >> 8) & 0xFF;
|
||||
mock_rom_data_[0x0DBB73 + i] = i & 0xFF; // Entrance ID
|
||||
}
|
||||
|
||||
// Setup exit data (matches ZScream Constants.OWExit*)
|
||||
for (int i = 0; i < 0x4F; i++) {
|
||||
mock_rom_data_[0x015D8A + (i * 2)] = i & 0xFF; // Room ID
|
||||
mock_rom_data_[0x015D8A + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
mock_rom_data_[0x015E28 + i] = i & 0xFF; // Map ID
|
||||
mock_rom_data_[0x015E77 + (i * 2)] = i & 0xFF; // VRAM
|
||||
mock_rom_data_[0x015E77 + (i * 2) + 1] = (i >> 8) & 0xFF;
|
||||
// Add other exit fields...
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> mock_rom_data_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<Overworld> overworld_;
|
||||
bool use_real_rom_ = false;
|
||||
};
|
||||
|
||||
// Test Tile32 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile32ExpansionDetection) {
|
||||
mock_rom_data_[0x01772E] = 0x04;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x01772E] = 0x05;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
// Test Tile16 expansion detection
|
||||
TEST_F(OverworldIntegrationTest, Tile16ExpansionDetection) {
|
||||
mock_rom_data_[0x017D28] = 0x0F;
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Test expanded detection
|
||||
mock_rom_data_[0x017D28] = 0x10;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
// Test entrance loading matches ZScream coordinate calculation
|
||||
TEST_F(OverworldIntegrationTest, EntranceCoordinateCalculation) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
// Verify coordinate calculation matches ZScream logic:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
uint16_t map_pos = i * 16; // Our test data
|
||||
uint16_t map_id = i; // Our test data
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
EXPECT_EQ(entrance.entrance_id_, i);
|
||||
EXPECT_FALSE(entrance.is_hole_);
|
||||
}
|
||||
}
|
||||
|
||||
// Test exit loading matches ZScream data structure
|
||||
TEST_F(OverworldIntegrationTest, ExitDataLoading) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& exits = overworld_->exits();
|
||||
EXPECT_EQ(exits->size(), 0x4F);
|
||||
|
||||
// Verify exit data matches our test data
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(exits->size())); i++) {
|
||||
const auto& exit = exits->at(i);
|
||||
// EXPECT_EQ(exit.room_id_, i);
|
||||
// EXPECT_EQ(exit.map_id_, i);
|
||||
// EXPECT_EQ(exit.map_pos_, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Test ASM version detection affects item loading
|
||||
TEST_F(OverworldIntegrationTest, ASMVersionItemLoading) {
|
||||
// Test vanilla ASM (should limit to 0x80 maps)
|
||||
mock_rom_data_[0x140145] = 0xFF;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& items = overworld_->all_items();
|
||||
|
||||
// Test v3+ ASM (should support all 0xA0 maps)
|
||||
mock_rom_data_[0x140145] = 0x03;
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
|
||||
status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& items_v3 = overworld_->all_items();
|
||||
// v3 should have more comprehensive support
|
||||
EXPECT_GE(items_v3.size(), items.size());
|
||||
}
|
||||
|
||||
// Test map size assignment logic
|
||||
TEST_F(OverworldIntegrationTest, MapSizeAssignment) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
// Verify all maps are initialized
|
||||
for (const auto& map : maps) {
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
EXPECT_LE(map.area_size(), AreaSizeEnum::TallArea);
|
||||
}
|
||||
}
|
||||
|
||||
// Test integration with ZSCustomOverworld version detection
|
||||
TEST_F(OverworldIntegrationTest, ZSCustomOverworldVersionIntegration) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for ZSCustomOverworld version testing";
|
||||
}
|
||||
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Check ASM version detection
|
||||
auto version_byte = rom_->ReadByte(0x140145);
|
||||
ASSERT_TRUE(version_byte.ok());
|
||||
|
||||
uint8_t asm_version = *version_byte;
|
||||
|
||||
if (asm_version == 0xFF) {
|
||||
// Vanilla ROM
|
||||
EXPECT_FALSE(overworld_->expanded_tile16());
|
||||
EXPECT_FALSE(overworld_->expanded_tile32());
|
||||
} else if (asm_version >= 0x02 && asm_version <= 0x03) {
|
||||
// ZSCustomOverworld v2/v3
|
||||
// Should have expanded features
|
||||
EXPECT_TRUE(overworld_->expanded_tile16());
|
||||
EXPECT_TRUE(overworld_->expanded_tile32());
|
||||
}
|
||||
|
||||
// Verify version-specific features are properly detected
|
||||
if (asm_version >= 0x03) {
|
||||
// v3 features should be available
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160); // All 160 maps supported in v3
|
||||
}
|
||||
}
|
||||
|
||||
// Test compatibility with RomDependentTestSuite infrastructure
|
||||
TEST_F(OverworldIntegrationTest, RomDependentTestSuiteCompatibility) {
|
||||
if (!use_real_rom_) {
|
||||
GTEST_SKIP() << "Real ROM required for RomDependentTestSuite compatibility testing";
|
||||
}
|
||||
|
||||
// Test that our overworld loading works with the same patterns as RomDependentTestSuite
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Verify ROM-dependent features work correctly
|
||||
EXPECT_TRUE(overworld_->is_loaded());
|
||||
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
// Test that we can access the same data structures as RomDependentTestSuite
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(maps.size())); i++) {
|
||||
const auto& map = maps[i];
|
||||
|
||||
// Verify map properties are accessible
|
||||
EXPECT_GE(map.area_graphics(), 0);
|
||||
EXPECT_GE(map.main_palette(), 0);
|
||||
EXPECT_GE(map.area_size(), AreaSizeEnum::SmallArea);
|
||||
EXPECT_LE(map.area_size(), AreaSizeEnum::TallArea);
|
||||
}
|
||||
|
||||
// Test that sprite data is accessible (matches RomDependentTestSuite expectations)
|
||||
const auto& sprites = overworld_->sprites(0);
|
||||
EXPECT_EQ(sprites.size(), 3); // Three game states
|
||||
|
||||
// Test that item data is accessible
|
||||
const auto& items = overworld_->all_items();
|
||||
EXPECT_GE(items.size(), 0);
|
||||
|
||||
// Test that entrance/exit data is accessible
|
||||
const auto& entrances = overworld_->entrances();
|
||||
const auto& exits = overworld_->exits();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
EXPECT_EQ(exits->size(), 0x4F);
|
||||
}
|
||||
|
||||
// Test comprehensive overworld data integrity
|
||||
TEST_F(OverworldIntegrationTest, ComprehensiveDataIntegrity) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// Verify all major data structures are properly loaded
|
||||
EXPECT_GT(overworld_->tiles16().size(), 0);
|
||||
EXPECT_GT(overworld_->tiles32_unique().size(), 0);
|
||||
|
||||
// Verify map organization matches ZScream expectations
|
||||
const auto& map_tiles = overworld_->map_tiles();
|
||||
EXPECT_EQ(map_tiles.light_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.dark_world.size(), 512);
|
||||
EXPECT_EQ(map_tiles.special_world.size(), 512);
|
||||
|
||||
// Verify each world has proper 512x512 tile data
|
||||
for (const auto& row : map_tiles.light_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
}
|
||||
for (const auto& row : map_tiles.dark_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
}
|
||||
for (const auto& row : map_tiles.special_world) {
|
||||
EXPECT_EQ(row.size(), 512);
|
||||
}
|
||||
|
||||
// Verify overworld maps are properly initialized
|
||||
const auto& maps = overworld_->overworld_maps();
|
||||
EXPECT_EQ(maps.size(), 160);
|
||||
|
||||
for (const auto& map : maps) {
|
||||
// TODO: Find a way to compare
|
||||
// EXPECT_TRUE(map.bitmap_data() != nullptr);
|
||||
}
|
||||
|
||||
// Verify tile types are loaded
|
||||
const auto& tile_types = overworld_->all_tiles_types();
|
||||
EXPECT_EQ(tile_types.size(), 0x200);
|
||||
}
|
||||
|
||||
// Test ZScream coordinate calculation compatibility
|
||||
TEST_F(OverworldIntegrationTest, ZScreamCoordinateCompatibility) {
|
||||
auto status = overworld_->Load(rom_.get());
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
const auto& entrances = overworld_->entrances();
|
||||
EXPECT_EQ(entrances.size(), 129);
|
||||
|
||||
// Test coordinate calculation matches ZScream logic exactly
|
||||
for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
|
||||
const auto& entrance = entrances[i];
|
||||
|
||||
// ZScream coordinate calculation:
|
||||
// int p = mapPos >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = entrance.map_pos_;
|
||||
uint16_t map_id = entrance.map_id_;
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
EXPECT_EQ(entrance.x_, expected_x);
|
||||
EXPECT_EQ(entrance.y_, expected_y);
|
||||
}
|
||||
|
||||
// Test hole coordinate calculation with 0x400 offset
|
||||
const auto& holes = overworld_->holes();
|
||||
EXPECT_EQ(holes.size(), 0x13);
|
||||
|
||||
for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
|
||||
const auto& hole = holes[i];
|
||||
|
||||
// ZScream hole coordinate calculation:
|
||||
// int p = (mapPos + 0x400) >> 1;
|
||||
// int x = p % 64;
|
||||
// int y = p >> 6;
|
||||
// int real_x = (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512);
|
||||
// int real_y = (y * 16) + (((mapId % 64) / 8) * 512);
|
||||
|
||||
uint16_t map_pos = hole.map_pos_;
|
||||
uint16_t map_id = hole.map_id_;
|
||||
|
||||
int position = map_pos >> 1;
|
||||
int x_coord = position % 64;
|
||||
int y_coord = position >> 6;
|
||||
int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
|
||||
int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
|
||||
|
||||
EXPECT_EQ(hole.x_, expected_x);
|
||||
EXPECT_EQ(hole.y_, expected_y);
|
||||
EXPECT_TRUE(hole.is_hole_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
@@ -1,108 +0,0 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
|
||||
using namespace yaze::zelda3;
|
||||
using namespace yaze;
|
||||
|
||||
class ROMPatchUtility {
|
||||
public:
|
||||
static absl::Status CreateV3PatchedROM(const std::string& input_rom_path,
|
||||
const std::string& output_rom_path) {
|
||||
// Load the vanilla ROM
|
||||
Rom rom;
|
||||
RETURN_IF_ERROR(rom.LoadFromFile(input_rom_path));
|
||||
|
||||
// Apply ZSCustomOverworld v3 settings
|
||||
RETURN_IF_ERROR(ApplyV3Patch(rom));
|
||||
|
||||
// Save the patched ROM
|
||||
return rom.SaveToFile(Rom::SaveSettings{.filename = output_rom_path});
|
||||
}
|
||||
|
||||
private:
|
||||
static absl::Status ApplyV3Patch(Rom& rom) {
|
||||
// Set ASM version to v3
|
||||
rom.WriteByte(OverworldCustomASMHasBeenApplied, 0x03);
|
||||
|
||||
// Enable v3 features
|
||||
rom.WriteByte(OverworldCustomAreaSpecificBGEnabled, 0x01);
|
||||
rom.WriteByte(OverworldCustomSubscreenOverlayEnabled, 0x01);
|
||||
rom.WriteByte(OverworldCustomAnimatedGFXEnabled, 0x01);
|
||||
rom.WriteByte(OverworldCustomTileGFXGroupEnabled, 0x01);
|
||||
rom.WriteByte(OverworldCustomMosaicEnabled, 0x01);
|
||||
rom.WriteByte(OverworldCustomMainPaletteEnabled, 0x01);
|
||||
|
||||
// Apply v3 settings to first 10 maps for testing
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Set area sizes (mix of different sizes)
|
||||
AreaSizeEnum area_size = static_cast<AreaSizeEnum>(i % 4);
|
||||
rom.WriteByte(kOverworldScreenSize + i, static_cast<uint8_t>(area_size));
|
||||
|
||||
// Set main palettes
|
||||
rom.WriteByte(OverworldCustomMainPaletteArray + i, i % 8);
|
||||
|
||||
// Set area-specific background colors
|
||||
uint16_t bg_color = 0x0000 + (i * 0x1000);
|
||||
rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2),
|
||||
bg_color & 0xFF);
|
||||
rom.WriteByte(OverworldCustomAreaSpecificBGPalette + (i * 2) + 1,
|
||||
(bg_color >> 8) & 0xFF);
|
||||
|
||||
// Set subscreen overlays
|
||||
uint16_t overlay = 0x0090 + i;
|
||||
rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2),
|
||||
overlay & 0xFF);
|
||||
rom.WriteByte(OverworldCustomSubscreenOverlayArray + (i * 2) + 1,
|
||||
(overlay >> 8) & 0xFF);
|
||||
|
||||
// Set animated GFX
|
||||
rom.WriteByte(OverworldCustomAnimatedGFXArray + i, 0x50 + i);
|
||||
|
||||
// Set custom tile GFX groups (8 bytes per map)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
rom.WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j,
|
||||
0x20 + j + i);
|
||||
}
|
||||
|
||||
// Set mosaic settings
|
||||
rom.WriteByte(OverworldCustomMosaicArray + i, i % 16);
|
||||
|
||||
// Set expanded message IDs
|
||||
uint16_t message_id = 0x1000 + i;
|
||||
rom.WriteByte(kOverworldMessagesExpanded + (i * 2), message_id & 0xFF);
|
||||
rom.WriteByte(kOverworldMessagesExpanded + (i * 2) + 1,
|
||||
(message_id >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <input_rom> <output_rom>"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string input_rom = argv[1];
|
||||
std::string output_rom = argv[2];
|
||||
|
||||
auto status = ROMPatchUtility::CreateV3PatchedROM(input_rom, output_rom);
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to create patched ROM: " << status.message()
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Successfully created v3 patched ROM: " << output_rom
|
||||
<< std::endl;
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user