backend-infra-engineer: Release v0.3.3 snapshot

This commit is contained in:
scawful
2025-11-21 21:35:50 -05:00
parent 3d71417f62
commit 476dd1cd1c
818 changed files with 65706 additions and 35514 deletions

View File

@@ -53,14 +53,14 @@ TEST(ResourceCatalogTest, PatchSchemaIncludesAsarAndCreateActions) {
EXPECT_EQ(actions[0].name, "apply");
EXPECT_FALSE(actions[0].returns.empty());
auto has_asar = std::find_if(actions.begin(), actions.end(), [](const auto& action) {
return action.name == "apply-asar";
});
auto has_asar = std::find_if(
actions.begin(), actions.end(),
[](const auto& action) { return action.name == "apply-asar"; });
EXPECT_NE(has_asar, actions.end());
auto has_create = std::find_if(actions.begin(), actions.end(), [](const auto& action) {
return action.name == "create";
});
auto has_create =
std::find_if(actions.begin(), actions.end(),
[](const auto& action) { return action.name == "create"; });
EXPECT_NE(has_create, actions.end());
}
@@ -79,8 +79,8 @@ TEST(ResourceCatalogTest, DungeonSchemaListsMetadataAndObjectsReturns) {
TEST(ResourceCatalogTest, YamlSerializationIncludesMetadataAndActions) {
const auto& catalog = ResourceCatalog::Instance();
std::string yaml = catalog.SerializeResourcesAsYaml(
catalog.AllResources(), "0.1.0", "2025-10-01");
std::string yaml = catalog.SerializeResourcesAsYaml(catalog.AllResources(),
"0.1.0", "2025-10-01");
EXPECT_NE(yaml.find("version: \"0.1.0\""), std::string::npos);
EXPECT_NE(yaml.find("name: \"patch\""), std::string::npos);

View File

@@ -31,9 +31,9 @@ class Tile16ProposalGeneratorTest : public ::testing::Test {
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_ValidCommand) {
std::string command = "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E";
auto result = generator_->ParseSetTileCommand(command, nullptr);
ASSERT_TRUE(result.ok()) << result.status().message();
EXPECT_EQ(result->map_id, 0);
EXPECT_EQ(result->x, 10);
@@ -43,19 +43,19 @@ TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_ValidCommand) {
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_InvalidFormat) {
std::string command = "overworld set-tile --map 0"; // Missing required args
auto result = generator_->ParseSetTileCommand(command, nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("Invalid command format"));
}
TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_WrongCommandType) {
std::string command = "overworld get-tile --map 0 --x 10 --y 20";
auto result = generator_->ParseSetTileCommand(command, nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("Not a set-tile command"));
@@ -66,65 +66,68 @@ TEST_F(Tile16ProposalGeneratorTest, ParseSetTileCommand_WrongCommandType) {
// ============================================================================
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_ValidCommand) {
std::string command =
"overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 --tile 0x02E";
std::string command =
"overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 --tile "
"0x02E";
auto result = generator_->ParseSetAreaCommand(command, nullptr);
ASSERT_TRUE(result.ok()) << result.status().message();
EXPECT_EQ(result->size(), 15); // 5 width * 3 height = 15 tiles
// Check first tile
EXPECT_EQ((*result)[0].map_id, 0);
EXPECT_EQ((*result)[0].x, 10);
EXPECT_EQ((*result)[0].y, 20);
EXPECT_EQ((*result)[0].new_tile, 0x02E);
// Check last tile
EXPECT_EQ((*result)[14].x, 14); // 10 + 4
EXPECT_EQ((*result)[14].y, 22); // 20 + 2
}
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_SingleTile) {
std::string command =
"overworld set-area --map 0 --x 10 --y 20 --width 1 --height 1 --tile 0x02E";
std::string command =
"overworld set-area --map 0 --x 10 --y 20 --width 1 --height 1 --tile "
"0x02E";
auto result = generator_->ParseSetAreaCommand(command, nullptr);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->size(), 1);
}
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_LargeArea) {
std::string command =
"overworld set-area --map 0 --x 0 --y 0 --width 32 --height 32 --tile 0x000";
std::string command =
"overworld set-area --map 0 --x 0 --y 0 --width 32 --height 32 --tile "
"0x000";
auto result = generator_->ParseSetAreaCommand(command, nullptr);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->size(), 1024); // 32 * 32
}
TEST_F(Tile16ProposalGeneratorTest, ParseSetAreaCommand_InvalidFormat) {
std::string command = "overworld set-area --map 0 --x 10"; // Missing args
auto result = generator_->ParseSetAreaCommand(command, nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("Invalid set-area command format"));
}
// ============================================================================
// ParseReplaceTileCommand Tests
// ParseReplaceTileCommand Tests
// ============================================================================
TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_NoROM) {
std::string command =
std::string command =
"overworld replace-tile --map 0 --old-tile 0x02E --new-tile 0x030";
auto result = generator_->ParseReplaceTileCommand(command, nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("ROM must be loaded"));
@@ -132,9 +135,9 @@ TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_NoROM) {
TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_InvalidFormat) {
std::string command = "overworld replace-tile --map 0"; // Missing tiles
auto result = generator_->ParseReplaceTileCommand(command, nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("Invalid replace-tile command format"));
@@ -147,12 +150,12 @@ TEST_F(Tile16ProposalGeneratorTest, ParseReplaceTileCommand_InvalidFormat) {
TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_MultipleCommands) {
std::vector<std::string> commands = {
"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E",
"overworld set-area --map 0 --x 5 --y 5 --width 2 --height 2 --tile 0x030"
};
auto result = generator_->GenerateFromCommands(
"Test prompt", commands, "test_ai", nullptr);
"overworld set-area --map 0 --x 5 --y 5 --width 2 --height 2 --tile "
"0x030"};
auto result = generator_->GenerateFromCommands("Test prompt", commands,
"test_ai", nullptr);
ASSERT_TRUE(result.ok()) << result.status().message();
EXPECT_EQ(result->changes.size(), 5); // 1 from set-tile + 4 from set-area
EXPECT_EQ(result->prompt, "Test prompt");
@@ -162,10 +165,10 @@ TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_MultipleCommands) {
TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_EmptyCommands) {
std::vector<std::string> commands = {};
auto result = generator_->GenerateFromCommands(
"Test prompt", commands, "test_ai", nullptr);
auto result = generator_->GenerateFromCommands("Test prompt", commands,
"test_ai", nullptr);
EXPECT_FALSE(result.ok());
EXPECT_THAT(result.status().message(),
::testing::HasSubstr("No valid tile16 changes found"));
@@ -178,10 +181,10 @@ TEST_F(Tile16ProposalGeneratorTest, GenerateFromCommands_IgnoresComments) {
"# Another comment",
"" // Empty line
};
auto result = generator_->GenerateFromCommands(
"Test prompt", commands, "test_ai", nullptr);
auto result = generator_->GenerateFromCommands("Test prompt", commands,
"test_ai", nullptr);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->changes.size(), 1); // Only the valid command
}
@@ -197,9 +200,9 @@ TEST_F(Tile16ProposalGeneratorTest, Tile16Change_ToString) {
change.y = 20;
change.old_tile = 0x02E;
change.new_tile = 0x030;
std::string result = change.ToString();
EXPECT_THAT(result, ::testing::HasSubstr("Map 5"));
EXPECT_THAT(result, ::testing::HasSubstr("(10,20)"));
EXPECT_THAT(result, ::testing::HasSubstr("0x2e"));
@@ -217,7 +220,7 @@ TEST_F(Tile16ProposalGeneratorTest, Proposal_ToJsonAndFromJson) {
original.ai_service = "gemini";
original.reasoning = "Test reasoning";
original.status = Tile16Proposal::Status::PENDING;
Tile16Change change;
change.map_id = 5;
change.x = 10;
@@ -225,10 +228,10 @@ TEST_F(Tile16ProposalGeneratorTest, Proposal_ToJsonAndFromJson) {
change.old_tile = 0x02E;
change.new_tile = 0x030;
original.changes.push_back(change);
std::string json = original.ToJson();
auto result = Tile16Proposal::FromJson(json);
ASSERT_TRUE(result.ok()) << result.status().message();
EXPECT_EQ(result->id, original.id);
EXPECT_EQ(result->prompt, original.prompt);

View File

@@ -1,10 +1,12 @@
#include "core/asar_wrapper.h"
#include "test_utils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <fstream>
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
#include "test_utils.h"
namespace yaze {
namespace core {
@@ -17,9 +19,7 @@ class AsarWrapperTest : public ::testing::Test {
CreateTestFiles();
}
void TearDown() override {
CleanupTestFiles();
}
void TearDown() override { CleanupTestFiles(); }
void CreateTestFiles() {
// Create test directory
@@ -79,7 +79,7 @@ LDA unknown_operand
TEST_F(AsarWrapperTest, InitializationAndShutdown) {
// Test initialization
ASSERT_FALSE(wrapper_->IsInitialized());
auto status = wrapper_->Initialize();
EXPECT_TRUE(status.ok()) << status.message();
EXPECT_TRUE(wrapper_->IsInitialized());
@@ -113,7 +113,7 @@ TEST_F(AsarWrapperTest, OperationsWithoutInitialization) {
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
EXPECT_FALSE(patch_result.ok());
EXPECT_THAT(patch_result.status().message(),
EXPECT_THAT(patch_result.status().message(),
testing::HasSubstr("not initialized"));
auto symbols_result = wrapper_->ExtractSymbols(test_asm_path_.string());
@@ -132,8 +132,8 @@ TEST_F(AsarWrapperTest, ValidPatchApplication) {
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
const auto& result = patch_result.value();
EXPECT_TRUE(result.success) << "Patch failed: "
<< testing::PrintToString(result.errors);
EXPECT_TRUE(result.success)
<< "Patch failed: " << testing::PrintToString(result.errors);
EXPECT_GT(result.rom_size, 0);
EXPECT_EQ(rom_copy.size(), result.rom_size);
@@ -146,9 +146,10 @@ TEST_F(AsarWrapperTest, InvalidPatchHandling) {
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatch(invalid_asm_path_.string(), rom_copy);
auto patch_result =
wrapper_->ApplyPatch(invalid_asm_path_.string(), rom_copy);
EXPECT_FALSE(patch_result.ok());
EXPECT_THAT(patch_result.status().message(),
EXPECT_THAT(patch_result.status().message(),
testing::HasSubstr("Patch failed"));
}
@@ -181,7 +182,8 @@ TEST_F(AsarWrapperTest, SymbolExtraction) {
if (symbol.name == "testlabel") {
found_testlabel = true;
EXPECT_EQ(symbol.address, 0x008000); // Expected address from org directive
EXPECT_EQ(symbol.address,
0x008000); // Expected address from org directive
} else if (symbol.name == "anotherlabel") {
found_anotherlabel = true;
}
@@ -216,9 +218,10 @@ TEST_F(AsarWrapperTest, SymbolTableOperations) {
// Test symbols at address lookup
if (testlabel_symbol) {
auto symbols_at_addr = wrapper_->GetSymbolsAtAddress(testlabel_symbol->address);
auto symbols_at_addr =
wrapper_->GetSymbolsAtAddress(testlabel_symbol->address);
EXPECT_GT(symbols_at_addr.size(), 0);
bool found = false;
for (const auto& symbol : symbols_at_addr) {
if (symbol.name == "testlabel") {
@@ -242,9 +245,9 @@ stringpatchlabel:
)";
std::vector<uint8_t> rom_copy = test_rom_;
auto patch_result = wrapper_->ApplyPatchFromString(
patch_content, rom_copy, test_dir_.string());
auto patch_result = wrapper_->ApplyPatchFromString(patch_content, rom_copy,
test_dir_.string());
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
const auto& result = patch_result.value();
@@ -273,11 +276,11 @@ TEST_F(AsarWrapperTest, AssemblyValidation) {
// Test invalid assembly
auto invalid_status = wrapper_->ValidateAssembly(invalid_asm_path_.string());
EXPECT_FALSE(invalid_status.ok());
EXPECT_THAT(invalid_status.message(),
EXPECT_THAT(invalid_status.message(),
testing::AnyOf(testing::HasSubstr("validation failed"),
testing::HasSubstr("Patch failed"),
testing::HasSubstr("Unknown command"),
testing::HasSubstr("Label")));
testing::HasSubstr("Patch failed"),
testing::HasSubstr("Unknown command"),
testing::HasSubstr("Label")));
}
TEST_F(AsarWrapperTest, ResetFunctionality) {
@@ -294,10 +297,10 @@ TEST_F(AsarWrapperTest, ResetFunctionality) {
// Reset and verify state is cleared
wrapper_->Reset();
auto symbol_table_after = wrapper_->GetSymbolTable();
EXPECT_EQ(symbol_table_after.size(), 0);
auto errors = wrapper_->GetLastErrors();
auto warnings = wrapper_->GetLastWarnings();
EXPECT_EQ(errors.size(), 0);
@@ -313,7 +316,7 @@ TEST_F(AsarWrapperTest, CreatePatchNotImplemented) {
std::string patch_path = test_dir_.string() + "/generated.asm";
auto status = wrapper_->CreatePatch(original_rom, modified_rom, patch_path);
EXPECT_FALSE(status.ok());
EXPECT_THAT(status.message(), testing::HasSubstr("not yet implemented"));
}

View File

@@ -1,7 +1,7 @@
#include "testing.h"
#include "util/hex.h"
#include "testing.h"
namespace yaze {
namespace test {
@@ -100,4 +100,4 @@ TEST(HexTest, HexLongLong) {
}
} // namespace test
} // namespace yaze
} // namespace yaze

View File

@@ -71,4 +71,3 @@ TEST_F(ApuDspTest, GetSamplesReturnsSilenceAfterReset) {
} // namespace emu
} // namespace yaze

View File

@@ -1,14 +1,14 @@
#include <gtest/gtest.h>
#include "app/emu/audio/apu.h"
#include "app/emu/memory/memory.h"
#include "app/emu/audio/spc700.h"
#include "app/emu/memory/memory.h"
namespace yaze {
namespace emu {
class ApuIplHandshakeTest : public ::testing::Test {
protected:
protected:
MemoryImpl mem;
Apu* apu;
@@ -25,9 +25,9 @@ protected:
TEST_F(ApuIplHandshakeTest, SPC700StartsAtIplRomEntry) {
// After reset, PC should be at IPL ROM reset vector
uint16_t reset_vector = apu->spc700().read(0xFFFE) |
(apu->spc700().read(0xFFFF) << 8);
uint16_t reset_vector =
apu->spc700().read(0xFFFE) | (apu->spc700().read(0xFFFF) << 8);
// The IPL ROM reset vector should point to 0xFFC0 (start of IPL ROM)
EXPECT_EQ(reset_vector, 0xFFC0);
}
@@ -35,7 +35,7 @@ TEST_F(ApuIplHandshakeTest, SPC700StartsAtIplRomEntry) {
TEST_F(ApuIplHandshakeTest, IplRomReadable) {
// IPL ROM should be readable at 0xFFC0-0xFFFF after reset
uint8_t first_byte = apu->Read(0xFFC0);
// First byte of IPL ROM should be 0xCD (CMP Y, #$EF)
EXPECT_EQ(first_byte, 0xCD);
}
@@ -43,7 +43,7 @@ TEST_F(ApuIplHandshakeTest, IplRomReadable) {
TEST_F(ApuIplHandshakeTest, CycleTrackingWorks) {
// Execute one SPC700 opcode
apu->spc700().RunOpcode();
// GetLastOpcodeCycles should return a valid cycle count (2-12 typically)
int cycles = apu->spc700().GetLastOpcodeCycles();
EXPECT_GT(cycles, 0);
@@ -54,15 +54,15 @@ TEST_F(ApuIplHandshakeTest, PortReadWrite) {
// Write to input port from CPU side (simulating CPU writes to $2140-$2143)
apu->in_ports_[0] = 0xAA;
apu->in_ports_[1] = 0xBB;
// SPC should be able to read these ports at $F4-$F7
EXPECT_EQ(apu->Read(0xF4), 0xAA);
EXPECT_EQ(apu->Read(0xF5), 0xBB);
// Write to output ports from SPC side
apu->Write(0xF4, 0xCC);
apu->Write(0xF5, 0xDD);
// CPU should be able to read these (simulating reads from $2140-$2143)
EXPECT_EQ(apu->out_ports_[0], 0xCC);
EXPECT_EQ(apu->out_ports_[1], 0xDD);
@@ -71,21 +71,21 @@ TEST_F(ApuIplHandshakeTest, PortReadWrite) {
TEST_F(ApuIplHandshakeTest, IplRomDisableViaControlRegister) {
// IPL ROM is readable by default
EXPECT_EQ(apu->Read(0xFFC0), 0xCD);
// Write to control register ($F1) to disable IPL ROM (bit 7 = 1)
apu->Write(0xF1, 0x80);
// Now $FFC0-$FFFF should read from RAM instead of IPL ROM
// RAM is initialized to 0, so we should read 0
EXPECT_EQ(apu->Read(0xFFC0), 0x00);
// Write something to RAM
apu->ram[0xFFC0] = 0x42;
EXPECT_EQ(apu->Read(0xFFC0), 0x42);
// Re-enable IPL ROM (bit 7 = 0)
apu->Write(0xF1, 0x00);
// Should read IPL ROM again
EXPECT_EQ(apu->Read(0xFFC0), 0xCD);
}
@@ -93,18 +93,18 @@ TEST_F(ApuIplHandshakeTest, IplRomDisableViaControlRegister) {
TEST_F(ApuIplHandshakeTest, TimersEnableAndCount) {
// Enable timer 0 via control register
apu->Write(0xF1, 0x01);
// Set timer 0 target to 4
apu->Write(0xFA, 0x04);
// Run enough cycles to trigger timer
for (int i = 0; i < 1000; ++i) {
apu->Cycle();
}
// Read timer 0 counter (auto-clears on read)
uint8_t counter = apu->Read(0xFD);
// Counter should be non-zero if timer is working
EXPECT_GT(counter, 0);
EXPECT_LE(counter, 0x0F);
@@ -113,17 +113,17 @@ TEST_F(ApuIplHandshakeTest, TimersEnableAndCount) {
TEST_F(ApuIplHandshakeTest, IplBootSequenceProgresses) {
// This test verifies that the IPL ROM boot sequence can actually progress
// without getting stuck in an infinite loop
uint16_t initial_pc = apu->spc700().PC;
// Run multiple opcodes to let the IPL boot sequence progress
for (int i = 0; i < 100; ++i) {
apu->spc700().RunOpcode();
apu->Cycle();
}
uint16_t final_pc = apu->spc700().PC;
// PC should have advanced (boot sequence is progressing)
// If it's stuck in a tight loop, PC won't change much
EXPECT_NE(initial_pc, final_pc);
@@ -131,15 +131,15 @@ TEST_F(ApuIplHandshakeTest, IplBootSequenceProgresses) {
TEST_F(ApuIplHandshakeTest, AccurateCycleCountsForCommonOpcodes) {
// Test that specific opcodes return correct cycle counts
// NOP (0x00) should take 2 cycles
apu->spc700().PC = 0x0000;
apu->ram[0x0000] = 0x00; // NOP
apu->spc700().RunOpcode();
apu->spc700().RunOpcode(); // Execute
EXPECT_EQ(apu->spc700().GetLastOpcodeCycles(), 2);
// MOV A, #imm (0xE8) should take 2 cycles
// MOV A, #imm (0xE8) should take 2 cycles
apu->spc700().PC = 0x0002;
apu->ram[0x0002] = 0xE8; // MOV A, #imm
apu->ram[0x0003] = 0x42; // immediate value
@@ -150,4 +150,3 @@ TEST_F(ApuIplHandshakeTest, AccurateCycleCountsForCommonOpcodes) {
} // namespace emu
} // namespace yaze

View File

@@ -27,4 +27,3 @@ TEST(Spc700ResetTest, ResetVectorExecutesIplSequence) {
} // namespace emu
} // namespace yaze

View File

@@ -3,8 +3,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstdint>
#include <array>
#include <cstdint>
#include "absl/status/statusor.h"
#include "app/rom.h"
@@ -172,7 +172,8 @@ TEST(LC_LZ2_CompressionTest, CompressionSingleSet) {
TEST(LC_LZ2_CompressionTest, CompressionSingleWord) {
Rom rom;
uint8_t single_word[6] = {0x2A, 0x01, 0x2A, 0x01, 0x2A, 0x01};
uint8_t single_word_expected[4] = {BUILD_HEADER(0x02, 0x06), 0x2A, 0x01, 0xFF};
uint8_t single_word_expected[4] = {BUILD_HEADER(0x02, 0x06), 0x2A, 0x01,
0xFF};
auto comp_result = ExpectCompressOk(rom, single_word, 6);
EXPECT_THAT(single_word_expected, ElementsAreArray(comp_result.data(), 4));
@@ -412,16 +413,16 @@ TEST(LC_LZ2_CompressionTest, DecompressionValidCommand) {
TEST(LC_LZ2_CompressionTest, DecompressionMixingCommand) {
Rom rom;
uint8_t random1_i[11] = {BUILD_HEADER(0x01, 0x03),
0x2A,
BUILD_HEADER(0x00, 0x04),
0x01,
0x02,
0x03,
0x04,
BUILD_HEADER(0x02, 0x02),
0x0B,
0x16,
0xFF};
0x2A,
BUILD_HEADER(0x00, 0x04),
0x01,
0x02,
0x03,
0x04,
BUILD_HEADER(0x02, 0x02),
0x0B,
0x16,
0xFF};
uint8_t random1_o[9] = {42, 42, 42, 1, 2, 3, 4, 11, 22};
auto decomp_result = ExpectDecompressOk(rom, random1_i, 11);
EXPECT_THAT(random1_o, ElementsAreArray(decomp_result.data(), 9));

View File

@@ -27,7 +27,7 @@ TEST(SnesTileTest, UnpackBppTile) {
// First pixel should be 3 (both bits set): plane0 bit7=1, plane1 bit7=1
// Last pixel of first row should be 1: plane0 bit0=1, plane1 bit0=0
std::vector<uint8_t> data2bpp = {
0x81, 0x80, // Row 0: plane0=10000001, plane1=10000000
0x81, 0x80, // Row 0: plane0=10000001, plane1=10000000
0x00, 0x00, // Row 1: plane0=00000000, plane1=00000000
0x00, 0x00, // Row 2: plane0=00000000, plane1=00000000
0x00, 0x00, // Row 3: plane0=00000000, plane1=00000000
@@ -37,25 +37,26 @@ TEST(SnesTileTest, UnpackBppTile) {
0x01, 0x81 // Row 7: plane0=00000001, plane1=10000001
};
auto tile2bpp = gfx::UnpackBppTile(data2bpp, 0, 2);
EXPECT_EQ(tile2bpp.data[0], 3); // First pixel: 1|1<<1 = 3
EXPECT_EQ(tile2bpp.data[7], 1); // Last pixel of first row: 1|0<<1 = 1
EXPECT_EQ(tile2bpp.data[56], 2); // First pixel of last row: 0|1<<1 = 2
EXPECT_EQ(tile2bpp.data[63], 3); // Last pixel: 1|1<<1 = 3
EXPECT_EQ(tile2bpp.data[0], 3); // First pixel: 1|1<<1 = 3
EXPECT_EQ(tile2bpp.data[7], 1); // Last pixel of first row: 1|0<<1 = 1
EXPECT_EQ(tile2bpp.data[56], 2); // First pixel of last row: 0|1<<1 = 2
EXPECT_EQ(tile2bpp.data[63], 3); // Last pixel: 1|1<<1 = 3
// Test 4bpp tile unpacking
// According to SnesLab: First planes 1&2 intertwined, then planes 3&4 intertwined
// 32 bytes total: 16 bytes for planes 1&2, then 16 bytes for planes 3&4
// Test 4bpp tile unpacking
// According to SnesLab: First planes 1&2 intertwined, then planes 3&4
// intertwined 32 bytes total: 16 bytes for planes 1&2, then 16 bytes for
// planes 3&4
std::vector<uint8_t> data4bpp = {
// Planes 1&2 intertwined (rows 0-7)
0x81, 0x80, // Row 0: bp1=10000001, bp2=10000000
0x00, 0x00, // Row 1: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 1: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 2: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 3: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 4: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 5: bp1=00000000, bp2=00000000
0x00, 0x00, // Row 6: bp1=00000000, bp2=00000000
0x01, 0x81, // Row 7: bp1=00000001, bp2=10000001
// Planes 3&4 intertwined (rows 0-7)
// Planes 3&4 intertwined (rows 0-7)
0x81, 0x80, // Row 0: bp3=10000001, bp4=10000000
0x00, 0x00, // Row 1: bp3=00000000, bp4=00000000
0x00, 0x00, // Row 2: bp3=00000000, bp4=00000000
@@ -67,9 +68,11 @@ TEST(SnesTileTest, UnpackBppTile) {
};
auto tile4bpp = gfx::UnpackBppTile(data4bpp, 0, 4);
EXPECT_EQ(tile4bpp.data[0], 0xF); // First pixel: 1|1<<1|1<<2|1<<3 = 15
EXPECT_EQ(tile4bpp.data[7], 0x5); // Last pixel of first row: 1|0<<1|1<<2|0<<3 = 5
EXPECT_EQ(tile4bpp.data[56], 0xA); // First pixel of last row: 0|1<<1|0<<2|1<<3 = 10
EXPECT_EQ(tile4bpp.data[63], 0xF); // Last pixel: 1|1<<1|1<<2|1<<3 = 15
EXPECT_EQ(tile4bpp.data[7],
0x5); // Last pixel of first row: 1|0<<1|1<<2|0<<3 = 5
EXPECT_EQ(tile4bpp.data[56],
0xA); // First pixel of last row: 0|1<<1|0<<2|1<<3 = 10
EXPECT_EQ(tile4bpp.data[63], 0xF); // Last pixel: 1|1<<1|1<<2|1<<3 = 15
}
TEST(SnesTileTest, PackBppTile) {
@@ -90,10 +93,14 @@ TEST(SnesTileTest, PackBppTile) {
tile2bpp.data[56] = 2;
tile2bpp.data[63] = 3;
auto packed2bpp = gfx::PackBppTile(tile2bpp, 2);
EXPECT_EQ(packed2bpp[0], 0x81); // First byte of first plane: pixel0=3→0x80, pixel7=1→0x01
EXPECT_EQ(packed2bpp[1], 0x80); // First byte of second plane: pixel0=3→0x80, pixel7=1→0x00
EXPECT_EQ(packed2bpp[14], 0x01); // Last byte of first plane: pixel56=2→0x00, pixel63=3→0x01
EXPECT_EQ(packed2bpp[15], 0x81); // Last byte of second plane: pixel56=2→0x80, pixel63=3→0x01
EXPECT_EQ(packed2bpp[0],
0x81); // First byte of first plane: pixel0=3→0x80, pixel7=1→0x01
EXPECT_EQ(packed2bpp[1],
0x80); // First byte of second plane: pixel0=3→0x80, pixel7=1→0x00
EXPECT_EQ(packed2bpp[14],
0x01); // Last byte of first plane: pixel56=2→0x00, pixel63=3→0x01
EXPECT_EQ(packed2bpp[15],
0x81); // Last byte of second plane: pixel56=2→0x80, pixel63=3→0x01
}
TEST(SnesTileTest, ConvertBpp) {
@@ -107,11 +114,11 @@ TEST(SnesTileTest, ConvertBpp) {
// Test 4bpp to 2bpp conversion (using only colors 0-3 for valid 2bpp)
std::vector<uint8_t> data4bpp = {
// Planes 1&2 (rows 0-7) - create colors 0-3 only
0x80, 0x80, 0x40, 0x00, 0x20, 0x40, 0x10, 0x80, // rows 0-3
0x08, 0x00, 0x04, 0x40, 0x02, 0x80, 0x01, 0x00, // rows 4-7
0x80, 0x80, 0x40, 0x00, 0x20, 0x40, 0x10, 0x80, // rows 0-3
0x08, 0x00, 0x04, 0x40, 0x02, 0x80, 0x01, 0x00, // rows 4-7
// Planes 3&4 (rows 0-7) - all zeros to ensure colors stay ≤ 3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 0-3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // rows 4-7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 0-3
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // rows 4-7
};
auto converted2bpp = gfx::ConvertBpp(data4bpp, 4, 2);
EXPECT_EQ(converted2bpp.size(), 16); // 2bpp tile is 16 bytes
@@ -126,7 +133,7 @@ TEST(SnesTileTest, TileInfo) {
EXPECT_TRUE(info.horizontal_mirror_);
EXPECT_TRUE(info.over_);
// Test TileInfo from bytes
// Test TileInfo from bytes
gfx::TileInfo infoFromBytes(0x23, 0xED); // v=1, h=1, o=1, p=3, id=0x123
EXPECT_EQ(infoFromBytes.id_, 0x123);
EXPECT_EQ(infoFromBytes.palette_, 3);

View File

@@ -18,7 +18,7 @@ class CanvasAutomationAPITest : public ::testing::Test {
void SetUp() override {
// Create a test canvas with known dimensions
canvas_ = std::make_unique<gui::Canvas>("TestCanvas", ImVec2(512, 512),
gui::CanvasGridSize::k16x16);
gui::CanvasGridSize::k16x16);
api_ = canvas_->GetAutomationAPI();
ASSERT_NE(api_, nullptr);
}
@@ -34,21 +34,21 @@ class CanvasAutomationAPITest : public ::testing::Test {
TEST_F(CanvasAutomationAPITest, TileToCanvas_BasicConversion) {
// At 1.0x zoom, tile (0,0) should be at canvas (0,0)
canvas_->set_global_scale(1.0f);
ImVec2 pos = api_->TileToCanvas(0, 0);
EXPECT_FLOAT_EQ(pos.x, 0.0f);
EXPECT_FLOAT_EQ(pos.y, 0.0f);
// Tile (1,0) should be at (16,0) for 16x16 grid
pos = api_->TileToCanvas(1, 0);
EXPECT_FLOAT_EQ(pos.x, 16.0f);
EXPECT_FLOAT_EQ(pos.y, 0.0f);
// Tile (0,1) should be at (0,16)
pos = api_->TileToCanvas(0, 1);
EXPECT_FLOAT_EQ(pos.x, 0.0f);
EXPECT_FLOAT_EQ(pos.y, 16.0f);
// Tile (10,10) should be at (160,160)
pos = api_->TileToCanvas(10, 10);
EXPECT_FLOAT_EQ(pos.x, 160.0f);
@@ -58,11 +58,11 @@ TEST_F(CanvasAutomationAPITest, TileToCanvas_BasicConversion) {
TEST_F(CanvasAutomationAPITest, TileToCanvas_WithZoom) {
// At 2.0x zoom, tile coordinates should scale
canvas_->set_global_scale(2.0f);
ImVec2 pos = api_->TileToCanvas(1, 1);
EXPECT_FLOAT_EQ(pos.x, 32.0f); // 1 * 16 * 2.0
EXPECT_FLOAT_EQ(pos.y, 32.0f);
// At 0.5x zoom, tile coordinates should scale down
canvas_->set_global_scale(0.5f);
pos = api_->TileToCanvas(10, 10);
@@ -72,17 +72,17 @@ TEST_F(CanvasAutomationAPITest, TileToCanvas_WithZoom) {
TEST_F(CanvasAutomationAPITest, CanvasToTile_BasicConversion) {
canvas_->set_global_scale(1.0f);
// Canvas (0,0) should be tile (0,0)
ImVec2 tile = api_->CanvasToTile(ImVec2(0, 0));
EXPECT_FLOAT_EQ(tile.x, 0.0f);
EXPECT_FLOAT_EQ(tile.y, 0.0f);
// Canvas (16,16) should be tile (1,1)
tile = api_->CanvasToTile(ImVec2(16, 16));
EXPECT_FLOAT_EQ(tile.x, 1.0f);
EXPECT_FLOAT_EQ(tile.y, 1.0f);
// Canvas (160,160) should be tile (10,10)
tile = api_->CanvasToTile(ImVec2(160, 160));
EXPECT_FLOAT_EQ(tile.x, 10.0f);
@@ -92,11 +92,11 @@ TEST_F(CanvasAutomationAPITest, CanvasToTile_BasicConversion) {
TEST_F(CanvasAutomationAPITest, CanvasToTile_WithZoom) {
// At 2.0x zoom
canvas_->set_global_scale(2.0f);
ImVec2 tile = api_->CanvasToTile(ImVec2(32, 32));
EXPECT_FLOAT_EQ(tile.x, 1.0f); // 32 / (16 * 2.0)
EXPECT_FLOAT_EQ(tile.y, 1.0f);
// At 0.5x zoom
canvas_->set_global_scale(0.5f);
tile = api_->CanvasToTile(ImVec2(8, 8));
@@ -106,12 +106,12 @@ TEST_F(CanvasAutomationAPITest, CanvasToTile_WithZoom) {
TEST_F(CanvasAutomationAPITest, CoordinateRoundTrip) {
canvas_->set_global_scale(1.0f);
// Test round-trip conversion
for (int i = 0; i < 32; ++i) {
ImVec2 canvas_pos = api_->TileToCanvas(i, i);
ImVec2 tile_pos = api_->CanvasToTile(canvas_pos);
EXPECT_FLOAT_EQ(tile_pos.x, static_cast<float>(i));
EXPECT_FLOAT_EQ(tile_pos.y, static_cast<float>(i));
}
@@ -131,7 +131,7 @@ TEST_F(CanvasAutomationAPITest, IsInBounds_InvalidCoordinates) {
EXPECT_FALSE(api_->IsInBounds(-1, 0));
EXPECT_FALSE(api_->IsInBounds(0, -1));
EXPECT_FALSE(api_->IsInBounds(-1, -1));
EXPECT_FALSE(api_->IsInBounds(32, 0)); // Out of bounds
EXPECT_FALSE(api_->IsInBounds(32, 0)); // Out of bounds
EXPECT_FALSE(api_->IsInBounds(0, 32));
EXPECT_FALSE(api_->IsInBounds(100, 100));
}
@@ -147,11 +147,11 @@ TEST_F(CanvasAutomationAPITest, SetTileAt_WithCallback) {
painted_tiles.push_back({x, y, tile_id});
return true;
});
// Paint some tiles
EXPECT_TRUE(api_->SetTileAt(5, 5, 42));
EXPECT_TRUE(api_->SetTileAt(10, 10, 100));
ASSERT_EQ(painted_tiles.size(), 2);
EXPECT_EQ(painted_tiles[0], std::make_tuple(5, 5, 42));
EXPECT_EQ(painted_tiles[1], std::make_tuple(10, 10, 100));
@@ -163,12 +163,12 @@ TEST_F(CanvasAutomationAPITest, SetTileAt_OutOfBounds) {
callback_called = true;
return true;
});
// Out of bounds tiles should return false without calling callback
EXPECT_FALSE(api_->SetTileAt(-1, 0, 42));
EXPECT_FALSE(api_->SetTileAt(0, -1, 42));
EXPECT_FALSE(api_->SetTileAt(100, 100, 42));
EXPECT_FALSE(callback_called);
}
@@ -177,7 +177,7 @@ TEST_F(CanvasAutomationAPITest, GetTileAt_WithCallback) {
api_->SetTileQueryCallback([](int x, int y) {
return x * 100 + y; // Simple deterministic value
});
EXPECT_EQ(api_->GetTileAt(0, 0), 0);
EXPECT_EQ(api_->GetTileAt(1, 0), 100);
EXPECT_EQ(api_->GetTileAt(0, 1), 1);
@@ -186,7 +186,7 @@ TEST_F(CanvasAutomationAPITest, GetTileAt_WithCallback) {
TEST_F(CanvasAutomationAPITest, GetTileAt_OutOfBounds) {
api_->SetTileQueryCallback([](int x, int y) { return 42; });
EXPECT_EQ(api_->GetTileAt(-1, 0), -1);
EXPECT_EQ(api_->GetTileAt(0, -1), -1);
EXPECT_EQ(api_->GetTileAt(100, 100), -1);
@@ -198,15 +198,10 @@ TEST_F(CanvasAutomationAPITest, SetTiles_BatchOperation) {
painted_tiles.push_back({x, y, tile_id});
return true;
});
std::vector<std::tuple<int, int, int>> tiles_to_paint = {
{0, 0, 10},
{1, 0, 11},
{2, 0, 12},
{0, 1, 20},
{1, 1, 21}
};
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}, {0, 1, 20}, {1, 1, 21}};
EXPECT_TRUE(api_->SetTiles(tiles_to_paint));
EXPECT_EQ(painted_tiles.size(), 5);
}
@@ -217,7 +212,7 @@ TEST_F(CanvasAutomationAPITest, SetTiles_BatchOperation) {
TEST_F(CanvasAutomationAPITest, SelectTile) {
api_->SelectTile(5, 5);
auto selection = api_->GetSelection();
EXPECT_TRUE(selection.has_selection);
EXPECT_EQ(selection.selected_tiles.size(), 1);
@@ -225,13 +220,13 @@ TEST_F(CanvasAutomationAPITest, SelectTile) {
TEST_F(CanvasAutomationAPITest, SelectTileRect) {
api_->SelectTileRect(5, 5, 9, 9);
auto selection = api_->GetSelection();
EXPECT_TRUE(selection.has_selection);
// 5x5 rectangle = 25 tiles
EXPECT_EQ(selection.selected_tiles.size(), 25);
// Check first and last tiles
EXPECT_FLOAT_EQ(selection.selected_tiles[0].x, 5.0f);
EXPECT_FLOAT_EQ(selection.selected_tiles[0].y, 5.0f);
@@ -242,7 +237,7 @@ TEST_F(CanvasAutomationAPITest, SelectTileRect) {
TEST_F(CanvasAutomationAPITest, SelectTileRect_SwappedCoordinates) {
// Should handle coordinates in any order
api_->SelectTileRect(9, 9, 5, 5); // Reversed
auto selection = api_->GetSelection();
EXPECT_TRUE(selection.has_selection);
EXPECT_EQ(selection.selected_tiles.size(), 25);
@@ -250,12 +245,12 @@ TEST_F(CanvasAutomationAPITest, SelectTileRect_SwappedCoordinates) {
TEST_F(CanvasAutomationAPITest, ClearSelection) {
api_->SelectTileRect(5, 5, 10, 10);
auto selection = api_->GetSelection();
EXPECT_TRUE(selection.has_selection);
api_->ClearSelection();
selection = api_->GetSelection();
EXPECT_FALSE(selection.has_selection);
EXPECT_EQ(selection.selected_tiles.size(), 0);
@@ -265,7 +260,7 @@ TEST_F(CanvasAutomationAPITest, SelectTile_OutOfBounds) {
api_->SelectTile(-1, 0);
auto selection = api_->GetSelection();
EXPECT_FALSE(selection.has_selection);
api_->SelectTile(100, 100);
selection = api_->GetSelection();
EXPECT_FALSE(selection.has_selection);
@@ -278,10 +273,10 @@ TEST_F(CanvasAutomationAPITest, SelectTile_OutOfBounds) {
TEST_F(CanvasAutomationAPITest, SetZoom_ValidRange) {
api_->SetZoom(1.0f);
EXPECT_FLOAT_EQ(api_->GetZoom(), 1.0f);
api_->SetZoom(2.0f);
EXPECT_FLOAT_EQ(api_->GetZoom(), 2.0f);
api_->SetZoom(0.5f);
EXPECT_FLOAT_EQ(api_->GetZoom(), 0.5f);
}
@@ -290,10 +285,10 @@ TEST_F(CanvasAutomationAPITest, SetZoom_Clamping) {
// Should clamp to 0.25 - 4.0 range
api_->SetZoom(10.0f);
EXPECT_LE(api_->GetZoom(), 4.0f);
api_->SetZoom(0.1f);
EXPECT_GE(api_->GetZoom(), 0.25f);
api_->SetZoom(-1.0f);
EXPECT_GE(api_->GetZoom(), 0.25f);
}
@@ -303,7 +298,7 @@ TEST_F(CanvasAutomationAPITest, ScrollToTile_ValidTile) {
api_->ScrollToTile(0, 0, true);
api_->ScrollToTile(10, 10, false);
api_->ScrollToTile(15, 15, true);
// Just verify no crash - actual scroll behavior depends on ImGui state
}
@@ -311,7 +306,7 @@ TEST_F(CanvasAutomationAPITest, ScrollToTile_OutOfBounds) {
// Should handle out of bounds gracefully
api_->ScrollToTile(-1, 0, true);
api_->ScrollToTile(100, 100, true);
// Should not crash
}
@@ -320,8 +315,9 @@ TEST_F(CanvasAutomationAPITest, CenterOn_ValidTile) {
api_->CenterOn(10, 10);
api_->CenterOn(0, 0);
api_->CenterOn(20, 20);
// Verify scroll position changed (should be non-zero after centering on non-origin)
// Verify scroll position changed (should be non-zero after centering on
// non-origin)
ImVec2 scroll = canvas_->scrolling();
// Scroll values will depend on canvas size, just verify they're set
}
@@ -329,7 +325,7 @@ TEST_F(CanvasAutomationAPITest, CenterOn_ValidTile) {
TEST_F(CanvasAutomationAPITest, CenterOn_OutOfBounds) {
api_->CenterOn(-1, 0);
api_->CenterOn(100, 100);
// Should not crash
}
@@ -339,32 +335,32 @@ TEST_F(CanvasAutomationAPITest, CenterOn_OutOfBounds) {
TEST_F(CanvasAutomationAPITest, GetDimensions) {
canvas_->set_global_scale(1.0f);
auto dims = api_->GetDimensions();
EXPECT_EQ(dims.tile_size, 16); // 16x16 grid
EXPECT_EQ(dims.tile_size, 16); // 16x16 grid
EXPECT_EQ(dims.width_tiles, 32); // 512 / 16
EXPECT_EQ(dims.height_tiles, 32);
}
TEST_F(CanvasAutomationAPITest, GetDimensions_WithZoom) {
canvas_->set_global_scale(2.0f);
auto dims = api_->GetDimensions();
EXPECT_EQ(dims.tile_size, 16);
EXPECT_EQ(dims.width_tiles, 16); // 512 / (16 * 2.0)
EXPECT_EQ(dims.width_tiles, 16); // 512 / (16 * 2.0)
EXPECT_EQ(dims.height_tiles, 16);
}
TEST_F(CanvasAutomationAPITest, GetVisibleRegion) {
canvas_->set_global_scale(1.0f);
canvas_->set_scrolling(ImVec2(0, 0));
auto region = api_->GetVisibleRegion();
// At origin with no scroll, should start at (0,0)
EXPECT_GE(region.min_x, 0);
EXPECT_GE(region.min_y, 0);
// Should have valid bounds
EXPECT_GE(region.max_x, region.min_x);
EXPECT_GE(region.max_y, region.min_y);
@@ -373,11 +369,11 @@ TEST_F(CanvasAutomationAPITest, GetVisibleRegion) {
TEST_F(CanvasAutomationAPITest, IsTileVisible_AtOrigin) {
canvas_->set_global_scale(1.0f);
canvas_->set_scrolling(ImVec2(0, 0));
// Tiles at origin should be visible
EXPECT_TRUE(api_->IsTileVisible(0, 0));
EXPECT_TRUE(api_->IsTileVisible(1, 1));
// Tiles far away might not be visible (depends on canvas size)
// We just verify the method doesn't crash
api_->IsTileVisible(50, 50);
@@ -396,36 +392,33 @@ TEST_F(CanvasAutomationAPITest, IsTileVisible_OutOfBounds) {
TEST_F(CanvasAutomationAPITest, CompleteWorkflow) {
// Simulate a complete automation workflow
// 1. Set zoom level
api_->SetZoom(1.0f);
EXPECT_FLOAT_EQ(api_->GetZoom(), 1.0f);
// 2. Select a tile region
api_->SelectTileRect(0, 0, 4, 4);
auto selection = api_->GetSelection();
EXPECT_EQ(selection.selected_tiles.size(), 25);
// 3. Query tile data with callback
api_->SetTileQueryCallback([](int x, int y) {
return x + y * 100;
});
api_->SetTileQueryCallback([](int x, int y) { return x + y * 100; });
EXPECT_EQ(api_->GetTileAt(2, 3), 302);
// 4. Paint tiles with callback
std::vector<std::tuple<int, int, int>> painted;
api_->SetTilePaintCallback([&](int x, int y, int tile_id) {
painted.push_back({x, y, tile_id});
return true;
});
std::vector<std::tuple<int, int, int>> tiles = {
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}
};
{0, 0, 10}, {1, 0, 11}, {2, 0, 12}};
EXPECT_TRUE(api_->SetTiles(tiles));
EXPECT_EQ(painted.size(), 3);
// 5. Clear selection
api_->ClearSelection();
selection = api_->GetSelection();
@@ -434,19 +427,19 @@ TEST_F(CanvasAutomationAPITest, CompleteWorkflow) {
TEST_F(CanvasAutomationAPITest, DifferentGridSizes) {
// Test with 8x8 grid
auto canvas_8x8 = std::make_unique<gui::Canvas>(
"Test8x8", ImVec2(512, 512), gui::CanvasGridSize::k8x8);
auto canvas_8x8 = std::make_unique<gui::Canvas>("Test8x8", ImVec2(512, 512),
gui::CanvasGridSize::k8x8);
auto api_8x8 = canvas_8x8->GetAutomationAPI();
auto dims = api_8x8->GetDimensions();
EXPECT_EQ(dims.tile_size, 8);
EXPECT_EQ(dims.width_tiles, 64); // 512 / 8
// Test with 32x32 grid
auto canvas_32x32 = std::make_unique<gui::Canvas>(
"Test32x32", ImVec2(512, 512), gui::CanvasGridSize::k32x32);
auto api_32x32 = canvas_32x32->GetAutomationAPI();
dims = api_32x32->GetDimensions();
EXPECT_EQ(dims.tile_size, 32);
EXPECT_EQ(dims.width_tiles, 16); // 512 / 32
@@ -454,19 +447,19 @@ TEST_F(CanvasAutomationAPITest, DifferentGridSizes) {
TEST_F(CanvasAutomationAPITest, MultipleZoomLevels) {
float zoom_levels[] = {0.25f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f};
for (float zoom : zoom_levels) {
api_->SetZoom(zoom);
float actual_zoom = api_->GetZoom();
// Should be clamped to valid range
EXPECT_GE(actual_zoom, 0.25f);
EXPECT_LE(actual_zoom, 4.0f);
// Coordinate conversion should still work
ImVec2 canvas_pos = api_->TileToCanvas(10, 10);
ImVec2 tile_pos = api_->CanvasToTile(canvas_pos);
EXPECT_FLOAT_EQ(tile_pos.x, 10.0f);
EXPECT_FLOAT_EQ(tile_pos.y, 10.0f);
}
@@ -474,4 +467,3 @@ TEST_F(CanvasAutomationAPITest, MultipleZoomLevels) {
} // namespace test
} // namespace yaze

View File

@@ -1,8 +1,7 @@
#include "app/gui/canvas/canvas.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "app/gui/canvas/canvas.h"
#include "testing.h"
namespace yaze {
@@ -27,8 +26,8 @@ class CanvasCoordinateSyncTest : public ::testing::Test {
protected:
void SetUp() override {
// Create a test canvas with known dimensions (4096x4096 for overworld)
canvas_ = std::make_unique<gui::Canvas>("OverworldCanvas", ImVec2(4096, 4096),
gui::CanvasGridSize::k16x16);
canvas_ = std::make_unique<gui::Canvas>(
"OverworldCanvas", ImVec2(4096, 4096), gui::CanvasGridSize::k16x16);
canvas_->set_global_scale(1.0f);
}
@@ -100,16 +99,15 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_SmallMaps) {
// Simulate hover at different world positions
std::vector<ImVec2> test_positions = {
ImVec2(0, 0), // Map (0, 0)
ImVec2(512, 0), // Map (1, 0)
ImVec2(0, 512), // Map (0, 1)
ImVec2(512, 512), // Map (1, 1)
ImVec2(1536, 1024), // Map (3, 2)
ImVec2(0, 0), // Map (0, 0)
ImVec2(512, 0), // Map (1, 0)
ImVec2(0, 512), // Map (0, 1)
ImVec2(512, 512), // Map (1, 1)
ImVec2(1536, 1024), // Map (3, 2)
};
std::vector<std::pair<int, int>> expected_maps = {
{0, 0}, {1, 0}, {0, 1}, {1, 1}, {3, 2}
};
{0, 0}, {1, 0}, {0, 1}, {1, 1}, {3, 2}};
for (size_t i = 0; i < test_positions.size(); ++i) {
ImVec2 pos = test_positions[i];
@@ -127,15 +125,14 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_LargeMaps) {
// Large maps should span multiple standard map coordinates
std::vector<ImVec2> test_positions = {
ImVec2(0, 0), // Large map (0, 0)
ImVec2(1024, 0), // Large map (1, 0)
ImVec2(0, 1024), // Large map (0, 1)
ImVec2(2048, 2048), // Large map (2, 2)
ImVec2(0, 0), // Large map (0, 0)
ImVec2(1024, 0), // Large map (1, 0)
ImVec2(0, 1024), // Large map (0, 1)
ImVec2(2048, 2048), // Large map (2, 2)
};
std::vector<std::pair<int, int>> expected_large_maps = {
{0, 0}, {1, 0}, {0, 1}, {2, 2}
};
{0, 0}, {1, 0}, {0, 1}, {2, 2}};
for (size_t i = 0; i < test_positions.size(); ++i) {
ImVec2 pos = test_positions[i];
@@ -152,8 +149,8 @@ TEST_F(CanvasCoordinateSyncTest, MapCalculation_LargeMaps) {
// ============================================================================
TEST_F(CanvasCoordinateSyncTest, HoverPosition_ScaleInvariant) {
// REGRESSION TEST: Hover position should be in world space regardless of scale
// The bug was scale-dependent because it used screen coordinates
// REGRESSION TEST: Hover position should be in world space regardless of
// scale The bug was scale-dependent because it used screen coordinates
auto test_hover_at_scale = [&](float scale) {
canvas_->set_global_scale(scale);
@@ -186,7 +183,8 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapHighlight_UsesHoverNotDrawn) {
// The pattern used in DrawOverworldEdits (line 664) for painting:
auto drawn_pos = canvas_->drawn_tile_position();
// The pattern that SHOULD be used in CheckForCurrentMap (line 1041) for highlighting:
// The pattern that SHOULD be used in CheckForCurrentMap (line 1041) for
// highlighting:
auto hover_pos = canvas_->hover_mouse_pos();
// These are different methods for different purposes:
@@ -210,19 +208,19 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapIndex_From8x8Grid) {
};
std::vector<TestCase> test_cases = {
// Light World (0x00 - 0x3F)
{ImVec2(0, 0), 0, 0}, // Map 0 (Light World)
{ImVec2(512, 0), 0, 1}, // Map 1
{ImVec2(1024, 512), 0, 10}, // Map 10 = 2 + 1*8
// Light World (0x00 - 0x3F)
{ImVec2(0, 0), 0, 0}, // Map 0 (Light World)
{ImVec2(512, 0), 0, 1}, // Map 1
{ImVec2(1024, 512), 0, 10}, // Map 10 = 2 + 1*8
// Dark World (0x40 - 0x7F)
{ImVec2(0, 0), 1, 0x40}, // Map 0x40 (Dark World)
{ImVec2(512, 0), 1, 0x41}, // Map 0x41
{ImVec2(1024, 512), 1, 0x4A}, // Map 0x4A = 0x40 + 10
// Dark World (0x40 - 0x7F)
{ImVec2(0, 0), 1, 0x40}, // Map 0x40 (Dark World)
{ImVec2(512, 0), 1, 0x41}, // Map 0x41
{ImVec2(1024, 512), 1, 0x4A}, // Map 0x4A = 0x40 + 10
// Special World (0x80+)
{ImVec2(0, 0), 2, 0x80}, // Map 0x80 (Special World)
{ImVec2(512, 512), 2, 0x89}, // Map 0x89 = 0x80 + 9
// Special World (0x80+)
{ImVec2(0, 0), 2, 0x80}, // Map 0x80 (Special World)
{ImVec2(512, 512), 2, 0x89}, // Map 0x89 = 0x80 + 9
};
for (const auto& tc : test_cases) {
@@ -237,8 +235,8 @@ TEST_F(CanvasCoordinateSyncTest, OverworldMapIndex_From8x8Grid) {
}
EXPECT_EQ(hovered_map, tc.expected_map_index)
<< "Failed for world " << tc.current_world
<< " at position (" << tc.hover_pos.x << ", " << tc.hover_pos.y << ")";
<< "Failed for world " << tc.current_world << " at position ("
<< tc.hover_pos.x << ", " << tc.hover_pos.y << ")";
}
}
@@ -252,12 +250,12 @@ TEST_F(CanvasCoordinateSyncTest, MapBoundaries_512x512) {
// Boundary coordinates (edges of maps)
std::vector<ImVec2> boundary_positions = {
ImVec2(511, 0), // Right edge of map 0
ImVec2(512, 0), // Left edge of map 1
ImVec2(0, 511), // Bottom edge of map 0
ImVec2(0, 512), // Top edge of map 8
ImVec2(511, 511), // Corner of map 0
ImVec2(512, 512), // Corner of map 9
ImVec2(511, 0), // Right edge of map 0
ImVec2(512, 0), // Left edge of map 1
ImVec2(0, 511), // Bottom edge of map 0
ImVec2(0, 512), // Top edge of map 8
ImVec2(511, 511), // Corner of map 0
ImVec2(512, 512), // Corner of map 9
};
for (const auto& pos : boundary_positions) {
@@ -276,10 +274,10 @@ TEST_F(CanvasCoordinateSyncTest, MapBoundaries_1024x1024) {
const int kLargeMapSize = 1024;
std::vector<ImVec2> boundary_positions = {
ImVec2(1023, 0), // Right edge of large map 0
ImVec2(1024, 0), // Left edge of large map 1
ImVec2(0, 1023), // Bottom edge of large map 0
ImVec2(0, 1024), // Top edge of large map 4 (0,1 in 4x4 grid)
ImVec2(1023, 0), // Right edge of large map 0
ImVec2(1024, 0), // Left edge of large map 1
ImVec2(0, 1023), // Bottom edge of large map 0
ImVec2(0, 1024), // Top edge of large map 4 (0,1 in 4x4 grid)
};
for (const auto& pos : boundary_positions) {

View File

@@ -18,7 +18,7 @@ class TileSelectorWidgetTest : public ::testing::Test {
void SetUp() override {
// Create a test canvas
canvas_ = std::make_unique<gui::Canvas>("TestCanvas", ImVec2(512, 512),
gui::CanvasGridSize::k16x16);
gui::CanvasGridSize::k16x16);
// Create a test config
config_.tile_size = 16;
@@ -97,16 +97,16 @@ TEST_F(TileSelectorWidgetTest, TileOrigin) {
// Test tile at (1,0)
origin = widget.TileOrigin(1);
float expected_x = config_.draw_offset.x +
(config_.tile_size * config_.display_scale);
float expected_x =
config_.draw_offset.x + (config_.tile_size * config_.display_scale);
EXPECT_FLOAT_EQ(origin.x, expected_x);
EXPECT_FLOAT_EQ(origin.y, config_.draw_offset.y);
// Test tile at (0,1) - first tile of second row
origin = widget.TileOrigin(8);
expected_x = config_.draw_offset.x;
float expected_y = config_.draw_offset.y +
(config_.tile_size * config_.display_scale);
float expected_y =
config_.draw_offset.y + (config_.tile_size * config_.display_scale);
EXPECT_FLOAT_EQ(origin.x, expected_x);
EXPECT_FLOAT_EQ(origin.y, expected_y);
@@ -191,4 +191,3 @@ TEST_F(TileSelectorWidgetTest, DifferentConfigs) {
} // namespace test
} // namespace yaze

View File

@@ -5,9 +5,9 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/transaction.h"
#include "mocks/mock_rom.h"
#include "testing.h"
#include "app/transaction.h"
namespace yaze {
namespace test {
@@ -196,7 +196,8 @@ TEST_F(RomTest, SaveTruncatesExistingFile) {
#if defined(__linux__)
GTEST_SKIP();
#endif
// Prepare ROM data and save to a temp file twice; second save should overwrite, not append
// Prepare ROM data and save to a temp file twice; second save should
// overwrite, not append
EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false));
const char* tmp_name = "test_temp_rom.sfc";
@@ -211,7 +212,8 @@ TEST_F(RomTest, SaveTruncatesExistingFile) {
EXPECT_OK(rom_.WriteByte(0, 0xEE));
EXPECT_OK(rom_.SaveToFile(settings));
// Load the saved file and verify size equals original data size and first byte matches
// Load the saved file and verify size equals original data size and first
// byte matches
Rom verify;
EXPECT_OK(verify.LoadFromFile(tmp_name, /*z3_load=*/false));
EXPECT_EQ(verify.size(), kMockRomData.size());
@@ -224,9 +226,10 @@ TEST_F(RomTest, TransactionRollbackRestoresOriginals) {
EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false));
// Force an out-of-range write to trigger failure after a successful write
yaze::Transaction tx{rom_};
auto status = tx.WriteByte(0x01, 0xAA) // valid
.WriteWord(0xFFFF, 0xBBBB) // invalid: should fail and rollback
.Commit();
auto status =
tx.WriteByte(0x01, 0xAA) // valid
.WriteWord(0xFFFF, 0xBBBB) // invalid: should fail and rollback
.Commit();
EXPECT_FALSE(status.ok());
auto b1 = rom_.ReadByte(0x01);
ASSERT_TRUE(b1.ok());

View File

@@ -29,7 +29,7 @@ TEST_F(SnesColorTest, SetRgbFromImGuiNormalizedValues) {
// Internal storage should be in 0-255 range
auto rgb = color.rgb();
EXPECT_FLOAT_EQ(rgb.x, 127.5f); // 0.5 * 255
EXPECT_FLOAT_EQ(rgb.x, 127.5f); // 0.5 * 255
EXPECT_FLOAT_EQ(rgb.y, 191.25f); // 0.75 * 255
EXPECT_FLOAT_EQ(rgb.z, 255.0f); // 1.0 * 255
EXPECT_FLOAT_EQ(rgb.w, 255.0f); // Alpha always 255

View File

@@ -1,9 +1,8 @@
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "app/gfx/background_buffer.h"
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "gtest/gtest.h"
#include "zelda3/dungeon/object_drawer.h"
#include "zelda3/dungeon/object_parser.h"
#include "zelda3/dungeon/room_object.h"
@@ -17,33 +16,31 @@ class ObjectRenderingTest : public ::testing::Test {
// Create a mock ROM for testing
rom_ = std::make_unique<Rom>();
// Initialize with minimal ROM data for testing
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
std::vector<uint8_t> mock_rom_data(1024 * 1024, 0); // 1MB mock ROM
rom_->LoadFromData(mock_rom_data);
}
void TearDown() override {
rom_.reset();
}
void TearDown() override { rom_.reset(); }
std::unique_ptr<Rom> rom_;
gfx::BackgroundBuffer bg1_;
gfx::BackgroundBuffer bg2_;
// Create a test palette
gfx::SnesPalette CreateTestPalette() {
gfx::SnesPalette palette;
// Add some test colors
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
palette.AddColor(gfx::SnesColor(0, 0, 0)); // Transparent
palette.AddColor(gfx::SnesColor(255, 0, 0)); // Red
palette.AddColor(gfx::SnesColor(0, 255, 0)); // Green
palette.AddColor(gfx::SnesColor(0, 0, 255)); // Blue
palette.AddColor(gfx::SnesColor(255, 255, 0)); // Yellow
palette.AddColor(gfx::SnesColor(255, 0, 255)); // Magenta
palette.AddColor(gfx::SnesColor(0, 255, 255)); // Cyan
palette.AddColor(gfx::SnesColor(255, 255, 255)); // White
return palette;
}
gfx::PaletteGroup CreateTestPaletteGroup() {
gfx::PaletteGroup group;
group.AddPalette(CreateTestPalette());
@@ -54,7 +51,7 @@ class ObjectRenderingTest : public ::testing::Test {
// Test object drawer initialization
TEST_F(ObjectRenderingTest, ObjectDrawerInitializesCorrectly) {
ObjectDrawer drawer(rom_.get());
// Test that drawer can be created without errors
EXPECT_NE(rom_.get(), nullptr);
}
@@ -62,31 +59,31 @@ TEST_F(ObjectRenderingTest, ObjectDrawerInitializesCorrectly) {
// Test object parser draw routine detection
TEST_F(ObjectRenderingTest, ObjectParserDetectsDrawRoutines) {
ObjectParser parser(rom_.get());
// Test common object IDs and their expected draw routines
auto info_00 = parser.GetObjectDrawInfo(0x00);
EXPECT_EQ(info_00.draw_routine_id, 0);
EXPECT_EQ(info_00.routine_name, "Rightwards2x2_1to15or32");
EXPECT_TRUE(info_00.is_horizontal);
auto info_01 = parser.GetObjectDrawInfo(0x01);
EXPECT_EQ(info_01.draw_routine_id, 1);
EXPECT_EQ(info_01.routine_name, "Rightwards2x4_1to15or26");
EXPECT_TRUE(info_01.is_horizontal);
auto info_09 = parser.GetObjectDrawInfo(0x09);
EXPECT_EQ(info_09.draw_routine_id, 5);
EXPECT_EQ(info_09.routine_name, "DiagonalAcute_1to16");
EXPECT_FALSE(info_09.is_horizontal);
auto info_34 = parser.GetObjectDrawInfo(0x34);
EXPECT_EQ(info_34.draw_routine_id, 16);
EXPECT_EQ(info_34.routine_name, "Rightwards1x1Solid_1to16_plus3");
EXPECT_TRUE(info_34.is_horizontal);
// Test unmapped object defaults to solid block routine
auto info_unknown = parser.GetObjectDrawInfo(0x999);
EXPECT_EQ(info_unknown.draw_routine_id, 16); // Default solid routine
EXPECT_EQ(info_unknown.draw_routine_id, 16); // Default solid routine
EXPECT_EQ(info_unknown.routine_name, "DefaultSolid");
}
@@ -94,25 +91,25 @@ TEST_F(ObjectRenderingTest, ObjectParserDetectsDrawRoutines) {
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesVariousObjectTypes) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
// Test object 0x00 (horizontal floor tile)
RoomObject floor_object(0x00, 10, 10, 3, 0); // ID, X, Y, size, layer
RoomObject floor_object(0x00, 10, 10, 3, 0); // ID, X, Y, size, layer
auto status = drawer.DrawObject(floor_object, bg1_, bg2_, palette_group);
// Should succeed even if tiles aren't loaded (graceful handling)
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test object 0x09 (diagonal stairs)
RoomObject stair_object(0x09, 15, 15, 5, 0);
stair_object.set_rom(rom_.get());
status = drawer.DrawObject(stair_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test object 0x34 (solid block)
RoomObject block_object(0x34, 20, 20, 1, 0);
block_object.set_rom(rom_.get());
status = drawer.DrawObject(block_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
@@ -121,18 +118,18 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesVariousObjectTypes) {
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesDifferentLayers) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
// Test BG1 layer object
RoomObject bg1_object(0x00, 5, 5, 2, 0); // Layer 0 = BG1
RoomObject bg1_object(0x00, 5, 5, 2, 0); // Layer 0 = BG1
bg1_object.set_rom(rom_.get());
auto status = drawer.DrawObject(bg1_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test BG2 layer object
RoomObject bg2_object(0x01, 10, 10, 2, 1); // Layer 1 = BG2
RoomObject bg2_object(0x01, 10, 10, 2, 1); // Layer 1 = BG2
bg2_object.set_rom(rom_.get());
status = drawer.DrawObject(bg2_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
@@ -141,25 +138,25 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesDifferentLayers) {
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesSizeVariations) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
// Test small object
RoomObject small_object(0x00, 5, 5, 1, 0); // Size = 1
RoomObject small_object(0x00, 5, 5, 1, 0); // Size = 1
small_object.set_rom(rom_.get());
auto status = drawer.DrawObject(small_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test large object
RoomObject large_object(0x00, 10, 10, 15, 0); // Size = 15
RoomObject large_object(0x00, 10, 10, 15, 0); // Size = 15
large_object.set_rom(rom_.get());
status = drawer.DrawObject(large_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test maximum size object
RoomObject max_object(0x00, 15, 15, 31, 0); // Size = 31 (0x1F)
RoomObject max_object(0x00, 15, 15, 31, 0); // Size = 31 (0x1F)
max_object.set_rom(rom_.get());
status = drawer.DrawObject(max_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
@@ -168,25 +165,25 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesSizeVariations) {
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesEdgeCases) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
// Test object at origin
RoomObject origin_object(0x34, 0, 0, 1, 0);
origin_object.set_rom(rom_.get());
auto status = drawer.DrawObject(origin_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test object with zero size
RoomObject zero_size_object(0x34, 10, 10, 0, 0);
zero_size_object.set_rom(rom_.get());
status = drawer.DrawObject(zero_size_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test object with maximum coordinates
RoomObject max_coord_object(0x34, 63, 63, 1, 0); // Near buffer edge
RoomObject max_coord_object(0x34, 63, 63, 1, 0); // Near buffer edge
max_coord_object.set_rom(rom_.get());
status = drawer.DrawObject(max_coord_object, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
@@ -195,20 +192,20 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesEdgeCases) {
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesMultipleObjects) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
std::vector<RoomObject> objects;
// Create various test objects
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
objects.emplace_back(0x01, 10, 10, 2, 0); // Vertical floor
objects.emplace_back(0x09, 15, 15, 4, 0); // Diagonal stairs
objects.emplace_back(0x34, 20, 20, 1, 1); // Solid block on BG2
objects.emplace_back(0x00, 5, 5, 3, 0); // Horizontal floor
objects.emplace_back(0x01, 10, 10, 2, 0); // Vertical floor
objects.emplace_back(0x09, 15, 15, 4, 0); // Diagonal stairs
objects.emplace_back(0x34, 20, 20, 1, 1); // Solid block on BG2
// Set ROM for all objects
for (auto& obj : objects) {
obj.set_rom(rom_.get());
}
auto status = drawer.DrawObjectList(objects, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
@@ -217,36 +214,36 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesMultipleObjects) {
TEST_F(ObjectRenderingTest, DrawRoutinesWorkCorrectly) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
// Test rightward patterns
RoomObject rightward_obj(0x00, 5, 5, 5, 0);
rightward_obj.set_rom(rom_.get());
auto status = drawer.DrawObject(rightward_obj, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test diagonal patterns
RoomObject diagonal_obj(0x09, 10, 10, 6, 0);
diagonal_obj.set_rom(rom_.get());
status = drawer.DrawObject(diagonal_obj, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Test solid block patterns
RoomObject solid_obj(0x34, 15, 15, 8, 0);
solid_obj.set_rom(rom_.get());
status = drawer.DrawObject(solid_obj, bg1_, bg2_, palette_group);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
}
// Test object drawer error handling
TEST_F(ObjectRenderingTest, ObjectDrawerHandlesErrorsGracefully) {
ObjectDrawer drawer(nullptr); // No ROM
ObjectDrawer drawer(nullptr); // No ROM
auto palette_group = CreateTestPaletteGroup();
RoomObject test_object(0x00, 5, 5, 1, 0);
auto status = drawer.DrawObject(test_object, bg1_, bg2_, palette_group);
EXPECT_FALSE(status.ok());
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
@@ -255,32 +252,30 @@ TEST_F(ObjectRenderingTest, ObjectDrawerHandlesErrorsGracefully) {
// Test object parser with various object IDs
TEST_F(ObjectRenderingTest, ObjectParserHandlesVariousObjectIDs) {
ObjectParser parser(rom_.get());
// Test subtype 1 objects (0x00-0xFF)
for (int id = 0; id <= 0x40; id += 4) { // Test every 4th object
for (int id = 0; id <= 0x40; id += 4) { // Test every 4th object
auto info = parser.GetObjectDrawInfo(id);
EXPECT_GE(info.draw_routine_id, 0);
EXPECT_LT(info.draw_routine_id, 25); // Should be within valid range
EXPECT_LT(info.draw_routine_id, 25); // Should be within valid range
EXPECT_FALSE(info.routine_name.empty());
}
// Test some specific important objects
std::vector<int16_t> important_objects = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x15, 0x16, 0x21, 0x22, 0x2F, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40
};
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
0x15, 0x16, 0x21, 0x22, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40};
for (int16_t obj_id : important_objects) {
auto info = parser.GetObjectDrawInfo(obj_id);
EXPECT_GE(info.draw_routine_id, 0);
EXPECT_LT(info.draw_routine_id, 25);
EXPECT_FALSE(info.routine_name.empty());
// Verify tile count is reasonable
EXPECT_GT(info.tile_count, 0);
EXPECT_LE(info.tile_count, 64); // Reasonable upper bound
EXPECT_LE(info.tile_count, 64); // Reasonable upper bound
}
}
@@ -288,35 +283,35 @@ TEST_F(ObjectRenderingTest, ObjectParserHandlesVariousObjectIDs) {
TEST_F(ObjectRenderingTest, ObjectDrawerPerformanceTest) {
ObjectDrawer drawer(rom_.get());
auto palette_group = CreateTestPaletteGroup();
std::vector<RoomObject> objects;
// Create 100 test objects
for (int i = 0; i < 100; ++i) {
int id = i % 65; // Cycle through object IDs 0-64
int x = (i * 2) % 60; // Spread across buffer
int id = i % 65; // Cycle through object IDs 0-64
int x = (i * 2) % 60; // Spread across buffer
int y = (i * 3) % 60;
int size = (i % 8) + 1; // Size 1-8
int layer = i % 2; // Alternate layers
int size = (i % 8) + 1; // Size 1-8
int layer = i % 2; // Alternate layers
objects.emplace_back(id, x, y, size, layer);
objects.back().set_rom(rom_.get());
}
// Time the drawing operation
auto start_time = std::chrono::high_resolution_clock::now();
auto status = drawer.DrawObjectList(objects, bg1_, bg2_, palette_group);
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
EXPECT_TRUE(status.ok() || status.code() == absl::StatusCode::kOk);
// Should complete in reasonable time (less than 1 second for 100 objects)
EXPECT_LT(duration.count(), 1000);
std::cout << "Drew 100 objects in " << duration.count() << "ms" << std::endl;
}

View File

@@ -1,6 +1,7 @@
// Tests for Room object manipulation methods (Phase 3)
#include <gtest/gtest.h>
#include "app/rom.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_object.h"
@@ -16,20 +17,20 @@ class RoomManipulationTest : public ::testing::Test {
// Create a minimal ROM for testing
std::vector<uint8_t> dummy_data(0x200000, 0);
rom_->LoadFromData(dummy_data, false);
room_ = std::make_unique<Room>(0, rom_.get());
}
std::unique_ptr<Rom> rom_;
std::unique_ptr<Room> room_;
};
TEST_F(RoomManipulationTest, AddObject) {
RoomObject obj(0x10, 10, 20, 3, 0);
auto status = room_->AddObject(obj);
ASSERT_TRUE(status.ok());
auto objects = room_->GetTileObjects();
EXPECT_EQ(objects.size(), 1);
EXPECT_EQ(objects[0].id_, 0x10);
@@ -40,7 +41,7 @@ TEST_F(RoomManipulationTest, AddObject) {
TEST_F(RoomManipulationTest, AddInvalidObject) {
// Invalid X position (> 63)
RoomObject obj(0x10, 100, 20, 3, 0);
auto status = room_->AddObject(obj);
EXPECT_FALSE(status.ok());
EXPECT_EQ(room_->GetTileObjects().size(), 0);
@@ -49,15 +50,15 @@ TEST_F(RoomManipulationTest, AddInvalidObject) {
TEST_F(RoomManipulationTest, RemoveObject) {
RoomObject obj1(0x10, 10, 20, 3, 0);
RoomObject obj2(0x20, 15, 25, 2, 1);
room_->AddObject(obj1);
room_->AddObject(obj2);
EXPECT_EQ(room_->GetTileObjects().size(), 2);
auto status = room_->RemoveObject(0);
ASSERT_TRUE(status.ok());
auto objects = room_->GetTileObjects();
EXPECT_EQ(objects.size(), 1);
EXPECT_EQ(objects[0].id_, 0x20);
@@ -71,11 +72,11 @@ TEST_F(RoomManipulationTest, RemoveInvalidIndex) {
TEST_F(RoomManipulationTest, UpdateObject) {
RoomObject obj(0x10, 10, 20, 3, 0);
room_->AddObject(obj);
RoomObject updated(0x20, 15, 25, 5, 1);
auto status = room_->UpdateObject(0, updated);
ASSERT_TRUE(status.ok());
auto objects = room_->GetTileObjects();
EXPECT_EQ(objects[0].id_, 0x20);
EXPECT_EQ(objects[0].x(), 15);
@@ -85,14 +86,14 @@ TEST_F(RoomManipulationTest, UpdateObject) {
TEST_F(RoomManipulationTest, FindObjectAt) {
RoomObject obj1(0x10, 10, 20, 3, 0);
RoomObject obj2(0x20, 15, 25, 2, 1);
room_->AddObject(obj1);
room_->AddObject(obj2);
auto result = room_->FindObjectAt(15, 25, 1);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.value(), 1);
auto not_found = room_->FindObjectAt(99, 99, 0);
EXPECT_FALSE(not_found.ok());
}
@@ -101,19 +102,19 @@ TEST_F(RoomManipulationTest, ValidateObject) {
// Valid Type 1 object
RoomObject valid1(0x10, 10, 20, 3, 0);
EXPECT_TRUE(room_->ValidateObject(valid1));
// Valid Type 2 object
RoomObject valid2(0x110, 30, 40, 0, 1);
EXPECT_TRUE(room_->ValidateObject(valid2));
// Invalid X (> 63)
RoomObject invalid_x(0x10, 100, 20, 3, 0);
EXPECT_FALSE(room_->ValidateObject(invalid_x));
// Invalid layer (> 2)
RoomObject invalid_layer(0x10, 10, 20, 3, 5);
EXPECT_FALSE(room_->ValidateObject(invalid_layer));
// Invalid size for Type 1 (> 15)
RoomObject invalid_size(0x10, 10, 20, 20, 0);
EXPECT_FALSE(room_->ValidateObject(invalid_size));
@@ -125,21 +126,21 @@ TEST_F(RoomManipulationTest, MultipleOperations) {
RoomObject obj(0x10 + i, i * 5, i * 6, i, 0);
ASSERT_TRUE(room_->AddObject(obj).ok());
}
EXPECT_EQ(room_->GetTileObjects().size(), 5);
// Update middle object
RoomObject updated(0x99, 30, 35, 7, 1);
ASSERT_TRUE(room_->UpdateObject(2, updated).ok());
// Verify update
auto objects = room_->GetTileObjects();
EXPECT_EQ(objects[2].id_, 0x99);
// Remove first object
ASSERT_TRUE(room_->RemoveObject(0).ok());
EXPECT_EQ(room_->GetTileObjects().size(), 4);
// Verify first object is now what was second
EXPECT_EQ(room_->GetTileObjects()[0].id_, 0x11);
}
@@ -149,16 +150,16 @@ TEST_F(RoomManipulationTest, LayerOrganization) {
RoomObject layer0_obj(0x10, 10, 10, 2, 0);
RoomObject layer1_obj(0x20, 20, 20, 3, 1);
RoomObject layer2_obj(0x30, 30, 30, 4, 2);
room_->AddObject(layer0_obj);
room_->AddObject(layer1_obj);
room_->AddObject(layer2_obj);
// Verify can find by layer
EXPECT_TRUE(room_->FindObjectAt(10, 10, 0).ok());
EXPECT_TRUE(room_->FindObjectAt(20, 20, 1).ok());
EXPECT_TRUE(room_->FindObjectAt(30, 30, 2).ok());
// Wrong layer should not find
EXPECT_FALSE(room_->FindObjectAt(10, 10, 1).ok());
}
@@ -166,4 +167,3 @@ TEST_F(RoomManipulationTest, LayerOrganization) {
} // namespace test
} // namespace zelda3
} // namespace yaze

View File

@@ -5,10 +5,10 @@
// correctly for all three object types (Type1, Type2, Type3) based on
// ZScream's proven implementation.
#include "zelda3/dungeon/room_object.h"
#include <gtest/gtest.h>
#include "zelda3/dungeon/room_object.h"
namespace yaze {
namespace zelda3 {
namespace {
@@ -45,15 +45,16 @@ TEST(RoomObjectEncodingTest, DetermineObjectTypeType3) {
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);
auto decoded =
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
@@ -69,10 +70,11 @@ TEST(RoomObjectEncodingTest, Type1MaxValues) {
// - 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);
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());
@@ -82,10 +84,11 @@ TEST(RoomObjectEncodingTest, Type1MaxValues) {
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);
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());
@@ -96,10 +99,11 @@ 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);
auto decoded =
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
EXPECT_EQ(decoded.size(), size) << "Failed for size " << size;
}
}
@@ -108,9 +112,9 @@ 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);
@@ -120,9 +124,9 @@ TEST(RoomObjectEncodingTest, Type1RealWorldExample1) {
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);
@@ -136,18 +140,19 @@ TEST(RoomObjectEncodingTest, Type1RealWorldExample2) {
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);
auto decoded =
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 1);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
@@ -161,10 +166,11 @@ TEST(RoomObjectEncodingTest, Type2MaxValues) {
// 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);
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());
@@ -173,12 +179,13 @@ TEST(RoomObjectEncodingTest, Type2MaxValues) {
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);
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);
@@ -191,18 +198,19 @@ TEST(RoomObjectEncodingTest, Type2RealWorldExample) {
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);
auto decoded =
RoomObject::DecodeObjectFromBytes(bytes.b1, bytes.b2, bytes.b3, 0);
// Verify
EXPECT_EQ(decoded.id_, obj.id_);
EXPECT_EQ(decoded.x(), obj.x());
@@ -211,12 +219,13 @@ TEST(RoomObjectEncodingTest, Type3EncodeDecodeChest) {
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);
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);
@@ -225,9 +234,9 @@ TEST(RoomObjectEncodingTest, Type3EncodeDcodeBigChest) {
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);
@@ -242,40 +251,46 @@ 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;
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);
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);
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);
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);
auto decoded3 =
RoomObject::DecodeObjectFromBytes(bytes3.b1, bytes3.b2, bytes3.b3, 0);
EXPECT_EQ(decoded3.id_, 0xF99);
}
@@ -283,13 +298,15 @@ 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);
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);
auto decoded2 =
RoomObject::DecodeObjectFromBytes(bytes2.b1, bytes2.b2, bytes2.b3, 0);
EXPECT_EQ(decoded2.x(), 0);
EXPECT_EQ(decoded2.y(), 0);
}
@@ -301,21 +318,21 @@ TEST(RoomObjectEncodingTest, ZeroPosition) {
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)
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;

View File

@@ -1,4 +1,5 @@
#include <gtest/gtest.h>
#include <memory>
// Test the individual components independently
@@ -10,7 +11,7 @@ namespace test {
/**
* @brief Unit tests for individual dungeon components
*
*
* These tests validate component behavior without requiring ROM files
* or complex graphics initialization.
*/
@@ -18,35 +19,31 @@ namespace test {
// Test DungeonToolset Component
TEST(DungeonToolsetTest, BasicFunctionality) {
editor::DungeonToolset toolset;
// Test initial state
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackgroundAny);
EXPECT_EQ(toolset.placement_type(), editor::DungeonToolset::kNoType);
// Test state changes
toolset.set_background_type(editor::DungeonToolset::kBackground1);
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground1);
toolset.set_placement_type(editor::DungeonToolset::kObject);
EXPECT_EQ(toolset.placement_type(), editor::DungeonToolset::kObject);
// Test all background types
toolset.set_background_type(editor::DungeonToolset::kBackground2);
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground2);
toolset.set_background_type(editor::DungeonToolset::kBackground3);
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackground3);
// Test all placement types
std::vector<editor::DungeonToolset::PlacementType> placement_types = {
editor::DungeonToolset::kSprite,
editor::DungeonToolset::kItem,
editor::DungeonToolset::kEntrance,
editor::DungeonToolset::kDoor,
editor::DungeonToolset::kChest,
editor::DungeonToolset::kBlock
};
editor::DungeonToolset::kSprite, editor::DungeonToolset::kItem,
editor::DungeonToolset::kEntrance, editor::DungeonToolset::kDoor,
editor::DungeonToolset::kChest, editor::DungeonToolset::kBlock};
for (auto type : placement_types) {
toolset.set_placement_type(type);
EXPECT_EQ(toolset.placement_type(), type);
@@ -56,47 +53,48 @@ TEST(DungeonToolsetTest, BasicFunctionality) {
// Test DungeonToolset Callbacks
TEST(DungeonToolsetTest, CallbackFunctionality) {
editor::DungeonToolset toolset;
// Test callback setup (should not crash)
bool undo_called = false;
bool redo_called = false;
bool palette_called = false;
toolset.SetUndoCallback([&undo_called]() { undo_called = true; });
toolset.SetRedoCallback([&redo_called]() { redo_called = true; });
toolset.SetPaletteToggleCallback([&palette_called]() { palette_called = true; });
toolset.SetPaletteToggleCallback(
[&palette_called]() { palette_called = true; });
// Callbacks are set but won't be triggered without UI interaction
// The fact that we can set them without crashing validates the interface
EXPECT_FALSE(undo_called); // Not called yet
EXPECT_FALSE(redo_called); // Not called yet
EXPECT_FALSE(undo_called); // Not called yet
EXPECT_FALSE(redo_called); // Not called yet
EXPECT_FALSE(palette_called); // Not called yet
}
// Test DungeonUsageTracker Component
TEST(DungeonUsageTrackerTest, BasicFunctionality) {
editor::DungeonUsageTracker tracker;
// Test initial state
EXPECT_TRUE(tracker.GetBlocksetUsage().empty());
EXPECT_TRUE(tracker.GetSpritesetUsage().empty());
EXPECT_TRUE(tracker.GetPaletteUsage().empty());
// Test initial selection state
EXPECT_EQ(tracker.GetSelectedBlockset(), 0xFFFF);
EXPECT_EQ(tracker.GetSelectedSpriteset(), 0xFFFF);
EXPECT_EQ(tracker.GetSelectedPalette(), 0xFFFF);
// Test selection setters
tracker.SetSelectedBlockset(0x01);
EXPECT_EQ(tracker.GetSelectedBlockset(), 0x01);
tracker.SetSelectedSpriteset(0x02);
EXPECT_EQ(tracker.GetSelectedSpriteset(), 0x02);
tracker.SetSelectedPalette(0x03);
EXPECT_EQ(tracker.GetSelectedPalette(), 0x03);
// Test clear functionality
tracker.ClearUsageStats();
EXPECT_EQ(tracker.GetSelectedBlockset(), 0xFFFF);
@@ -108,16 +106,16 @@ TEST(DungeonUsageTrackerTest, BasicFunctionality) {
TEST(ComponentArchitectureTest, FileSizeReduction) {
// This test validates that the refactoring actually reduced complexity
// by ensuring the component files exist and are reasonably sized
// The main dungeon_editor.cc should be significantly smaller
// Before: ~1444 lines, Target: ~400-600 lines
// We can't directly test file sizes, but we can test that
// the components exist and function properly
editor::DungeonToolset toolset;
editor::DungeonUsageTracker tracker;
// If we can create the components, the refactoring was successful
EXPECT_EQ(toolset.background_type(), editor::DungeonToolset::kBackgroundAny);
EXPECT_TRUE(tracker.GetBlocksetUsage().empty());

View File

@@ -1,6 +1,5 @@
#include "zelda3/dungeon/object_parser.h"
#include "gtest/gtest.h"
#include "zelda3/dungeon/object_parser.h"
namespace yaze {
namespace test {
@@ -12,7 +11,7 @@ class ObjectParserStructsTest : public ::testing::Test {
TEST_F(ObjectParserStructsTest, ObjectRoutineInfoDefaultConstructor) {
zelda3::ObjectRoutineInfo info;
EXPECT_EQ(info.routine_ptr, 0);
EXPECT_EQ(info.tile_ptr, 0);
EXPECT_EQ(info.tile_count, 0);
@@ -22,7 +21,7 @@ TEST_F(ObjectParserStructsTest, ObjectRoutineInfoDefaultConstructor) {
TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoDefaultConstructor) {
zelda3::ObjectSubtypeInfo info;
EXPECT_EQ(info.subtype, 0);
EXPECT_EQ(info.subtype_ptr, 0);
EXPECT_EQ(info.routine_ptr, 0);
@@ -31,7 +30,7 @@ TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoDefaultConstructor) {
TEST_F(ObjectParserStructsTest, ObjectSizeInfoDefaultConstructor) {
zelda3::ObjectSizeInfo info;
EXPECT_EQ(info.width_tiles, 0);
EXPECT_EQ(info.height_tiles, 0);
EXPECT_TRUE(info.is_horizontal);
@@ -41,13 +40,13 @@ TEST_F(ObjectParserStructsTest, ObjectSizeInfoDefaultConstructor) {
TEST_F(ObjectParserStructsTest, ObjectRoutineInfoAssignment) {
zelda3::ObjectRoutineInfo info;
info.routine_ptr = 0x12345;
info.tile_ptr = 0x67890;
info.tile_count = 8;
info.is_repeatable = true;
info.is_orientation_dependent = true;
EXPECT_EQ(info.routine_ptr, 0x12345);
EXPECT_EQ(info.tile_ptr, 0x67890);
EXPECT_EQ(info.tile_count, 8);
@@ -57,12 +56,12 @@ TEST_F(ObjectParserStructsTest, ObjectRoutineInfoAssignment) {
TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoAssignment) {
zelda3::ObjectSubtypeInfo info;
info.subtype = 2;
info.subtype_ptr = 0x83F0;
info.routine_ptr = 0x8470;
info.max_tile_count = 16;
EXPECT_EQ(info.subtype, 2);
EXPECT_EQ(info.subtype_ptr, 0x83F0);
EXPECT_EQ(info.routine_ptr, 0x8470);
@@ -71,13 +70,13 @@ TEST_F(ObjectParserStructsTest, ObjectSubtypeInfoAssignment) {
TEST_F(ObjectParserStructsTest, ObjectSizeInfoAssignment) {
zelda3::ObjectSizeInfo info;
info.width_tiles = 4;
info.height_tiles = 2;
info.is_horizontal = false;
info.is_repeatable = true;
info.repeat_count = 3;
EXPECT_EQ(info.width_tiles, 4);
EXPECT_EQ(info.height_tiles, 2);
EXPECT_FALSE(info.is_horizontal);

View File

@@ -1,8 +1,10 @@
#include "zelda3/overworld/overworld.h"
#include <gtest/gtest.h>
#include <memory>
#include "app/rom.h"
#include "zelda3/overworld/overworld.h"
#include "zelda3/overworld/overworld_map.h"
namespace yaze {
@@ -18,42 +20,44 @@ class OverworldTest : public ::testing::Test {
// Create a mock ROM for testing
rom_ = std::make_unique<Rom>();
// Initialize with minimal ROM data for testing
std::vector<uint8_t> mock_rom_data(0x200000, 0x00); // 2MB ROM filled with 0x00
std::vector<uint8_t> mock_rom_data(0x200000,
0x00); // 2MB ROM filled with 0x00
// Set up some basic ROM data that OverworldMap expects
mock_rom_data[0x140145] = 0xFF; // OverworldCustomASMHasBeenApplied = vanilla
mock_rom_data[0x140145] =
0xFF; // OverworldCustomASMHasBeenApplied = vanilla
// Message IDs (2 bytes per map)
for (int i = 0; i < 160; i++) { // 160 maps total
for (int i = 0; i < 160; i++) { // 160 maps total
mock_rom_data[0x3F51D + (i * 2)] = 0x00;
mock_rom_data[0x3F51D + (i * 2) + 1] = 0x00;
}
// Area graphics (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x7C9C + i] = 0x00;
}
// Area palettes (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x7D1C + i] = 0x00;
}
// Screen sizes (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x1788D + i] = 0x01; // Small area by default
mock_rom_data[0x1788D + i] = 0x01; // Small area by default
}
// Sprite sets (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x7A41 + i] = 0x00;
}
// Sprite palettes (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x7B41 + i] = 0x00;
}
// Music (1 byte per map)
for (int i = 0; i < 160; i++) {
mock_rom_data[0x14303 + i] = 0x00;
@@ -61,26 +65,26 @@ class OverworldTest : public ::testing::Test {
mock_rom_data[0x14303 + 0x80 + i] = 0x00;
mock_rom_data[0x14303 + 0xC0 + i] = 0x00;
}
// Dark World music
for (int i = 0; i < 64; i++) {
mock_rom_data[0x14403 + i] = 0x00;
}
// Special world graphics and palettes
for (int i = 0; i < 32; i++) {
mock_rom_data[0x16821 + i] = 0x00;
mock_rom_data[0x16831 + i] = 0x00;
}
// Special world sprite graphics and palettes
for (int i = 0; i < 32; i++) {
mock_rom_data[0x0166E1 + i] = 0x00;
mock_rom_data[0x016701 + i] = 0x00;
}
rom_->LoadFromData(mock_rom_data);
overworld_ = std::make_unique<Overworld>(rom_.get());
}
@@ -96,7 +100,7 @@ class OverworldTest : public ::testing::Test {
TEST_F(OverworldTest, OverworldMapInitialization) {
// Test that OverworldMap can be created with valid parameters
OverworldMap map(0, rom_.get());
EXPECT_EQ(map.area_graphics(), 0);
EXPECT_EQ(map.area_palette(), 0);
EXPECT_EQ(map.message_id(), 0);
@@ -117,27 +121,27 @@ TEST_F(OverworldTest, AreaSizeEnumValues) {
TEST_F(OverworldTest, OverworldMapSetters) {
OverworldMap map(0, rom_.get());
// Test main palette setter
map.set_main_palette(5);
EXPECT_EQ(map.main_palette(), 5);
// Test area-specific background color setter
map.set_area_specific_bg_color(0x7FFF);
EXPECT_EQ(map.area_specific_bg_color(), 0x7FFF);
// Test subscreen overlay setter
map.set_subscreen_overlay(0x1234);
EXPECT_EQ(map.subscreen_overlay(), 0x1234);
// Test animated GFX setter
map.set_animated_gfx(10);
EXPECT_EQ(map.animated_gfx(), 10);
// Test custom tileset setter
map.set_custom_tileset(0, 20);
EXPECT_EQ(map.custom_tileset(0), 20);
// Test area size setter
map.SetAreaSize(AreaSizeEnum::LargeArea);
EXPECT_EQ(map.area_size(), AreaSizeEnum::LargeArea);
@@ -145,14 +149,14 @@ TEST_F(OverworldTest, OverworldMapSetters) {
TEST_F(OverworldTest, OverworldMapLargeMapSetup) {
OverworldMap map(0, rom_.get());
// Test SetAsLargeMap
map.SetAsLargeMap(10, 2);
EXPECT_EQ(map.parent(), 10);
EXPECT_EQ(map.large_index(), 2);
EXPECT_TRUE(map.is_large_map());
EXPECT_EQ(map.area_size(), AreaSizeEnum::LargeArea);
// Test SetAsSmallMap
map.SetAsSmallMap(5);
EXPECT_EQ(map.parent(), 5);
@@ -163,13 +167,13 @@ TEST_F(OverworldTest, OverworldMapLargeMapSetup) {
TEST_F(OverworldTest, OverworldMapCustomTilesetArray) {
OverworldMap map(0, rom_.get());
// Test setting all 8 custom tileset slots
for (int i = 0; i < 8; i++) {
map.set_custom_tileset(i, i + 10);
EXPECT_EQ(map.custom_tileset(i), i + 10);
}
// Test mutable access
for (int i = 0; i < 8; i++) {
*map.mutable_custom_tileset(i) = i + 20;
@@ -179,21 +183,21 @@ TEST_F(OverworldTest, OverworldMapCustomTilesetArray) {
TEST_F(OverworldTest, OverworldMapSpriteProperties) {
OverworldMap map(0, rom_.get());
// Test sprite graphics setters
map.set_sprite_graphics(0, 1);
map.set_sprite_graphics(1, 2);
map.set_sprite_graphics(2, 3);
EXPECT_EQ(map.sprite_graphics(0), 1);
EXPECT_EQ(map.sprite_graphics(1), 2);
EXPECT_EQ(map.sprite_graphics(2), 3);
// Test sprite palette setters
map.set_sprite_palette(0, 4);
map.set_sprite_palette(1, 5);
map.set_sprite_palette(2, 6);
EXPECT_EQ(map.sprite_palette(0), 4);
EXPECT_EQ(map.sprite_palette(1), 5);
EXPECT_EQ(map.sprite_palette(2), 6);
@@ -201,52 +205,52 @@ TEST_F(OverworldTest, OverworldMapSpriteProperties) {
TEST_F(OverworldTest, OverworldMapBasicProperties) {
OverworldMap map(0, rom_.get());
// Test basic property setters
map.set_area_graphics(15);
EXPECT_EQ(map.area_graphics(), 15);
map.set_area_palette(8);
EXPECT_EQ(map.area_palette(), 8);
map.set_message_id(0x1234);
EXPECT_EQ(map.message_id(), 0x1234);
}
TEST_F(OverworldTest, OverworldMapMutableAccessors) {
OverworldMap map(0, rom_.get());
// Test mutable accessors
*map.mutable_area_graphics() = 25;
EXPECT_EQ(map.area_graphics(), 25);
*map.mutable_area_palette() = 12;
EXPECT_EQ(map.area_palette(), 12);
*map.mutable_message_id() = 0x5678;
EXPECT_EQ(map.message_id(), 0x5678);
*map.mutable_main_palette() = 7;
EXPECT_EQ(map.main_palette(), 7);
*map.mutable_animated_gfx() = 15;
EXPECT_EQ(map.animated_gfx(), 15);
*map.mutable_subscreen_overlay() = 0x9ABC;
EXPECT_EQ(map.subscreen_overlay(), 0x9ABC);
}
TEST_F(OverworldTest, OverworldMapDestroy) {
OverworldMap map(0, rom_.get());
// Set some properties
map.set_area_graphics(10);
map.set_main_palette(5);
map.SetAreaSize(AreaSizeEnum::LargeArea);
// Destroy and verify reset
map.Destroy();
EXPECT_EQ(map.area_graphics(), 0);
EXPECT_EQ(map.main_palette(), 0);
EXPECT_EQ(map.area_size(), AreaSizeEnum::SmallArea);
@@ -257,34 +261,34 @@ TEST_F(OverworldTest, OverworldMapDestroy) {
TEST_F(OverworldTest, WorldBasedSpriteFiltering) {
// This test verifies the logic used in DrawOverworldSprites
// for filtering sprites by world
int current_world = 1; // Dark World
int sprite_map_id = 0x50; // Map 0x50 (Dark World)
int current_world = 1; // Dark World
int sprite_map_id = 0x50; // Map 0x50 (Dark World)
// Test that sprite should be shown for Dark World
bool should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
sprite_map_id >= (current_world * 0x40));
EXPECT_TRUE(should_show);
// Test that sprite should NOT be shown for Light World
current_world = 0; // Light World
current_world = 0; // Light World
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
sprite_map_id >= (current_world * 0x40));
EXPECT_FALSE(should_show);
// Test boundary conditions
current_world = 1; // Dark World
sprite_map_id = 0x40; // First Dark World map
current_world = 1; // Dark World
sprite_map_id = 0x40; // First Dark World map
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
sprite_map_id >= (current_world * 0x40));
EXPECT_TRUE(should_show);
sprite_map_id = 0x7F; // Last Dark World map
sprite_map_id = 0x7F; // Last Dark World map
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
sprite_map_id >= (current_world * 0x40));
EXPECT_TRUE(should_show);
sprite_map_id = 0x80; // First Special World map
sprite_map_id = 0x80; // First Special World map
should_show = (sprite_map_id < 0x40 + (current_world * 0x40) &&
sprite_map_id >= (current_world * 0x40));
EXPECT_FALSE(should_show);

View File

@@ -14,7 +14,7 @@ namespace test {
/**
* @brief Simplified test framework for dungeon object rendering
*
*
* This provides a clean, focused testing environment for dungeon object
* functionality without the complexity of full integration tests.
*/
@@ -26,18 +26,18 @@ class TestDungeonObjects : public ::testing::Test {
// Test helpers
absl::Status CreateTestRom();
absl::Status SetupObjectData();
// Mock data generators
std::vector<uint8_t> CreateObjectSubtypeTable(int base_addr, int count);
std::vector<uint8_t> CreateTileData(int base_addr, int tile_count);
std::vector<uint8_t> CreateRoomHeader(int room_id);
std::unique_ptr<MockRom> test_rom_;
// Test constants
static constexpr int kTestObjectId = 0x01;
static constexpr int kTestRoomId = 0x00;
static constexpr size_t kTestRomSize = 0x100000; // 1MB test ROM
static constexpr size_t kTestRomSize = 0x100000; // 1MB test ROM
};
} // namespace test