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:
@@ -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();
|
||||
243
test/integration/ai/test_gemini_vision.cc
Normal file
243
test/integration/ai/test_gemini_vision.cc
Normal file
@@ -0,0 +1,243 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "cli/service/ai/gemini_ai_service.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "app/core/service/screenshot_utils.h"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
class GeminiVisionTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Check if GEMINI_API_KEY is set
|
||||
const char* api_key = std::getenv("GEMINI_API_KEY");
|
||||
if (!api_key || std::string(api_key).empty()) {
|
||||
GTEST_SKIP() << "GEMINI_API_KEY not set. Skipping multimodal tests.";
|
||||
}
|
||||
|
||||
api_key_ = api_key;
|
||||
|
||||
// Create test data directory
|
||||
test_dir_ = std::filesystem::temp_directory_path() / "yaze_multimodal_test";
|
||||
std::filesystem::create_directories(test_dir_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Clean up test directory
|
||||
if (std::filesystem::exists(test_dir_)) {
|
||||
std::filesystem::remove_all(test_dir_);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Create a simple test image (16x16 PNG)
|
||||
std::filesystem::path CreateTestImage() {
|
||||
auto image_path = test_dir_ / "test_image.png";
|
||||
|
||||
// Create a minimal PNG file (16x16 red square)
|
||||
// PNG signature + IHDR + IDAT + IEND
|
||||
const unsigned char png_data[] = {
|
||||
// PNG signature
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||
// IHDR chunk
|
||||
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x91, 0x68,
|
||||
0x36,
|
||||
// IDAT chunk (minimal data)
|
||||
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54,
|
||||
0x08, 0x99, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00,
|
||||
0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB4,
|
||||
// IEND chunk
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44,
|
||||
0xAE, 0x42, 0x60, 0x82
|
||||
};
|
||||
|
||||
std::ofstream file(image_path, std::ios::binary);
|
||||
file.write(reinterpret_cast<const char*>(png_data), sizeof(png_data));
|
||||
file.close();
|
||||
|
||||
return image_path;
|
||||
}
|
||||
|
||||
std::string api_key_;
|
||||
std::filesystem::path test_dir_;
|
||||
};
|
||||
|
||||
TEST_F(GeminiVisionTest, BasicImageAnalysis) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash"; // Vision-capable model
|
||||
config.verbose = false;
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
// Create test image
|
||||
auto image_path = CreateTestImage();
|
||||
ASSERT_TRUE(std::filesystem::exists(image_path));
|
||||
|
||||
// Send multimodal request
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image in one sentence."
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
std::cout << "Vision API response: " << response->text_response << std::endl;
|
||||
}
|
||||
|
||||
TEST_F(GeminiVisionTest, ImageWithSpecificPrompt) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
// Ask specific question about the image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"What color is the dominant color in this image? Answer with just the color name."
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
// Response should mention "red" since we created a red square
|
||||
std::string response_lower = response->text_response;
|
||||
std::transform(response_lower.begin(), response_lower.end(),
|
||||
response_lower.begin(), ::tolower);
|
||||
EXPECT_TRUE(response_lower.find("red") != std::string::npos ||
|
||||
response_lower.find("pink") != std::string::npos)
|
||||
<< "Expected color 'red' or 'pink' in response: " << response->text_response;
|
||||
}
|
||||
|
||||
TEST_F(GeminiVisionTest, InvalidImagePath) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
// Try with non-existent image
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
"/nonexistent/image.png",
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
EXPECT_FALSE(response.ok());
|
||||
EXPECT_TRUE(absl::IsNotFound(response.status()) ||
|
||||
absl::IsInternal(response.status()));
|
||||
}
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
// Integration test with screenshot capture
|
||||
TEST_F(GeminiVisionTest, ScreenshotCaptureIntegration) {
|
||||
// Note: This test requires a running YAZE instance with gRPC test harness
|
||||
// Skip if we can't connect
|
||||
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
// Attempt to capture a screenshot
|
||||
auto screenshot_result = yaze::test::CaptureHarnessScreenshot(
|
||||
(test_dir_ / "screenshot.png").string());
|
||||
|
||||
if (!screenshot_result.ok()) {
|
||||
GTEST_SKIP() << "Screenshot capture failed (YAZE may not be running): "
|
||||
<< screenshot_result.status().message();
|
||||
}
|
||||
|
||||
// Analyze the captured screenshot
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
screenshot_result->file_path,
|
||||
"What UI elements are visible in this screenshot? List them."
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
|
||||
std::cout << "Screenshot analysis: " << response->text_response << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Performance test
|
||||
TEST_F(GeminiVisionTest, MultipleRequestsSequential) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
// Make 3 sequential requests
|
||||
const int num_requests = 3;
|
||||
for (int i = 0; i < num_requests; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
absl::StrCat("Request ", i + 1, ": Describe this image briefly.")
|
||||
);
|
||||
|
||||
ASSERT_TRUE(response.ok()) << "Request " << i + 1 << " failed: "
|
||||
<< response.status().message();
|
||||
EXPECT_FALSE(response->text_response.empty());
|
||||
}
|
||||
}
|
||||
|
||||
// Rate limiting test (should handle gracefully)
|
||||
TEST_F(GeminiVisionTest, RateLimitHandling) {
|
||||
cli::GeminiConfig config;
|
||||
config.api_key = api_key_;
|
||||
config.model = "gemini-2.5-flash";
|
||||
config.verbose = false;
|
||||
|
||||
cli::GeminiAIService service(config);
|
||||
|
||||
auto image_path = CreateTestImage();
|
||||
|
||||
// Make many rapid requests (may hit rate limit)
|
||||
int successful = 0;
|
||||
int rate_limited = 0;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
auto response = service.GenerateMultimodalResponse(
|
||||
image_path.string(),
|
||||
"Describe this image."
|
||||
);
|
||||
|
||||
if (response.ok()) {
|
||||
successful++;
|
||||
} else if (absl::IsResourceExhausted(response.status()) ||
|
||||
response.status().message().find("429") != std::string::npos) {
|
||||
rate_limited++;
|
||||
}
|
||||
}
|
||||
|
||||
// At least some requests should succeed
|
||||
EXPECT_GT(successful, 0) << "No successful requests out of 10";
|
||||
|
||||
// If we hit rate limits, that's expected behavior (not a failure)
|
||||
if (rate_limited > 0) {
|
||||
std::cout << "Note: Hit rate limit on " << rate_limited << " out of 10 requests (expected)" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
// Note: main() is provided by yaze_test.cc for the unified test runner
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
659
test/integration/zelda3/dungeon_object_rendering_tests.cc
Normal file
659
test/integration/zelda3/dungeon_object_rendering_tests.cc
Normal file
@@ -0,0 +1,659 @@
|
||||
#include "app/zelda3/dungeon/object_renderer.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "app/zelda3/dungeon/room_layout.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "testing.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* @brief Advanced tests for actual dungeon object rendering scenarios
|
||||
*
|
||||
* These tests focus on real-world dungeon editing scenarios including:
|
||||
* - Complex room layouts with multiple object types
|
||||
* - Object interaction and collision detection
|
||||
* - Performance with realistic dungeon configurations
|
||||
* - Edge cases in dungeon editing workflows
|
||||
*/
|
||||
class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
BoundRomTest::SetUp();
|
||||
|
||||
// Setup palette data before scenarios require it
|
||||
SetupTestPalettes();
|
||||
|
||||
// Create renderer
|
||||
renderer_ = std::make_unique<zelda3::ObjectRenderer>(rom());
|
||||
|
||||
// Setup realistic dungeon scenarios
|
||||
SetupDungeonScenarios();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
renderer_.reset();
|
||||
BoundRomTest::TearDown();
|
||||
}
|
||||
|
||||
std::unique_ptr<zelda3::ObjectRenderer> renderer_;
|
||||
|
||||
struct DungeonScenario {
|
||||
std::string name;
|
||||
std::vector<zelda3::RoomObject> objects;
|
||||
zelda3::RoomLayout layout;
|
||||
gfx::SnesPalette palette;
|
||||
int expected_width;
|
||||
int expected_height;
|
||||
};
|
||||
|
||||
std::vector<DungeonScenario> scenarios_;
|
||||
std::vector<gfx::SnesPalette> test_palettes_;
|
||||
|
||||
private:
|
||||
void SetupDungeonScenarios() {
|
||||
// Scenario 1: Empty room with basic walls
|
||||
CreateEmptyRoomScenario();
|
||||
|
||||
// Scenario 2: Room with multiple object types
|
||||
CreateMultiObjectScenario();
|
||||
|
||||
// Scenario 3: Complex room with all subtypes
|
||||
CreateComplexRoomScenario();
|
||||
|
||||
// Scenario 4: Large room with many objects
|
||||
CreateLargeRoomScenario();
|
||||
|
||||
// Scenario 5: Boss room configuration
|
||||
CreateBossRoomScenario();
|
||||
|
||||
// Scenario 6: Puzzle room with interactive elements
|
||||
CreatePuzzleRoomScenario();
|
||||
}
|
||||
|
||||
void SetupTestPalettes() {
|
||||
// Create different palettes for different dungeon themes
|
||||
CreateDungeonPalette(); // Standard dungeon
|
||||
CreateIcePalacePalette(); // Ice Palace theme
|
||||
CreateDesertPalacePalette(); // Desert Palace theme
|
||||
CreateDarkPalacePalette(); // Palace of Darkness theme
|
||||
CreateBossRoomPalette(); // Boss room theme
|
||||
}
|
||||
|
||||
void CreateEmptyRoomScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Empty Room";
|
||||
|
||||
// Create basic wall objects around the perimeter
|
||||
for (int x = 0; x < 16; x++) {
|
||||
// Top and bottom walls
|
||||
scenario.objects.emplace_back(0x10, x, 0, 0x12, 0); // Top wall
|
||||
scenario.objects.emplace_back(0x10, x, 10, 0x12, 0); // Bottom wall
|
||||
}
|
||||
|
||||
for (int y = 1; y < 10; y++) {
|
||||
// Left and right walls
|
||||
scenario.objects.emplace_back(0x11, 0, y, 0x12, 0); // Left wall
|
||||
scenario.objects.emplace_back(0x11, 15, y, 0x12, 0); // Right wall
|
||||
}
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[0]; // Dungeon palette
|
||||
scenario.expected_width = 256;
|
||||
scenario.expected_height = 176;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreateMultiObjectScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Multi-Object Room";
|
||||
|
||||
// Walls
|
||||
scenario.objects.emplace_back(0x10, 0, 0, 0x12, 0); // Wall
|
||||
scenario.objects.emplace_back(0x10, 1, 0, 0x12, 0); // Wall
|
||||
scenario.objects.emplace_back(0x10, 0, 1, 0x12, 0); // Wall
|
||||
|
||||
// Decorative objects
|
||||
scenario.objects.emplace_back(0x20, 5, 5, 0x12, 0); // Statue
|
||||
scenario.objects.emplace_back(0x21, 8, 7, 0x12, 0); // Pot
|
||||
|
||||
// Interactive objects
|
||||
scenario.objects.emplace_back(0xF9, 10, 8, 0x12, 0); // Chest
|
||||
scenario.objects.emplace_back(0x13, 3, 3, 0x12, 0); // Stairs
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[0];
|
||||
scenario.expected_width = 256;
|
||||
scenario.expected_height = 176;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreateComplexRoomScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Complex Room";
|
||||
|
||||
// Subtype 1 objects (basic)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
scenario.objects.emplace_back(i, (i % 8) * 2, (i / 8) * 2, 0x12, 0);
|
||||
}
|
||||
|
||||
// Subtype 2 objects (complex)
|
||||
for (int i = 0; i < 5; i++) {
|
||||
scenario.objects.emplace_back(0x100 + i, (i % 4) * 3, (i / 4) * 3, 0x12, 0);
|
||||
}
|
||||
|
||||
// Subtype 3 objects (special)
|
||||
for (int i = 0; i < 3; i++) {
|
||||
scenario.objects.emplace_back(0x200 + i, (i % 3) * 4, (i / 3) * 4, 0x12, 0);
|
||||
}
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[1]; // Ice Palace palette
|
||||
scenario.expected_width = 256;
|
||||
scenario.expected_height = 176;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreateLargeRoomScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Large Room";
|
||||
|
||||
// Create a room with many objects (stress test scenario)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = (i % 16) * 2;
|
||||
int y = (i / 16) * 2;
|
||||
int object_id = (i % 50) + 0x10; // Mix of different object types
|
||||
|
||||
scenario.objects.emplace_back(object_id, x, y, 0x12, i % 3);
|
||||
}
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[2]; // Desert Palace palette
|
||||
scenario.expected_width = 512;
|
||||
scenario.expected_height = 256;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreateBossRoomScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Boss Room";
|
||||
|
||||
// Boss room typically has special objects
|
||||
scenario.objects.emplace_back(0x30, 7, 4, 0x12, 0); // Boss platform
|
||||
scenario.objects.emplace_back(0x31, 7, 5, 0x12, 0); // Boss platform
|
||||
scenario.objects.emplace_back(0x32, 8, 4, 0x12, 0); // Boss platform
|
||||
scenario.objects.emplace_back(0x33, 8, 5, 0x12, 0); // Boss platform
|
||||
|
||||
// Walls around the room
|
||||
for (int x = 0; x < 16; x++) {
|
||||
scenario.objects.emplace_back(0x10, x, 0, 0x12, 0);
|
||||
scenario.objects.emplace_back(0x10, x, 10, 0x12, 0);
|
||||
}
|
||||
|
||||
for (int y = 1; y < 10; y++) {
|
||||
scenario.objects.emplace_back(0x11, 0, y, 0x12, 0);
|
||||
scenario.objects.emplace_back(0x11, 15, y, 0x12, 0);
|
||||
}
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[4]; // Boss room palette
|
||||
scenario.expected_width = 256;
|
||||
scenario.expected_height = 176;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreatePuzzleRoomScenario() {
|
||||
DungeonScenario scenario;
|
||||
scenario.name = "Puzzle Room";
|
||||
|
||||
// Puzzle rooms have specific interactive elements
|
||||
scenario.objects.emplace_back(0x40, 4, 4, 0x12, 0); // Switch
|
||||
scenario.objects.emplace_back(0x41, 8, 6, 0x12, 0); // Block
|
||||
scenario.objects.emplace_back(0x42, 6, 8, 0x12, 0); // Pressure plate
|
||||
|
||||
// Chests for puzzle rewards
|
||||
scenario.objects.emplace_back(0xF9, 2, 2, 0x12, 0); // Small chest
|
||||
scenario.objects.emplace_back(0xFA, 12, 2, 0x12, 0); // Large chest
|
||||
|
||||
// Decorative elements
|
||||
scenario.objects.emplace_back(0x50, 1, 5, 0x12, 0); // Torch
|
||||
scenario.objects.emplace_back(0x51, 14, 5, 0x12, 0); // Torch
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : scenario.objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
scenario.palette = test_palettes_[3]; // Dark Palace palette
|
||||
scenario.expected_width = 256;
|
||||
scenario.expected_height = 176;
|
||||
|
||||
scenarios_.push_back(scenario);
|
||||
}
|
||||
|
||||
void CreateDungeonPalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Standard dungeon colors (grays and browns)
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
||||
palette.AddColor(gfx::SnesColor(0x20, 0x20, 0x20)); // Dark gray
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x40, 0x40)); // Medium gray
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x60)); // Light gray
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x80)); // Very light gray
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0xA0, 0xA0)); // Almost white
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0xC0, 0xC0)); // White
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x20)); // Brown
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x40)); // Light brown
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0x40)); // Green
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x60, 0x80)); // Blue
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x80)); // Purple
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x40)); // Yellow
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x40)); // Red
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x80, 0x80)); // Cyan
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
||||
test_palettes_.push_back(palette);
|
||||
}
|
||||
|
||||
void CreateIcePalacePalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Ice Palace colors (blues and whites)
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
||||
palette.AddColor(gfx::SnesColor(0x20, 0x40, 0x80)); // Dark blue
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x60, 0xA0)); // Medium blue
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0xC0)); // Light blue
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0xA0, 0xE0)); // Very light blue
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0xC0, 0xFF)); // Pale blue
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0xE0, 0xFF)); // Almost white
|
||||
palette.AddColor(gfx::SnesColor(0xE0, 0xF0, 0xFF)); // White
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x80, 0xC0)); // Ice blue
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0xA0, 0xE0)); // Light ice
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0xC0, 0xFF)); // Pale ice
|
||||
palette.AddColor(gfx::SnesColor(0x20, 0x60, 0xA0)); // Deep ice
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x40, 0x80)); // Dark ice
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0xA0)); // Gray-blue
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0xA0, 0xC0)); // Light gray-blue
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
||||
test_palettes_.push_back(palette);
|
||||
}
|
||||
|
||||
void CreateDesertPalacePalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Desert Palace colors (yellows, oranges, and browns)
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x20, 0x00)); // Dark brown
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x40, 0x20)); // Medium brown
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x40)); // Light brown
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0x60)); // Very light brown
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0x80)); // Tan
|
||||
palette.AddColor(gfx::SnesColor(0xE0, 0xC0, 0xA0)); // Light tan
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xE0, 0xC0)); // Cream
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x00)); // Orange
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x20)); // Light orange
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0x80, 0x40)); // Pale orange
|
||||
palette.AddColor(gfx::SnesColor(0xE0, 0xA0, 0x60)); // Very pale orange
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x20)); // Olive
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x40)); // Light olive
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0xA0, 0x60)); // Very light olive
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
||||
test_palettes_.push_back(palette);
|
||||
}
|
||||
|
||||
void CreateDarkPalacePalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Palace of Darkness colors (dark purples and grays)
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
||||
palette.AddColor(gfx::SnesColor(0x20, 0x00, 0x20)); // Dark purple
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x20, 0x40)); // Medium purple
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x40, 0x60)); // Light purple
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x80)); // Very light purple
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0xA0)); // Pale purple
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0xC0)); // Almost white purple
|
||||
palette.AddColor(gfx::SnesColor(0x10, 0x10, 0x10)); // Very dark gray
|
||||
palette.AddColor(gfx::SnesColor(0x30, 0x30, 0x30)); // Dark gray
|
||||
palette.AddColor(gfx::SnesColor(0x50, 0x50, 0x50)); // Medium gray
|
||||
palette.AddColor(gfx::SnesColor(0x70, 0x70, 0x70)); // Light gray
|
||||
palette.AddColor(gfx::SnesColor(0x90, 0x90, 0x90)); // Very light gray
|
||||
palette.AddColor(gfx::SnesColor(0xB0, 0xB0, 0xB0)); // Almost white
|
||||
palette.AddColor(gfx::SnesColor(0xD0, 0xD0, 0xD0)); // Off white
|
||||
palette.AddColor(gfx::SnesColor(0xF0, 0xF0, 0xF0)); // Near white
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
||||
test_palettes_.push_back(palette);
|
||||
}
|
||||
|
||||
void CreateBossRoomPalette() {
|
||||
gfx::SnesPalette palette;
|
||||
// Boss room colors (dramatic reds, golds, and blacks)
|
||||
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x00, 0x00)); // Dark red
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x20, 0x00)); // Dark red-orange
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x00)); // Red-orange
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x20)); // Orange
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0x80, 0x40)); // Light orange
|
||||
palette.AddColor(gfx::SnesColor(0xE0, 0xA0, 0x60)); // Very light orange
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x00)); // Dark gold
|
||||
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0x20)); // Gold
|
||||
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0x40)); // Light gold
|
||||
palette.AddColor(gfx::SnesColor(0xE0, 0xC0, 0x60)); // Very light gold
|
||||
palette.AddColor(gfx::SnesColor(0x20, 0x20, 0x20)); // Dark gray
|
||||
palette.AddColor(gfx::SnesColor(0x40, 0x40, 0x40)); // Medium gray
|
||||
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x60)); // Light gray
|
||||
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x80)); // Very light gray
|
||||
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
||||
test_palettes_.push_back(palette);
|
||||
}
|
||||
};
|
||||
|
||||
// Scenario-based rendering tests
|
||||
TEST_F(DungeonObjectRenderingTests, EmptyRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 1) << "Empty room scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[0];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Empty room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Empty room bitmap not active";
|
||||
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Empty room width too small";
|
||||
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Empty room height too small";
|
||||
|
||||
// Verify wall objects are rendered
|
||||
EXPECT_GT(bitmap.size(), 0) << "Empty room bitmap has no content";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, MultiObjectRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 2) << "Multi-object scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[1];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Multi-object room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Multi-object room bitmap not active";
|
||||
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Multi-object room width too small";
|
||||
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Multi-object room height too small";
|
||||
|
||||
// Verify different object types are rendered
|
||||
EXPECT_GT(bitmap.size(), 0) << "Multi-object room bitmap has no content";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, ComplexRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 3) << "Complex room scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[2];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Complex room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Complex room bitmap not active";
|
||||
EXPECT_GT(bitmap.width(), 0) << "Complex room width not positive";
|
||||
EXPECT_GT(bitmap.height(), 0) << "Complex room height not positive";
|
||||
|
||||
// Verify all subtypes are rendered correctly
|
||||
EXPECT_GT(bitmap.size(), 0) << "Complex room bitmap has no content";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, LargeRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 4) << "Large room scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[3];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Large room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Large room bitmap not active";
|
||||
EXPECT_GT(bitmap.width(), 0) << "Large room width not positive";
|
||||
EXPECT_GT(bitmap.height(), 0) << "Large room height not positive";
|
||||
|
||||
// Verify performance with many objects
|
||||
auto stats = renderer_->GetPerformanceStats();
|
||||
EXPECT_GT(stats.objects_rendered, 0) << "Large room objects not rendered";
|
||||
EXPECT_GT(stats.tiles_rendered, 0) << "Large room tiles not rendered";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, BossRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 5) << "Boss room scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[4];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Boss room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Boss room bitmap not active";
|
||||
EXPECT_GT(bitmap.width(), 0) << "Boss room width not positive";
|
||||
EXPECT_GT(bitmap.height(), 0) << "Boss room height not positive";
|
||||
|
||||
// Verify boss-specific objects are rendered
|
||||
EXPECT_GT(bitmap.size(), 0) << "Boss room bitmap has no content";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, PuzzleRoomRendering) {
|
||||
ASSERT_GE(scenarios_.size(), 6) << "Puzzle room scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[5];
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
|
||||
ASSERT_TRUE(result.ok()) << "Puzzle room rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Puzzle room bitmap not active";
|
||||
EXPECT_GT(bitmap.width(), 0) << "Puzzle room width not positive";
|
||||
EXPECT_GT(bitmap.height(), 0) << "Puzzle room height not positive";
|
||||
|
||||
// Verify puzzle elements are rendered
|
||||
EXPECT_GT(bitmap.size(), 0) << "Puzzle room bitmap has no content";
|
||||
}
|
||||
|
||||
// Palette-specific rendering tests
|
||||
TEST_F(DungeonObjectRenderingTests, PaletteConsistency) {
|
||||
ASSERT_GE(scenarios_.size(), 1) << "Test scenario not available";
|
||||
|
||||
const auto& scenario = scenarios_[0];
|
||||
|
||||
// Render with different palettes
|
||||
for (size_t i = 0; i < test_palettes_.size(); i++) {
|
||||
auto result = renderer_->RenderObjects(scenario.objects, test_palettes_[i]);
|
||||
ASSERT_TRUE(result.ok()) << "Palette " << i << " rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Palette " << i << " bitmap not active";
|
||||
EXPECT_GT(bitmap.size(), 0) << "Palette " << i << " bitmap has no content";
|
||||
}
|
||||
}
|
||||
|
||||
// Performance tests with realistic scenarios
|
||||
TEST_F(DungeonObjectRenderingTests, ScenarioPerformanceBenchmark) {
|
||||
const int iterations = 10;
|
||||
|
||||
for (const auto& scenario : scenarios_) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
ASSERT_TRUE(result.ok()) << "Scenario " << scenario.name
|
||||
<< " rendering failed: " << result.status().message();
|
||||
}
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
// Each scenario should render within reasonable time
|
||||
EXPECT_LT(duration.count(), 5000) << "Scenario " << scenario.name
|
||||
<< " performance below expectations: "
|
||||
<< duration.count() << "ms";
|
||||
}
|
||||
}
|
||||
|
||||
// Memory usage tests with realistic scenarios
|
||||
TEST_F(DungeonObjectRenderingTests, ScenarioMemoryUsage) {
|
||||
size_t initial_memory = renderer_->GetMemoryUsage();
|
||||
|
||||
// Render all scenarios multiple times
|
||||
for (int round = 0; round < 3; round++) {
|
||||
for (const auto& scenario : scenarios_) {
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
ASSERT_TRUE(result.ok()) << "Scenario memory test failed: " << result.status().message();
|
||||
}
|
||||
}
|
||||
|
||||
size_t final_memory = renderer_->GetMemoryUsage();
|
||||
|
||||
// Memory usage should not grow excessively
|
||||
EXPECT_LT(final_memory, initial_memory * 5) << "Memory leak detected in scenario tests: "
|
||||
<< initial_memory << " -> " << final_memory;
|
||||
|
||||
// Clear cache and verify memory reduction
|
||||
renderer_->ClearCache();
|
||||
size_t memory_after_clear = renderer_->GetMemoryUsage();
|
||||
EXPECT_LE(memory_after_clear, final_memory) << "Cache clear did not reduce memory usage";
|
||||
}
|
||||
|
||||
// Object interaction tests
|
||||
TEST_F(DungeonObjectRenderingTests, ObjectOverlapHandling) {
|
||||
// Create objects that overlap
|
||||
std::vector<zelda3::RoomObject> overlapping_objects;
|
||||
|
||||
// Two objects at the same position
|
||||
overlapping_objects.emplace_back(0x10, 5, 5, 0x12, 0);
|
||||
overlapping_objects.emplace_back(0x20, 5, 5, 0x12, 1); // Different layer
|
||||
|
||||
// Objects that partially overlap
|
||||
overlapping_objects.emplace_back(0x30, 3, 3, 0x12, 0);
|
||||
overlapping_objects.emplace_back(0x31, 4, 4, 0x12, 0);
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : overlapping_objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
auto result = renderer_->RenderObjects(overlapping_objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Overlapping objects rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Overlapping objects bitmap not active";
|
||||
EXPECT_GT(bitmap.size(), 0) << "Overlapping objects bitmap has no content";
|
||||
}
|
||||
|
||||
TEST_F(DungeonObjectRenderingTests, LayerRenderingOrder) {
|
||||
// Create objects on different layers
|
||||
std::vector<zelda3::RoomObject> layered_objects;
|
||||
|
||||
// Background layer (0)
|
||||
layered_objects.emplace_back(0x10, 5, 5, 0x12, 0);
|
||||
|
||||
// Middle layer (1)
|
||||
layered_objects.emplace_back(0x20, 5, 5, 0x12, 1);
|
||||
|
||||
// Foreground layer (2)
|
||||
layered_objects.emplace_back(0x30, 5, 5, 0x12, 2);
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : layered_objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
auto result = renderer_->RenderObjects(layered_objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Layered objects rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Layered objects bitmap not active";
|
||||
EXPECT_GT(bitmap.size(), 0) << "Layered objects bitmap has no content";
|
||||
}
|
||||
|
||||
// Cache efficiency with realistic scenarios
|
||||
TEST_F(DungeonObjectRenderingTests, ScenarioCacheEfficiency) {
|
||||
renderer_->ClearCache();
|
||||
|
||||
// Render scenarios multiple times to test cache
|
||||
for (int round = 0; round < 5; round++) {
|
||||
for (const auto& scenario : scenarios_) {
|
||||
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
||||
ASSERT_TRUE(result.ok()) << "Cache efficiency test failed: " << result.status().message();
|
||||
}
|
||||
}
|
||||
|
||||
auto stats = renderer_->GetPerformanceStats();
|
||||
|
||||
// Cache hit rate should be high after multiple renders
|
||||
EXPECT_GE(stats.cache_hits, 0) << "Cache hits unexpectedly negative";
|
||||
EXPECT_GE(stats.cache_hit_rate(), 0.0) << "Cache hit rate negative: " << stats.cache_hit_rate();
|
||||
}
|
||||
|
||||
// Edge cases in dungeon editing
|
||||
TEST_F(DungeonObjectRenderingTests, BoundaryObjectPlacement) {
|
||||
// Create objects at room boundaries
|
||||
std::vector<zelda3::RoomObject> boundary_objects;
|
||||
|
||||
// Objects at exact boundaries
|
||||
boundary_objects.emplace_back(0x10, 0, 0, 0x12, 0); // Top-left
|
||||
boundary_objects.emplace_back(0x11, 15, 0, 0x12, 0); // Top-right
|
||||
boundary_objects.emplace_back(0x12, 0, 10, 0x12, 0); // Bottom-left
|
||||
boundary_objects.emplace_back(0x13, 15, 10, 0x12, 0); // Bottom-right
|
||||
|
||||
// Objects just outside boundaries (should be handled gracefully)
|
||||
boundary_objects.emplace_back(0x14, -1, 5, 0x12, 0); // Left edge
|
||||
boundary_objects.emplace_back(0x15, 16, 5, 0x12, 0); // Right edge
|
||||
boundary_objects.emplace_back(0x16, 5, -1, 0x12, 0); // Top edge
|
||||
boundary_objects.emplace_back(0x17, 5, 11, 0x12, 0); // Bottom edge
|
||||
|
||||
// Set ROM references and load tiles
|
||||
for (auto& obj : boundary_objects) {
|
||||
obj.set_rom(rom());
|
||||
obj.EnsureTilesLoaded();
|
||||
}
|
||||
|
||||
auto result = renderer_->RenderObjects(boundary_objects, test_palettes_[0]);
|
||||
ASSERT_TRUE(result.ok()) << "Boundary objects rendering failed: " << result.status().message();
|
||||
|
||||
auto bitmap = std::move(result.value());
|
||||
EXPECT_TRUE(bitmap.is_active()) << "Boundary objects bitmap not active";
|
||||
EXPECT_GT(bitmap.size(), 0) << "Boundary objects bitmap has no content";
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
34
test/integration/zelda3/dungeon_room_test.cc
Normal file
34
test/integration/zelda3/dungeon_room_test.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
class DungeonRoomTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests on Linux for automated github builds
|
||||
#if defined(__linux__)
|
||||
GTEST_SKIP();
|
||||
#else
|
||||
if (!rom_.LoadFromFile("./zelda3.sfc").ok()) {
|
||||
GTEST_SKIP_("Failed to load test ROM");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void TearDown() override {}
|
||||
|
||||
Rom rom_;
|
||||
};
|
||||
|
||||
TEST_F(DungeonRoomTest, SingleRoomLoadOk) {
|
||||
zelda3::Room test_room(/*room_id=*/0, &rom_);
|
||||
|
||||
test_room = zelda3::LoadRoomFromRom(&rom_, /*room_id=*/0);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
212
test/integration/zelda3/message_test.cc
Normal file
212
test/integration/zelda3/message_test.cc
Normal file
@@ -0,0 +1,212 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "app/editor/message/message_data.h"
|
||||
#include "app/editor/message/message_editor.h"
|
||||
#include "testing.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
class MessageRomTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Skip tests if ROM is not available
|
||||
if (getenv("YAZE_SKIP_ROM_TESTS")) {
|
||||
GTEST_SKIP() << "ROM tests disabled";
|
||||
}
|
||||
|
||||
// Check if ROM file exists
|
||||
std::string rom_path = "zelda3.sfc";
|
||||
if (!std::filesystem::exists(rom_path)) {
|
||||
GTEST_SKIP() << "Test ROM not found: " << rom_path;
|
||||
}
|
||||
|
||||
EXPECT_OK(rom_.LoadFromFile(rom_path));
|
||||
dictionary_ = editor::BuildDictionaryEntries(&rom_);
|
||||
}
|
||||
void TearDown() override {}
|
||||
|
||||
Rom rom_;
|
||||
editor::MessageEditor message_editor_;
|
||||
std::vector<editor::DictionaryEntry> dictionary_;
|
||||
};
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_CommandParsing) {
|
||||
std::vector<uint8_t> mock_data = {0x6A, 0x7F, 0x00};
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
EXPECT_TRUE(result.ok());
|
||||
const auto message_data = result.value();
|
||||
|
||||
// Verify that the command was recognized and parsed
|
||||
EXPECT_EQ(message_data.ContentsParsed, "[L]");
|
||||
EXPECT_EQ(pos, 2);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_BasicAscii) {
|
||||
// A, B, C, terminator
|
||||
std::vector<uint8_t> mock_data = {0x00, 0x01, 0x02, 0x7F, 0x00};
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
ASSERT_TRUE(result.ok());
|
||||
const auto message_data = result.value();
|
||||
EXPECT_EQ(pos, 4); // consumed all 4 bytes
|
||||
|
||||
std::vector<editor::MessageData> message_data_vector = {message_data};
|
||||
auto parsed = editor::ParseMessageData(message_data_vector, dictionary_);
|
||||
|
||||
EXPECT_THAT(parsed, ::testing::ElementsAre("ABC"));
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindMatchingCharacter_Success) {
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('A'), 0x00);
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('Z'), 0x19);
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('a'), 0x1A);
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('z'), 0x33);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindMatchingCharacter_Failure) {
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('@'), 0xFF);
|
||||
EXPECT_EQ(editor::FindMatchingCharacter('#'), 0xFF);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindDictionaryEntry_Success) {
|
||||
EXPECT_EQ(editor::FindDictionaryEntry(0x88), 0x00);
|
||||
EXPECT_EQ(editor::FindDictionaryEntry(0x90), 0x08);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindDictionaryEntry_Failure) {
|
||||
EXPECT_EQ(editor::FindDictionaryEntry(0x00), -1);
|
||||
EXPECT_EQ(editor::FindDictionaryEntry(0xFF), -1);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseMessageToData_Basic) {
|
||||
std::string input = "[L][C:01]ABC";
|
||||
auto result = editor::ParseMessageToData(input);
|
||||
std::vector<uint8_t> expected = {0x6A, 0x77, 0x01, 0x00, 0x01, 0x02};
|
||||
EXPECT_EQ(result, expected);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ReplaceAllDictionaryWords_Success) {
|
||||
std::vector<editor::DictionaryEntry> mock_dict = {
|
||||
editor::DictionaryEntry(0x00, "test"),
|
||||
editor::DictionaryEntry(0x01, "message")};
|
||||
std::string input = "This is a test message.";
|
||||
auto result = editor::ReplaceAllDictionaryWords(input, mock_dict);
|
||||
EXPECT_EQ(result, "This is a [D:00] [D:01].");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ReplaceAllDictionaryWords_NoMatch) {
|
||||
std::vector<editor::DictionaryEntry> mock_dict = {
|
||||
editor::DictionaryEntry(0x00, "hello")};
|
||||
std::string input = "No matching words.";
|
||||
auto result = editor::ReplaceAllDictionaryWords(input, mock_dict);
|
||||
EXPECT_EQ(result, "No matching words.");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseTextDataByte_Success) {
|
||||
EXPECT_EQ(editor::ParseTextDataByte(0x00), "A");
|
||||
EXPECT_EQ(editor::ParseTextDataByte(0x74), "[1]");
|
||||
EXPECT_EQ(editor::ParseTextDataByte(0x88), "[D:00]");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseTextDataByte_Failure) {
|
||||
EXPECT_EQ(editor::ParseTextDataByte(0xFF), "");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_SpecialCharacters) {
|
||||
std::vector<uint8_t> mock_data = {0x4D, 0x4E, 0x4F, 0x50, 0x7F};
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
ASSERT_TRUE(result.ok());
|
||||
const auto message_data = result.value();
|
||||
|
||||
EXPECT_EQ(message_data.ContentsParsed, "[UP][DOWN][LEFT][RIGHT]");
|
||||
EXPECT_EQ(pos, 5);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_DictionaryReference) {
|
||||
std::vector<uint8_t> mock_data = {0x88, 0x89, 0x7F};
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
ASSERT_TRUE(result.ok());
|
||||
const auto message_data = result.value();
|
||||
|
||||
EXPECT_EQ(message_data.ContentsParsed, "[D:00][D:01]");
|
||||
EXPECT_EQ(pos, 3);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_InvalidTerminator) {
|
||||
std::vector<uint8_t> mock_data = {0x00, 0x01, 0x02}; // No terminator
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
EXPECT_FALSE(result.ok());
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, ParseSingleMessage_EmptyData) {
|
||||
std::vector<uint8_t> mock_data = {0x7F};
|
||||
int pos = 0;
|
||||
|
||||
auto result = editor::ParseSingleMessage(mock_data, &pos);
|
||||
ASSERT_TRUE(result.ok());
|
||||
const auto message_data = result.value();
|
||||
|
||||
EXPECT_EQ(message_data.ContentsParsed, "");
|
||||
EXPECT_EQ(pos, 1);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, OptimizeMessageForDictionary_Basic) {
|
||||
std::vector<editor::DictionaryEntry> mock_dict = {
|
||||
editor::DictionaryEntry(0x00, "Link"),
|
||||
editor::DictionaryEntry(0x01, "Zelda")};
|
||||
std::string input = "[L] rescued Zelda from danger.";
|
||||
|
||||
editor::MessageData message_data;
|
||||
std::string optimized =
|
||||
message_data.OptimizeMessageForDictionary(input, mock_dict);
|
||||
|
||||
EXPECT_EQ(optimized, "[L] rescued [D:01] from danger.");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, SetMessage_Success) {
|
||||
std::vector<editor::DictionaryEntry> mock_dict = {
|
||||
editor::DictionaryEntry(0x00, "item")};
|
||||
editor::MessageData message_data;
|
||||
std::string input = "You got an item!";
|
||||
|
||||
message_data.SetMessage(input, mock_dict);
|
||||
|
||||
EXPECT_EQ(message_data.RawString, "You got an item!");
|
||||
EXPECT_EQ(message_data.ContentsParsed, "You got an [D:00]!");
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindMatchingElement_CommandWithArgument) {
|
||||
std::string input = "[W:02]";
|
||||
editor::ParsedElement result = editor::FindMatchingElement(input);
|
||||
|
||||
EXPECT_TRUE(result.Active);
|
||||
EXPECT_EQ(result.Parent.Token, "W");
|
||||
EXPECT_EQ(result.Value, 0x02);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, FindMatchingElement_InvalidCommand) {
|
||||
std::string input = "[INVALID]";
|
||||
editor::ParsedElement result = editor::FindMatchingElement(input);
|
||||
|
||||
EXPECT_FALSE(result.Active);
|
||||
}
|
||||
|
||||
TEST_F(MessageRomTest, BuildDictionaryEntries_CorrectSize) {
|
||||
auto result = editor::BuildDictionaryEntries(&rom_);
|
||||
EXPECT_EQ(result.size(), editor::kNumDictionaryEntries);
|
||||
EXPECT_FALSE(result.empty());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
157
test/integration/zelda3/sprite_position_test.cc
Normal file
157
test/integration/zelda3/sprite_position_test.cc
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "app/zelda3/overworld/overworld_map.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
class SpritePositionTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Try to load a vanilla ROM for testing
|
||||
rom_ = std::make_unique<Rom>();
|
||||
std::string rom_path = "bin/zelda3.sfc";
|
||||
|
||||
// Check if ROM exists in build directory
|
||||
std::ifstream rom_file(rom_path);
|
||||
if (rom_file.good()) {
|
||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path).ok()) << "Failed to load ROM from " << rom_path;
|
||||
} else {
|
||||
// Skip test if ROM not found
|
||||
GTEST_SKIP() << "ROM file not found at " << rom_path;
|
||||
}
|
||||
|
||||
overworld_ = std::make_unique<Overworld>(rom_.get());
|
||||
ASSERT_TRUE(overworld_->Load(rom_.get()).ok()) << "Failed to load overworld";
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
overworld_.reset();
|
||||
rom_.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<Overworld> overworld_;
|
||||
};
|
||||
|
||||
// Test sprite coordinate system understanding
|
||||
TEST_F(SpritePositionTest, SpriteCoordinateSystem) {
|
||||
// Test sprites from different worlds
|
||||
for (int game_state = 0; game_state < 3; game_state++) {
|
||||
const auto& sprites = overworld_->sprites(game_state);
|
||||
std::cout << "\n=== Game State " << game_state << " ===" << std::endl;
|
||||
std::cout << "Total sprites: " << sprites.size() << std::endl;
|
||||
|
||||
int sprite_count = 0;
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() && sprite_count < 10) { // Show first 10 sprites
|
||||
std::cout << "Sprite " << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(sprite.id()) << " (" << const_cast<Sprite&>(sprite).name() << ")" << std::endl;
|
||||
std::cout << " Map ID: 0x" << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< sprite.map_id() << std::endl;
|
||||
std::cout << " X: " << std::dec << sprite.x() << " (0x" << std::hex << sprite.x() << ")" << std::endl;
|
||||
std::cout << " Y: " << std::dec << sprite.y() << " (0x" << std::hex << sprite.y() << ")" << std::endl;
|
||||
std::cout << " map_x: " << std::dec << sprite.map_x() << std::endl;
|
||||
std::cout << " map_y: " << std::dec << sprite.map_y() << std::endl;
|
||||
|
||||
// Calculate expected world ranges
|
||||
int world_start = game_state * 0x40;
|
||||
int world_end = world_start + 0x40;
|
||||
std::cout << " World range: 0x" << std::hex << world_start << " - 0x" << world_end << std::endl;
|
||||
|
||||
sprite_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test sprite filtering logic
|
||||
TEST_F(SpritePositionTest, SpriteFilteringLogic) {
|
||||
// Test the filtering logic used in DrawOverworldSprites
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== Testing World " << current_world << " Filtering ===" << std::endl;
|
||||
|
||||
int visible_sprites = 0;
|
||||
int total_sprites = 0;
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted()) {
|
||||
total_sprites++;
|
||||
|
||||
// This is the filtering logic from DrawOverworldSprites
|
||||
bool should_show = (sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40));
|
||||
|
||||
if (should_show) {
|
||||
visible_sprites++;
|
||||
std::cout << " Visible: Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite.map_id() << " at ("
|
||||
<< std::dec << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "World " << current_world << ": " << visible_sprites << "/"
|
||||
<< total_sprites << " sprites visible" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Test map coordinate calculations
|
||||
TEST_F(SpritePositionTest, MapCoordinateCalculations) {
|
||||
// Test how map coordinates should be calculated
|
||||
for (int current_world = 0; current_world < 3; current_world++) {
|
||||
const auto& sprites = overworld_->sprites(current_world);
|
||||
|
||||
std::cout << "\n=== World " << current_world << " Coordinate Analysis ===" << std::endl;
|
||||
|
||||
for (const auto& sprite : sprites) {
|
||||
if (!sprite.deleted() &&
|
||||
sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||
sprite.map_id() >= (current_world * 0x40)) {
|
||||
|
||||
// Calculate map position
|
||||
int sprite_map_id = sprite.map_id();
|
||||
int local_map_index = sprite_map_id - (current_world * 0x40);
|
||||
int map_col = local_map_index % 8;
|
||||
int map_row = local_map_index / 8;
|
||||
|
||||
int map_canvas_x = map_col * 512; // kOverworldMapSize
|
||||
int map_canvas_y = map_row * 512;
|
||||
|
||||
std::cout << "Sprite 0x" << std::hex << static_cast<int>(sprite.id())
|
||||
<< " on map 0x" << sprite_map_id << std::endl;
|
||||
std::cout << " Local map index: " << std::dec << local_map_index << std::endl;
|
||||
std::cout << " Map position: (" << map_col << ", " << map_row << ")" << std::endl;
|
||||
std::cout << " Map canvas pos: (" << map_canvas_x << ", " << map_canvas_y << ")" << std::endl;
|
||||
std::cout << " Sprite global pos: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
std::cout << " Sprite local pos: (" << sprite.map_x() << ", " << sprite.map_y() << ")" << std::endl;
|
||||
|
||||
// Verify the calculation
|
||||
int expected_global_x = map_canvas_x + sprite.map_x();
|
||||
int expected_global_y = map_canvas_y + sprite.map_y();
|
||||
|
||||
std::cout << " Expected global: (" << expected_global_x << ", " << expected_global_y << ")" << std::endl;
|
||||
std::cout << " Actual global: (" << sprite.x() << ", " << sprite.y() << ")" << std::endl;
|
||||
|
||||
if (expected_global_x == sprite.x() && expected_global_y == sprite.y()) {
|
||||
std::cout << " ✓ Coordinates match!" << std::endl;
|
||||
} else {
|
||||
std::cout << " ✗ Coordinate mismatch!" << std::endl;
|
||||
}
|
||||
|
||||
break; // Only test first sprite for brevity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
Reference in New Issue
Block a user