epic: refactor SDL2_Renderer usage to IRenderer and queued texture rendering

- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding.
- Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend.
- Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations.
- Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance.
- Introduced new methods for texture creation and updates, streamlining the rendering process across the application.
- Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
This commit is contained in:
scawful
2025-10-07 17:15:11 -04:00
parent 9e6f538520
commit 6c331f1fd0
101 changed files with 1401 additions and 2677 deletions

View File

@@ -12,150 +12,95 @@ foreach (file
endforeach()
# Only build test executable if tests are enabled
# Double-check to ensure tests are actually enabled
if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF")
# Main test executable with enhanced argument handling for AI agents
# Use CI version for minimal builds, full version for development
# Base list of test sources for all builds
set(YAZE_TEST_BASE_SOURCES
test_editor.cc
test_editor.h
testing.h
test_utils.h
# Unit Tests
unit/core/asar_wrapper_test.cc
unit/core/hex_test.cc
unit/cli/resource_catalog_test.cc
unit/rom/rom_test.cc
unit/gfx/snes_tile_test.cc
unit/gfx/compression_test.cc
unit/gfx/snes_palette_test.cc
unit/gui/tile_selector_widget_test.cc
unit/gui/canvas_automation_api_test.cc
unit/zelda3/overworld_test.cc
unit/zelda3/object_parser_test.cc
unit/zelda3/object_parser_structs_test.cc
unit/zelda3/sprite_builder_test.cc
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
unit/zelda3/dungeon/room_object_encoding_test.cc
unit/zelda3/dungeon/room_manipulation_test.cc
unit/zelda3/dungeon_object_renderer_mock_test.cc
# CLI Services (for catalog serialization tests)
../src/cli/service/resources/resource_catalog.cc
# Integration Tests
integration/asar_integration_test.cc
integration/asar_rom_test.cc
integration/dungeon_editor_test.cc
integration/dungeon_editor_test.h
integration/dungeon_editor_v2_test.cc
integration/dungeon_editor_v2_test.h
integration/editor/tile16_editor_test.cc
integration/editor/editor_integration_test.cc
integration/editor/editor_integration_test.h
integration/ai/ai_gui_controller_test.cc
integration/ai/test_ai_tile_placement.cc
integration/ai/test_gemini_vision.cc
# E2E Tests
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
# Integration Tests (Zelda3)
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
integration/zelda3/dungeon_object_rendering_tests.cc
integration/zelda3/dungeon_room_test.cc
integration/zelda3/sprite_position_test.cc
integration/zelda3/message_test.cc
)
# Sources only for full development builds
set(YAZE_TEST_DEV_SOURCES
test_utils.cc
# E2E Tests (included in development builds)
e2e/canvas_selection_test.cc
e2e/framework_smoke_test.cc
e2e/dungeon_editor_smoke_test.cc
e2e/dungeon_editor_tests.cc
# Benchmarks
benchmarks/gfx_optimization_benchmarks.cc
)
if(YAZE_MINIMAL_BUILD)
# CI/Minimal build: use simplified test executable
# CI/Minimal build: use simplified test executable and base sources
add_executable(
yaze_test
yaze_test_ci.cc
# Emulator unit tests
unit/emu/apu_dsp_test.cc
unit/emu/spc700_reset_test.cc
test_editor.cc
test_editor.h
testing.h
test_utils.h
# Unit Tests
unit/core/asar_wrapper_test.cc
unit/core/hex_test.cc
unit/cli/resource_catalog_test.cc
unit/rom/rom_test.cc
unit/gfx/snes_tile_test.cc
unit/gfx/compression_test.cc
unit/gfx/snes_palette_test.cc
unit/gui/tile_selector_widget_test.cc
unit/gui/canvas_automation_api_test.cc
unit/zelda3/message_test.cc
unit/zelda3/overworld_test.cc
unit/zelda3/object_parser_test.cc
unit/zelda3/object_parser_structs_test.cc
unit/zelda3/sprite_builder_test.cc
unit/zelda3/sprite_position_test.cc
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
unit/zelda3/dungeon/room_object_encoding_test.cc
zelda3/dungeon/room_manipulation_test.cc
# CLI Services (for catalog serialization tests)
../src/cli/service/resources/resource_catalog.cc
# Integration Tests
integration/asar_integration_test.cc
integration/asar_rom_test.cc
integration/dungeon_editor_test.cc
integration/dungeon_editor_test.h
integration/dungeon_editor_v2_test.cc
integration/dungeon_editor_v2_test.h
integration/editor/tile16_editor_test.cc
integration/editor/editor_integration_test.cc
integration/editor/editor_integration_test.h
# E2E Tests (excluded in CI builds)
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
# Deprecated Tests (formerly legacy)
deprecated/comprehensive_integration_test.cc
deprecated/dungeon_integration_test.cc
# Integration Tests (Zelda3)
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
# Mock/Unit Tests for Zelda3
unit/zelda3/dungeon_object_renderer_mock_test.cc
unit/zelda3/dungeon_object_rendering_tests.cc
unit/zelda3/dungeon_room_test.cc
${YAZE_TEST_BASE_SOURCES}
)
else()
# Development build: use full-featured test executable
# Development build: use full-featured test executable and all sources
add_executable(
yaze_test
yaze_test.cc
# Emulator unit tests
unit/emu/apu_dsp_test.cc
unit/emu/spc700_reset_test.cc
test_editor.cc
test_editor.h
testing.h
test_utils.h
test_utils.cc
# Unit Tests
unit/core/asar_wrapper_test.cc
unit/core/hex_test.cc
unit/cli/resource_catalog_test.cc
unit/rom/rom_test.cc
unit/gfx/snes_tile_test.cc
unit/gfx/compression_test.cc
unit/gfx/snes_palette_test.cc
unit/gui/tile_selector_widget_test.cc
unit/gui/canvas_automation_api_test.cc
unit/zelda3/message_test.cc
unit/zelda3/overworld_test.cc
unit/zelda3/object_parser_test.cc
unit/zelda3/object_parser_structs_test.cc
unit/zelda3/sprite_builder_test.cc
unit/zelda3/sprite_position_test.cc
unit/zelda3/test_dungeon_objects.cc
unit/zelda3/dungeon_component_unit_test.cc
unit/zelda3/dungeon/room_object_encoding_test.cc
zelda3/dungeon/room_manipulation_test.cc
# CLI Services (for catalog serialization tests)
../src/cli/service/resources/resource_catalog.cc
# Integration Tests
integration/asar_integration_test.cc
integration/asar_rom_test.cc
integration/dungeon_editor_test.cc
integration/dungeon_editor_test.h
integration/dungeon_editor_v2_test.cc
integration/dungeon_editor_v2_test.h
integration/editor/tile16_editor_test.cc
integration/editor/editor_integration_test.cc
integration/editor/editor_integration_test.h
# E2E Tests (included in development builds)
e2e/canvas_selection_test.cc
e2e/framework_smoke_test.cc
e2e/dungeon_editor_smoke_test.cc
e2e/rom_dependent/e2e_rom_test.cc
e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc
# Deprecated Tests (formerly legacy)
deprecated/comprehensive_integration_test.cc
deprecated/dungeon_integration_test.cc
# Integration Tests (Zelda3)
integration/zelda3/overworld_integration_test.cc
integration/zelda3/dungeon_editor_system_integration_test.cc
integration/zelda3/dungeon_object_renderer_integration_test.cc
integration/zelda3/room_integration_test.cc
# Mock/Unit Tests for Zelda3
unit/zelda3/dungeon_object_renderer_mock_test.cc
unit/zelda3/dungeon_object_rendering_tests.cc
unit/zelda3/dungeon_room_test.cc
# Benchmarks
benchmarks/gfx_optimization_benchmarks.cc
${YAZE_TEST_BASE_SOURCES}
${YAZE_TEST_DEV_SOURCES}
)
endif()

View File

@@ -177,7 +177,8 @@ TEST_F(GraphicsOptimizationBenchmarks, BatchTextureUpdatePerformance) {
auto start = std::chrono::high_resolution_clock::now();
for (auto& bitmap : bitmaps) {
bitmap.UpdateTexture(nullptr); // Simulate renderer
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bitmap);
}
auto end = std::chrono::high_resolution_clock::now();
@@ -187,9 +188,10 @@ TEST_F(GraphicsOptimizationBenchmarks, BatchTextureUpdatePerformance) {
start = std::chrono::high_resolution_clock::now();
for (auto& bitmap : bitmaps) {
bitmap.QueueTextureUpdate(nullptr); // Queue for batch processing
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bitmap);
}
arena.ProcessBatchTextureUpdates(); // Process all at once
gfx::Arena::Get().ProcessTextureQueue(nullptr); // Process all at once
end = std::chrono::high_resolution_clock::now();
auto batch_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
@@ -413,9 +415,9 @@ TEST_F(GraphicsOptimizationBenchmarks, OverallPerformanceIntegration) {
start = std::chrono::high_resolution_clock::now();
for (auto& sheet : graphics_sheets) {
sheet.QueueTextureUpdate(nullptr);
arena.QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, &sheet);
}
arena.ProcessBatchTextureUpdates();
arena.ProcessTextureQueue(nullptr);
end = std::chrono::high_resolution_clock::now();
auto batch_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,7 @@
#endif
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/gui/widget_id_registry.h"
#include "app/gui/widgets/widget_id_registry.h"
#include "app/rom.h"
namespace yaze {

View File

@@ -128,8 +128,8 @@ TEST_F(AITilePlacementTest, DISABLED_FullAIControlLoop) {
gemini_config.api_key = api_key;
cli::GeminiAIService gemini_service(gemini_config);
cli::gui::GuiAutomationClient gui_client;
auto connect_status = gui_client.Connect("localhost", 50051);
cli::GuiAutomationClient gui_client("localhost:50051");
auto connect_status = gui_client.Connect();
if (!connect_status.ok()) {
GTEST_SKIP() << "GUI test harness not available: "
<< connect_status.message();

View File

@@ -163,7 +163,7 @@ TEST_F(GeminiVisionTest, ScreenshotCaptureIntegration) {
// Analyze the captured screenshot
auto response = service.GenerateMultimodalResponse(
screenshot_result->file_path.string(),
screenshot_result->file_path,
"What UI elements are visible in this screenshot? List them."
);
@@ -240,12 +240,4 @@ TEST_F(GeminiVisionTest, RateLimitHandling) {
} // namespace test
} // namespace yaze
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
std::cout << "\n=== Gemini Multimodal Vision Tests ===" << std::endl;
std::cout << "These tests require GEMINI_API_KEY environment variable." << std::endl;
std::cout << "Tests will be skipped if API key is not available.\n" << std::endl;
return RUN_ALL_TESTS();
}
// Note: main() is provided by yaze_test.cc for the unified test runner

View File

@@ -38,7 +38,9 @@ EditorIntegrationTest::~EditorIntegrationTest() {
}
absl::Status EditorIntegrationTest::Initialize() {
RETURN_IF_ERROR(core::CreateWindow(window_, SDL_WINDOW_RESIZABLE));
// Create renderer for test
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
RETURN_IF_ERROR(core::CreateWindow(window_, test_renderer_.get(), SDL_WINDOW_RESIZABLE));
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@@ -55,9 +57,9 @@ absl::Status EditorIntegrationTest::Initialize() {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// Initialize ImGui for SDL
ImGui_ImplSDL2_InitForSDLRenderer(
controller_.window(), yaze::core::Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_Init(yaze::core::Renderer::Get().renderer());
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer_->GetBackendRenderer());
ImGui_ImplSDL2_InitForSDLRenderer(controller_.window(), sdl_renderer);
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
// Register tests

View File

@@ -8,6 +8,7 @@
#include "app/rom.h"
#include "app/core/controller.h"
#include "app/core/window.h"
#include "app/gfx/backend/sdl2_renderer.h"
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
#include "imgui_test_engine/imgui_te_context.h"
@@ -81,6 +82,7 @@ class EditorIntegrationTest {
#endif
std::unique_ptr<Rom> test_rom_;
core::Window window_;
std::unique_ptr<gfx::SDL2Renderer> test_renderer_;
};
} // namespace test

View File

@@ -6,6 +6,8 @@
#include <gtest/gtest.h>
#include "app/rom.h"
#include "app/gfx/arena.h"
#include "app/gfx/backend/sdl2_renderer.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/tilemap.h"
#include "app/zelda3/overworld/overworld.h"
@@ -52,20 +54,22 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
auto palette = overworld_->current_area_palette();
tile16_blockset_ = std::make_unique<gfx::Tilemap>(
gfx::CreateTilemap(tile16_data, 0x80, 0x2000, 16,
gfx::CreateTilemap(nullptr, tile16_data, 0x80, 0x2000, 16,
zelda3::kNumTile16Individual, palette));
// Create graphics bitmap
current_gfx_bmp_ = std::make_unique<gfx::Bitmap>();
core::Renderer::Get().CreateAndRenderBitmap(0x80, 512, 0x40,
overworld_->current_graphics(),
*current_gfx_bmp_, palette);
current_gfx_bmp_->Create(0x80, 512, 0x40, overworld_->current_graphics());
current_gfx_bmp_->SetPalette(palette);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, current_gfx_bmp_.get());
// Create tile16 blockset bitmap
tile16_blockset_bmp_ = std::make_unique<gfx::Bitmap>();
core::Renderer::Get().CreateAndRenderBitmap(0x80, 0x2000, 0x08,
tile16_data,
*tile16_blockset_bmp_, palette);
tile16_blockset_bmp_->Create(0x80, 0x2000, 0x08, tile16_data);
tile16_blockset_bmp_->SetPalette(palette);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, tile16_blockset_bmp_.get());
// Initialize the tile16 editor
editor_ = std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
@@ -85,7 +89,9 @@ class Tile16EditorIntegrationTest : public ::testing::Test {
protected:
static void InitializeTestEnvironment() {
auto window_result = core::CreateWindow(test_window_, SDL_WINDOW_HIDDEN);
// Create renderer for test
test_renderer_ = std::make_unique<gfx::SDL2Renderer>();
auto window_result = core::CreateWindow(test_window_, test_renderer_.get(), SDL_WINDOW_HIDDEN);
if (window_result.ok()) {
window_initialized_ = true;
} else {
@@ -97,6 +103,7 @@ protected:
static bool window_initialized_;
static core::Window test_window_;
static std::unique_ptr<gfx::SDL2Renderer> test_renderer_;
bool rom_loaded_ = false;
std::unique_ptr<Rom> rom_;
@@ -111,6 +118,7 @@ protected:
// Static member definitions
bool Tile16EditorIntegrationTest::window_initialized_ = false;
core::Window Tile16EditorIntegrationTest::test_window_;
std::unique_ptr<gfx::SDL2Renderer> Tile16EditorIntegrationTest::test_renderer_;
// Basic validation tests (no ROM required)
TEST_F(Tile16EditorIntegrationTest, BasicValidation) {

View File

@@ -4,6 +4,7 @@
#include "app/core/controller.h"
#include "app/core/window.h"
#include "app/gfx/backend/sdl2_renderer.h"
#include "app/gui/style.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
@@ -56,7 +57,9 @@ void TestEditor::RegisterTests(ImGuiTestEngine* engine) {
int RunIntegrationTest() {
yaze::core::Controller controller;
yaze::core::Window window;
yaze::core::CreateWindow(window, SDL_WINDOW_RESIZABLE);
// Create renderer for test
auto test_renderer = std::make_unique<yaze::gfx::SDL2Renderer>();
yaze::core::CreateWindow(window, test_renderer.get(), SDL_WINDOW_RESIZABLE);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@@ -74,9 +77,9 @@ int RunIntegrationTest() {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// Initialize ImGui for SDL
ImGui_ImplSDL2_InitForSDLRenderer(
controller.window(), yaze::core::Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_Init(yaze::core::Renderer::Get().renderer());
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
ImGui_ImplSDL2_InitForSDLRenderer(controller.window(), sdl_renderer);
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
yaze::test::TestEditor test_editor;
#ifdef IMGUI_ENABLE_TEST_ENGINE

View File

@@ -231,7 +231,7 @@ TEST(RoomObjectEncodingTest, Type3RealWorldExample) {
// Expected: X=10, Y=15, ID=0xF99 (small chest)
EXPECT_EQ(decoded.x(), 10);
EXPECT_EQ(decoded.y(), 15);
EXPECT_EQ(decoded.id_, 0x99F);
EXPECT_EQ(decoded.id_, 0xF99);
}
// ============================================================================

View File

@@ -17,6 +17,7 @@
#include "imgui_test_engine/imgui_te_ui.h"
#include "app/core/window.h"
#include "app/core/controller.h"
#include "app/gfx/backend/sdl2_renderer.h"
#include "e2e/canvas_selection_test.h"
#include "e2e/framework_smoke_test.h"
#include "e2e/dungeon_editor_smoke_test.h"
@@ -242,10 +243,11 @@ int main(int argc, char* argv[]) {
if (config.enable_ui_tests) {
// Create a window
yaze::core::Window window;
yaze::core::CreateWindow(window, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
// Create renderer for test
auto test_renderer = std::make_unique<yaze::gfx::SDL2Renderer>();
yaze::core::CreateWindow(window, test_renderer.get(), SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
// Create a renderer
yaze::core::Renderer::Get().CreateRenderer(window.window_.get());
// Renderer is now owned by test
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
@@ -266,8 +268,9 @@ int main(int argc, char* argv[]) {
}
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), yaze::core::Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_Init(yaze::core::Renderer::Get().renderer());
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(test_renderer->GetBackendRenderer());
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), sdl_renderer);
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
// Setup test engine
ImGuiTestEngine* engine = ImGuiTestEngine_CreateContext();
@@ -319,9 +322,9 @@ int main(int argc, char* argv[]) {
// End the Dear ImGui frame
ImGui::Render();
yaze::core::Renderer::Get().Clear();
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), yaze::core::Renderer::Get().renderer());
yaze::core::Renderer::Get().Present();
test_renderer->Clear();
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), sdl_renderer);
test_renderer->Present();
// Update and Render additional Platform Windows
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {

View File

@@ -1,331 +0,0 @@
// test/zelda3/dungeon/room_object_encoding_test.cc
// Unit tests for Phase 1, Task 1.1: Object Encoding/Decoding
//
// These tests verify that the object encoding and decoding functions work
// correctly for all three object types (Type1, Type2, Type3) based on
// ZScream's proven implementation.
#include "app/zelda3/dungeon/room_object.h"
#include <gtest/gtest.h>
namespace yaze {
namespace zelda3 {
namespace {
// ============================================================================
// Object Type Detection Tests
// ============================================================================
TEST(RoomObjectEncodingTest, DetermineObjectTypeType1) {
// Type1: b1 < 0xFC, b3 < 0xF8
EXPECT_EQ(RoomObject::DetermineObjectType(0x28, 0x10), 1);
EXPECT_EQ(RoomObject::DetermineObjectType(0x50, 0x42), 1);
EXPECT_EQ(RoomObject::DetermineObjectType(0xFB, 0xF7), 1);
}
TEST(RoomObjectEncodingTest, DetermineObjectTypeType2) {
// Type2: b1 >= 0xFC, b3 < 0xF8
EXPECT_EQ(RoomObject::DetermineObjectType(0xFC, 0x42), 2);
EXPECT_EQ(RoomObject::DetermineObjectType(0xFD, 0x25), 2);
EXPECT_EQ(RoomObject::DetermineObjectType(0xFF, 0x00), 2);
}
TEST(RoomObjectEncodingTest, DetermineObjectTypeType3) {
// Type3: b3 >= 0xF8
EXPECT_EQ(RoomObject::DetermineObjectType(0x28, 0xF8), 3);
EXPECT_EQ(RoomObject::DetermineObjectType(0x50, 0xF9), 3);
EXPECT_EQ(RoomObject::DetermineObjectType(0xFC, 0xFF), 3);
}
// ============================================================================
// Type 1 Object Encoding/Decoding Tests
// ============================================================================
TEST(RoomObjectEncodingTest, Type1EncodeDecodeBasic) {
// Type1: xxxxxxss yyyyyyss iiiiiiii
// Example: Object ID 0x42, position (10, 20), size 3, layer 0
RoomObject obj(0x42, 10, 20, 3, 0);
// Encode
auto bytes = obj.EncodeObjectToBytes();
// Decode
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
EXPECT_EQ(decoded.size(), obj.size());
EXPECT_EQ(decoded.GetLayerValue(), obj.GetLayerValue());
}
TEST(RoomObjectEncodingTest, Type1MaxValues) {
// Test maximum valid values for Type1
// Constraints:
// - ID < 0xF8 (b3 >= 0xF8 triggers Type3 detection)
// - X < 63 OR Size < 12 (b1 >= 0xFC triggers Type2 detection)
// Safe max values: ID=0xF7, X=62, Y=63, Size=15
RoomObject obj(0xF7, 62, 63, 15, 2);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
EXPECT_EQ(decoded.size(), obj.size());
}
TEST(RoomObjectEncodingTest, Type1MinValues) {
// Test minimum values for Type1
RoomObject obj(0x00, 0, 0, 0, 0);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
EXPECT_EQ(decoded.size(), obj.size());
}
TEST(RoomObjectEncodingTest, Type1DifferentSizes) {
// Test all valid size values (0-15)
for (int size = 0; size <= 15; size++) {
RoomObject obj(0x30, 15, 20, size, 1);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
EXPECT_EQ(decoded.size(), size) << "Failed for size " << size;
}
}
TEST(RoomObjectEncodingTest, Type1RealWorldExample1) {
// Example from actual ROM: Wall object
// Bytes: 0x28 0x50 0x10
// Expected: X=10, Y=20, Size=0, ID=0x10
auto decoded = RoomObject::DecodeObjectFromBytes(0x28, 0x50, 0x10, 0);
EXPECT_EQ(decoded.x(), 10);
EXPECT_EQ(decoded.y(), 20);
EXPECT_EQ(decoded.size(), 0);
EXPECT_EQ(decoded.id_, 0x10);
}
TEST(RoomObjectEncodingTest, Type1RealWorldExample2) {
// Example: Ceiling object with size
// Correct bytes for X=10, Y=20, Size=3, ID=0x00: 0x28 0x53 0x00
auto decoded = RoomObject::DecodeObjectFromBytes(0x28, 0x53, 0x00, 0);
EXPECT_EQ(decoded.x(), 10);
EXPECT_EQ(decoded.y(), 20);
EXPECT_EQ(decoded.size(), 3);
EXPECT_EQ(decoded.id_, 0x00);
}
// ============================================================================
// Type 2 Object Encoding/Decoding Tests
// ============================================================================
TEST(RoomObjectEncodingTest, Type2EncodeDecodeBasic) {
// Type2: 111111xx xxxxyyyy yyiiiiii
// Example: Object ID 0x125, position (15, 30), size ignored, layer 1
RoomObject obj(0x125, 15, 30, 0, 1);
// Encode
auto bytes = obj.EncodeObjectToBytes();
// Verify b1 starts with 0xFC
EXPECT_GE(bytes.b1, 0xFC);
// Decode
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
EXPECT_EQ(decoded.GetLayerValue(), obj.GetLayerValue());
}
TEST(RoomObjectEncodingTest, Type2MaxValues) {
// Type2 allows larger position range, but has constraints:
// When Y=63 and ID=0x13F, b3 becomes 0xFF >= 0xF8, triggering Type3 detection
// Safe max: X=63, Y=59, ID=0x13F (b3 = ((59&0x03)<<6)|(0x3F) = 0xFF still!)
// Even safer: X=63, Y=63, ID=0x11F (b3 = (0xC0|0x1F) = 0xDF < 0xF8)
RoomObject obj(0x11F, 63, 63, 0, 2);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 2);
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
}
TEST(RoomObjectEncodingTest, Type2RealWorldExample) {
// Example: Large brazier (object 0x11C)
// Position (8, 12)
RoomObject obj(0x11C, 8, 12, 0, 0);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
EXPECT_EQ(decoded.id_, 0x11C);
EXPECT_EQ(decoded.x(), 8);
EXPECT_EQ(decoded.y(), 12);
}
// ============================================================================
// Type 3 Object Encoding/Decoding Tests
// ============================================================================
TEST(RoomObjectEncodingTest, Type3EncodeDecodeChest) {
// Type3: xxxxxxii yyyyyyii 11111iii
// Example: Small chest (0xF99), position (5, 10)
RoomObject obj(0xF99, 5, 10, 0, 0);
// Encode
auto bytes = obj.EncodeObjectToBytes();
// Verify b3 >= 0xF8
EXPECT_GE(bytes.b3, 0xF8);
// Decode
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
EXPECT_EQ(decoded.y(), obj.y());
}
TEST(RoomObjectEncodingTest, Type3EncodeDcodeBigChest) {
// Example: Big chest (0xFB1), position (15, 20)
RoomObject obj(0xFB1, 15, 20, 0, 1);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
EXPECT_EQ(decoded.id_, 0xFB1);
EXPECT_EQ(decoded.x(), 15);
EXPECT_EQ(decoded.y(), 20);
}
TEST(RoomObjectEncodingTest, Type3RealWorldExample) {
// Example from ROM: Chest at position (10, 15)
// Correct bytes for ID 0xF99: 0x29 0x3E 0xF9
auto decoded = RoomObject::DecodeObjectFromBytes(0x29, 0x3E, 0xF9, 0);
// Expected: X=10, Y=15, ID=0xF99 (small chest)
EXPECT_EQ(decoded.x(), 10);
EXPECT_EQ(decoded.y(), 15);
EXPECT_EQ(decoded.id_, 0xF99);
}
// ============================================================================
// Edge Cases and Special Values
// ============================================================================
TEST(RoomObjectEncodingTest, LayerPreservation) {
// Test that layer information is preserved through encode/decode
for (uint8_t layer = 0; layer <= 2; layer++) {
RoomObject obj(0x42, 10, 20, 3, layer);
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, layer);
EXPECT_EQ(decoded.GetLayerValue(), layer) << "Failed for layer " << (int)layer;
}
}
TEST(RoomObjectEncodingTest, BoundaryBetweenTypes) {
// Test boundary values between object types
// NOTE: Type1 can only go up to ID 0xF7 (b3 >= 0xF8 triggers Type3)
// Last safe Type1 object
RoomObject type1(0xF7, 10, 20, 3, 0);
auto bytes1 = type1.EncodeObjectToBytes();
auto decoded1 = RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
EXPECT_EQ(decoded1.id_, 0xF7);
// First Type2 object
RoomObject type2(0x100, 10, 20, 0, 0);
auto bytes2 = type2.EncodeObjectToBytes();
auto decoded2 = RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
EXPECT_EQ(decoded2.id_, 0x100);
// Last Type2 object
RoomObject type2_last(0x13F, 10, 20, 0, 0);
auto bytes2_last = type2_last.EncodeObjectToBytes();
auto decoded2_last = RoomObject::DecodeObjectFromBytes(bytes2_last.b1, bytes2_last.b2, bytes2_last.b3, 0);
EXPECT_EQ(decoded2_last.id_, 0x13F);
// Type3 objects (start at 0xF80)
RoomObject type3(0xF99, 10, 20, 0, 0);
auto bytes3 = type3.EncodeObjectToBytes();
auto decoded3 = RoomObject::DecodeObjectFromBytes(bytes3.b1, bytes3.b2, bytes3.b3, 0);
EXPECT_EQ(decoded3.id_, 0xF99);
}
TEST(RoomObjectEncodingTest, ZeroPosition) {
// Test objects at position (0, 0)
RoomObject type1(0x10, 0, 0, 0, 0);
auto bytes1 = type1.EncodeObjectToBytes();
auto decoded1 = RoomObject::DecodeObjectFromBytes(bytes1.b1, bytes1.b2, bytes1.b3, 0);
EXPECT_EQ(decoded1.x(), 0);
EXPECT_EQ(decoded1.y(), 0);
RoomObject type2(0x110, 0, 0, 0, 0);
auto bytes2 = type2.EncodeObjectToBytes();
auto decoded2 = RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
EXPECT_EQ(decoded2.x(), 0);
EXPECT_EQ(decoded2.y(), 0);
}
// ============================================================================
// Batch Tests with Multiple Objects
// ============================================================================
TEST(RoomObjectEncodingTest, MultipleObjectsRoundTrip) {
// Test encoding/decoding a batch of different objects
std::vector<RoomObject> objects;
// Add various objects
objects.emplace_back(0x10, 5, 10, 2, 0); // Type1
objects.emplace_back(0x42, 15, 20, 5, 1); // Type1
objects.emplace_back(0x110, 8, 12, 0, 0); // Type2
objects.emplace_back(0x125, 25, 30, 0, 1); // Type2
objects.emplace_back(0xF99, 10, 15, 0, 0); // Type3 (chest)
objects.emplace_back(0xFB1, 20, 25, 0, 2); // Type3 (big chest)
for (size_t i = 0; i < objects.size(); i++) {
auto& obj = objects[i];
auto bytes = obj.EncodeObjectToBytes();
auto decoded = RoomObject::DecodeObjectFromBytes(
bytes.b1, bytes.b2, bytes.b3, obj.GetLayerValue());
EXPECT_EQ(decoded.id_, obj.id_) << "Failed at index " << i;
EXPECT_EQ(decoded.x(), obj.x()) << "Failed at index " << i;
EXPECT_EQ(decoded.y(), obj.y()) << "Failed at index " << i;
if (obj.id_ < 0x100) { // Type1 objects have size
EXPECT_EQ(decoded.size(), obj.size()) << "Failed at index " << i;
}
}
}
} // namespace
} // namespace zelda3
} // namespace yaze