fix(dungeon): align object drawing and tests
This commit is contained in:
@@ -1148,21 +1148,16 @@ std::optional<size_t> DungeonObjectEditor::FindObjectAt(int room_x,
|
|||||||
|
|
||||||
bool DungeonObjectEditor::IsObjectAtPosition(const RoomObject& object, int x,
|
bool DungeonObjectEditor::IsObjectAtPosition(const RoomObject& object, int x,
|
||||||
int y) {
|
int y) {
|
||||||
// Convert object position to pixel coordinates
|
// Coordinates are in room tiles.
|
||||||
int obj_x = object.x_ * 16;
|
int obj_x = object.x_;
|
||||||
int obj_y = object.y_ * 16;
|
int obj_y = object.y_;
|
||||||
|
|
||||||
// Check if point is within object bounds
|
// Simplified bounds: default to 1x1 tile, grow to 2x2 for large objects.
|
||||||
// This is a simplified implementation - in practice, you'd check
|
int obj_width = 1;
|
||||||
// against the actual tile data
|
int obj_height = 1;
|
||||||
|
|
||||||
int obj_width = 16; // Default object width
|
|
||||||
int obj_height = 16; // Default object height
|
|
||||||
|
|
||||||
// Adjust size based on object size value
|
|
||||||
if (object.size_ > 0x80) {
|
if (object.size_ > 0x80) {
|
||||||
obj_width *= 2;
|
obj_width = 2;
|
||||||
obj_height *= 2;
|
obj_height = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
|
return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
|
||||||
@@ -1225,7 +1220,14 @@ int DungeonObjectEditor::SnapToGrid(int coordinate) {
|
|||||||
return coordinate;
|
return coordinate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (coordinate / config_.grid_size) * config_.grid_size;
|
int grid_size = config_.grid_size;
|
||||||
|
if (grid_size <= 0) {
|
||||||
|
return coordinate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coordinates are in room tiles; map pixel grid size to tile steps.
|
||||||
|
int tile_step = std::max(1, grid_size / 16);
|
||||||
|
return (coordinate / tile_step) * tile_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DungeonObjectEditor::UpdatePreviewObject() {
|
void DungeonObjectEditor::UpdatePreviewObject() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "app/gfx/types/snes_tile.h"
|
#include "app/gfx/types/snes_tile.h"
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "core/features.h"
|
||||||
#include "rom/snes.h"
|
#include "rom/snes.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "zelda3/dungeon/draw_routines/draw_routine_registry.h"
|
#include "zelda3/dungeon/draw_routines/draw_routine_registry.h"
|
||||||
@@ -340,10 +341,15 @@ void ObjectDrawer::InitializeDrawRoutines() {
|
|||||||
object_to_routine_map_[0x30] = 24; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
|
object_to_routine_map_[0x30] = 24; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
|
||||||
|
|
||||||
// Custom Objects (0x31-0x32) - Oracle of Secrets minecart tracks and furniture
|
// Custom Objects (0x31-0x32) - Oracle of Secrets minecart tracks and furniture
|
||||||
// These use external binary files instead of ROM tile data.
|
// USDASM marks these as RoomDraw_Nothing; only map to custom routines when enabled.
|
||||||
// Requires CustomObjectManager initialization and enable_custom_objects feature flag.
|
if (core::FeatureFlags::get().kEnableCustomObjects) {
|
||||||
object_to_routine_map_[0x31] = DrawRoutineIds::kCustomObject; // Custom tracks
|
// These use external binary files instead of ROM tile data.
|
||||||
object_to_routine_map_[0x32] = DrawRoutineIds::kCustomObject; // Custom furniture
|
object_to_routine_map_[0x31] = DrawRoutineIds::kCustomObject; // Custom tracks
|
||||||
|
object_to_routine_map_[0x32] = DrawRoutineIds::kCustomObject; // Custom furniture
|
||||||
|
} else {
|
||||||
|
object_to_routine_map_[0x31] = DrawRoutineIds::kNothing;
|
||||||
|
object_to_routine_map_[0x32] = DrawRoutineIds::kNothing;
|
||||||
|
}
|
||||||
object_to_routine_map_[0x33] = 16; // 4x4 Block
|
object_to_routine_map_[0x33] = 16; // 4x4 Block
|
||||||
object_to_routine_map_[0x34] = 25; // Solid 1x1
|
object_to_routine_map_[0x34] = 25; // Solid 1x1
|
||||||
object_to_routine_map_[0x35] = 26; // Door Switcher
|
object_to_routine_map_[0x35] = 26; // Door Switcher
|
||||||
@@ -633,19 +639,10 @@ void ObjectDrawer::InitializeDrawRoutines() {
|
|||||||
object_to_routine_map_[id] = 39; // Chest draw routine
|
object_to_routine_map_[id] = 39; // Chest draw routine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtype 2 Object Mappings (0x100-0x1FF)
|
// Subtype 2 Object Mappings (0x100-0x13F)
|
||||||
// LAYOUT CORNERS: 0x100-0x103 are the concave corners used in room layouts
|
// ASM Reference: bank_01.asm .type1_subtype_2_routine ($018470)
|
||||||
// These must use DrawCorner4x4 (routine 19) NOT DrawRightwards4x4 (routine 16)
|
// 0x100-0x107: RoomDraw_4x4
|
||||||
// ASM Reference: bank_01.asm RoomDraw_4x4Corner routine
|
for (int id = 0x100; id <= 0x107; id++) {
|
||||||
// 0x100 = Corner (top, concave) ▛ (upper-left)
|
|
||||||
// 0x101 = Corner (top, concave) ▙ (lower-left)
|
|
||||||
// 0x102 = Corner (top, concave) ▜ (upper-right)
|
|
||||||
// 0x103 = Corner (top, concave) ▟ (lower-right)
|
|
||||||
for (int id = 0x100; id <= 0x103; id++) {
|
|
||||||
object_to_routine_map_[id] = 19; // DrawCorner4x4 for layout corners
|
|
||||||
}
|
|
||||||
// 0x104-0x107: Other 4x4 patterns (non-corner)
|
|
||||||
for (int id = 0x104; id <= 0x107; id++) {
|
|
||||||
object_to_routine_map_[id] = 16; // Rightwards 4x4
|
object_to_routine_map_[id] = 16; // Rightwards 4x4
|
||||||
}
|
}
|
||||||
for (int id = 0x108; id <= 0x10F; id++) {
|
for (int id = 0x108; id <= 0x10F; id++) {
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ class ObjectDrawer {
|
|||||||
*/
|
*/
|
||||||
int GetDrawRoutineId(int16_t object_id) const;
|
int GetDrawRoutineId(int16_t object_id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the total number of registered draw routines
|
||||||
|
*/
|
||||||
|
int GetDrawRoutineCount() const {
|
||||||
|
return static_cast<int>(draw_routines_.size());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize draw routine registry
|
* @brief Initialize draw routine registry
|
||||||
* Must be called before drawing objects
|
* Must be called before drawing objects
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(
|
|||||||
info.subtype_ptr = kRoomObjectSubtype2 + (index * 2);
|
info.subtype_ptr = kRoomObjectSubtype2 + (index * 2);
|
||||||
// Routine table starts 128 bytes (64 entries * 2 bytes) after data table
|
// Routine table starts 128 bytes (64 entries * 2 bytes) after data table
|
||||||
info.routine_ptr = kRoomObjectSubtype2 + 0x80 + (index * 2);
|
info.routine_ptr = kRoomObjectSubtype2 + 0x80 + (index * 2);
|
||||||
info.max_tile_count = 8;
|
info.max_tile_count = GetSubtype2TileCount(object_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: {
|
case 3: {
|
||||||
@@ -127,7 +127,7 @@ absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(
|
|||||||
int index = (object_id - 0xF80) & 0x7F;
|
int index = (object_id - 0xF80) & 0x7F;
|
||||||
info.subtype_ptr = kRoomObjectSubtype3 + (index * 2);
|
info.subtype_ptr = kRoomObjectSubtype3 + (index * 2);
|
||||||
info.routine_ptr = kRoomObjectSubtype3 + 0x100 + (index * 2);
|
info.routine_ptr = kRoomObjectSubtype3 + 0x100 + (index * 2);
|
||||||
info.max_tile_count = 8;
|
info.max_tile_count = GetSubtype3TileCount(object_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "test/test_utils.h"
|
||||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||||
#include "zelda3/dungeon/room.h"
|
#include "zelda3/dungeon/room.h"
|
||||||
@@ -21,12 +23,22 @@ class DungeonEditorSystemIntegrationTest : public ::testing::Test {
|
|||||||
GTEST_SKIP() << "Dungeon editor tests require ROM file (unavailable on Linux CI)";
|
GTEST_SKIP() << "Dungeon editor tests require ROM file (unavailable on Linux CI)";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use the real ROM from build directory
|
yaze::test::TestRomManager::SkipIfRomMissing(
|
||||||
rom_path_ = "build/bin/zelda3.sfc";
|
yaze::test::RomRole::kVanilla,
|
||||||
|
"DungeonEditorSystemIntegrationTest");
|
||||||
|
rom_path_ =
|
||||||
|
yaze::test::TestRomManager::GetRomPath(yaze::test::RomRole::kVanilla);
|
||||||
|
ASSERT_FALSE(rom_path_.empty())
|
||||||
|
<< "ROM path not set for vanilla role. "
|
||||||
|
<< yaze::test::TestRomManager::GetRomRoleHint(
|
||||||
|
yaze::test::RomRole::kVanilla);
|
||||||
|
|
||||||
// Load ROM
|
// Load ROM
|
||||||
rom_ = std::make_unique<Rom>();
|
rom_ = std::make_unique<Rom>();
|
||||||
ASSERT_TRUE(rom_->LoadFromFile(rom_path_).ok());
|
auto load_status = rom_->LoadFromFile(rom_path_);
|
||||||
|
ASSERT_TRUE(load_status.ok())
|
||||||
|
<< "Failed to load ROM from " << rom_path_ << ": "
|
||||||
|
<< load_status.message();
|
||||||
|
|
||||||
// Initialize dungeon editor system
|
// Initialize dungeon editor system
|
||||||
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
dungeon_editor_system_ = std::make_unique<DungeonEditorSystem>(rom_.get());
|
||||||
@@ -99,8 +111,8 @@ TEST_F(DungeonEditorSystemIntegrationTest, ObjectEditorIntegration) {
|
|||||||
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
ASSERT_TRUE(dungeon_editor_system_->SetCurrentRoom(0x0000).ok());
|
||||||
|
|
||||||
// Test object insertion
|
// Test object insertion
|
||||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x0F, 0).ok());
|
||||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x0F, 1).ok());
|
||||||
|
|
||||||
// Verify objects were added
|
// Verify objects were added
|
||||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||||
@@ -125,8 +137,8 @@ TEST_F(DungeonEditorSystemIntegrationTest, UndoRedoFunctionality) {
|
|||||||
ASSERT_NE(object_editor, nullptr);
|
ASSERT_NE(object_editor, nullptr);
|
||||||
|
|
||||||
// Add some objects
|
// Add some objects
|
||||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x0F, 0).ok());
|
||||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x0F, 1).ok());
|
||||||
|
|
||||||
// Verify objects were added
|
// Verify objects were added
|
||||||
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
EXPECT_EQ(object_editor->GetObjectCount(), 2);
|
||||||
@@ -158,8 +170,8 @@ TEST_F(DungeonEditorSystemIntegrationTest, SaveLoadFunctionality) {
|
|||||||
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
auto object_editor = dungeon_editor_system_->GetObjectEditor();
|
||||||
ASSERT_NE(object_editor, nullptr);
|
ASSERT_NE(object_editor, nullptr);
|
||||||
|
|
||||||
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x12, 0).ok());
|
ASSERT_TRUE(object_editor->InsertObject(5, 5, 0x10, 0x0F, 0).ok());
|
||||||
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x22, 1).ok());
|
ASSERT_TRUE(object_editor->InsertObject(10, 10, 0x20, 0x0F, 1).ok());
|
||||||
|
|
||||||
// Save room
|
// Save room
|
||||||
ASSERT_TRUE(dungeon_editor_system_->SaveRoom(0x0000).ok());
|
ASSERT_TRUE(dungeon_editor_system_->SaveRoom(0x0000).ok());
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "test/test_utils.h"
|
||||||
#include "zelda3/dungeon/object_drawer.h"
|
#include "zelda3/dungeon/object_drawer.h"
|
||||||
#include "zelda3/dungeon/room.h"
|
#include "zelda3/dungeon/room.h"
|
||||||
#include "zelda3/game_data.h"
|
#include "zelda3/game_data.h"
|
||||||
@@ -20,11 +22,11 @@ class DungeonGraphicsTransparencyTest : public ::testing::Test {
|
|||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
rom_ = std::make_unique<Rom>();
|
rom_ = std::make_unique<Rom>();
|
||||||
|
|
||||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
yaze::test::TestRomManager::SkipIfRomMissing(
|
||||||
if (!rom_path) {
|
yaze::test::RomRole::kVanilla,
|
||||||
rom_path = "zelda3.sfc";
|
"DungeonGraphicsTransparencyTest");
|
||||||
}
|
const std::string rom_path =
|
||||||
|
yaze::test::TestRomManager::GetRomPath(yaze::test::RomRole::kVanilla);
|
||||||
auto status = rom_->LoadFromFile(rom_path);
|
auto status = rom_->LoadFromFile(rom_path);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||||
@@ -78,7 +80,8 @@ TEST_F(DungeonGraphicsTransparencyTest, GraphicsBufferHasTransparentPixels) {
|
|||||||
// Test 2: Verify room graphics buffer after CopyRoomGraphicsToBuffer
|
// Test 2: Verify room graphics buffer after CopyRoomGraphicsToBuffer
|
||||||
TEST_F(DungeonGraphicsTransparencyTest, RoomGraphicsBufferHasTransparentPixels) {
|
TEST_F(DungeonGraphicsTransparencyTest, RoomGraphicsBufferHasTransparentPixels) {
|
||||||
// Create room 0 (Ganon's room - known to have walls)
|
// Create room 0 (Ganon's room - known to have walls)
|
||||||
Room room(0x00, rom_.get());
|
Room room = LoadRoomHeaderFromRom(rom_.get(), 0x00);
|
||||||
|
room.SetGameData(&game_data_);
|
||||||
room.LoadRoomGraphics(0xFF);
|
room.LoadRoomGraphics(0xFF);
|
||||||
room.CopyRoomGraphicsToBuffer();
|
room.CopyRoomGraphicsToBuffer();
|
||||||
|
|
||||||
@@ -86,15 +89,22 @@ TEST_F(DungeonGraphicsTransparencyTest, RoomGraphicsBufferHasTransparentPixels)
|
|||||||
const auto& gfx16 = room.get_gfx_buffer();
|
const auto& gfx16 = room.get_gfx_buffer();
|
||||||
ASSERT_GT(gfx16.size(), 0);
|
ASSERT_GT(gfx16.size(), 0);
|
||||||
|
|
||||||
// Count zeros in the room's graphics buffer
|
// Count zeros in the room's graphics buffer (background blocks only)
|
||||||
|
constexpr int kBlockSize = 4096;
|
||||||
|
constexpr int kBgBlocks = 8;
|
||||||
int zero_count = 0;
|
int zero_count = 0;
|
||||||
for (size_t i = 0; i < gfx16.size(); i++) {
|
int total_pixels = 0;
|
||||||
if (gfx16[i] == 0) zero_count++;
|
for (int block = 0; block < kBgBlocks; block++) {
|
||||||
|
int base = block * kBlockSize;
|
||||||
|
for (int i = 0; i < kBlockSize; i++) {
|
||||||
|
if (gfx16[base + i] == 0) zero_count++;
|
||||||
|
total_pixels++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float zero_percent = 100.0f * zero_count / gfx16.size();
|
float zero_percent = 100.0f * zero_count / total_pixels;
|
||||||
printf("[RoomGraphics] Room 0: Zeros: %d / %zu (%.1f%%)\n", zero_count,
|
printf("[RoomGraphics] Room 0: Zeros: %d / %d (%.1f%%)\n", zero_count,
|
||||||
gfx16.size(), zero_percent);
|
total_pixels, zero_percent);
|
||||||
|
|
||||||
// Log first 64 bytes (one tile's worth) to see actual values
|
// Log first 64 bytes (one tile's worth) to see actual values
|
||||||
printf("[RoomGraphics] First 64 bytes:\n");
|
printf("[RoomGraphics] First 64 bytes:\n");
|
||||||
@@ -107,39 +117,44 @@ TEST_F(DungeonGraphicsTransparencyTest, RoomGraphicsBufferHasTransparentPixels)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print value distribution
|
// Print value distribution
|
||||||
int value_counts[8] = {0};
|
int value_counts[16] = {0};
|
||||||
int other_count = 0;
|
int other_count = 0;
|
||||||
for (size_t i = 0; i < gfx16.size(); i++) {
|
for (int block = 0; block < kBgBlocks; block++) {
|
||||||
if (gfx16[i] < 8) {
|
int base = block * kBlockSize;
|
||||||
value_counts[gfx16[i]]++;
|
for (int i = 0; i < kBlockSize; i++) {
|
||||||
} else {
|
uint8_t value = gfx16[base + i];
|
||||||
other_count++;
|
if (value < 16) {
|
||||||
|
value_counts[value]++;
|
||||||
|
} else {
|
||||||
|
other_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("[RoomGraphics] Value distribution:\n");
|
printf("[RoomGraphics] Value distribution:\n");
|
||||||
for (int v = 0; v < 8; v++) {
|
for (int v = 0; v < 16; v++) {
|
||||||
printf(" Value %d: %d (%.1f%%)\n", v, value_counts[v],
|
printf(" Value %d: %d (%.1f%%)\n", v, value_counts[v],
|
||||||
100.0f * value_counts[v] / gfx16.size());
|
100.0f * value_counts[v] / total_pixels);
|
||||||
}
|
}
|
||||||
if (other_count > 0) {
|
if (other_count > 0) {
|
||||||
printf(" Values >7: %d (%.1f%%) - UNEXPECTED for 3BPP!\n", other_count,
|
printf(" Values >15: %d (%.1f%%) - UNEXPECTED for 4BPP!\n", other_count,
|
||||||
100.0f * other_count / gfx16.size());
|
100.0f * other_count / total_pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_GT(zero_percent, 5.0f)
|
EXPECT_GT(zero_percent, 5.0f)
|
||||||
<< "Room graphics buffer should have transparent pixels. "
|
<< "Room graphics buffer should have transparent pixels. "
|
||||||
<< "Got " << zero_percent << "%. Check CopyRoomGraphicsToBuffer().";
|
<< "Got " << zero_percent << "%. Check CopyRoomGraphicsToBuffer().";
|
||||||
|
|
||||||
// All values should be 0-7 for 3BPP graphics
|
// Background blocks should not exceed 4BPP (0-15) values.
|
||||||
EXPECT_EQ(other_count, 0)
|
EXPECT_EQ(other_count, 0)
|
||||||
<< "Found " << other_count << " pixels with values > 7. "
|
<< "Found " << other_count << " pixels with values > 15 in BG blocks. "
|
||||||
<< "3BPP graphics should only have values 0-7.";
|
<< "BG graphics should only have values 0-15.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 3: Verify specific tile has expected mix of transparent/opaque
|
// Test 3: Verify specific tile has expected mix of transparent/opaque
|
||||||
TEST_F(DungeonGraphicsTransparencyTest, SpecificTileTransparency) {
|
TEST_F(DungeonGraphicsTransparencyTest, SpecificTileTransparency) {
|
||||||
Room room(0x00, rom_.get());
|
Room room = LoadRoomHeaderFromRom(rom_.get(), 0x00);
|
||||||
|
room.SetGameData(&game_data_);
|
||||||
room.LoadRoomGraphics(0xFF);
|
room.LoadRoomGraphics(0xFF);
|
||||||
room.CopyRoomGraphicsToBuffer();
|
room.CopyRoomGraphicsToBuffer();
|
||||||
|
|
||||||
|
|||||||
@@ -202,8 +202,8 @@ TEST_F(DungeonObjectRomValidationTest, DrawRoutineMapping_AllType1ObjectsHaveRou
|
|||||||
int routine = drawer.GetDrawRoutineId(id);
|
int routine = drawer.GetDrawRoutineId(id);
|
||||||
EXPECT_GE(routine, 0)
|
EXPECT_GE(routine, 0)
|
||||||
<< "Object 0x" << std::hex << id << " should have a valid draw routine";
|
<< "Object 0x" << std::hex << id << " should have a valid draw routine";
|
||||||
EXPECT_LT(routine, 40)
|
EXPECT_LT(routine, drawer.GetDrawRoutineCount())
|
||||||
<< "Object 0x" << std::hex << id << " routine ID should be < 40";
|
<< "Object 0x" << std::hex << id << " routine ID should be < registry size";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "app/gfx/core/bitmap.h"
|
#include "app/gfx/core/bitmap.h"
|
||||||
#include "app/gfx/types/snes_tile.h"
|
#include "app/gfx/types/snes_tile.h"
|
||||||
#include "zelda3/dungeon/object_drawer.h"
|
#include "zelda3/dungeon/object_drawer.h"
|
||||||
#include "zelda3/game_data.h"
|
#include "zelda3/game_data.h"
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "test/test_utils.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
@@ -44,10 +46,10 @@ TEST_F(DungeonPaletteTest, PaletteOffsetIsCorrectFor8BPP) {
|
|||||||
// Row 0, Col 1: Index 2
|
// Row 0, Col 1: Index 2
|
||||||
tiledata[1] = 2;
|
tiledata[1] = 2;
|
||||||
|
|
||||||
// Create TileInfo with palette index 1
|
// Create TileInfo with palette index 2 (first dungeon bank)
|
||||||
gfx::TileInfo tile_info;
|
gfx::TileInfo tile_info;
|
||||||
tile_info.id_ = 0;
|
tile_info.id_ = 0;
|
||||||
tile_info.palette_ = 1; // Palette 1
|
tile_info.palette_ = 2; // Palette 2
|
||||||
tile_info.horizontal_mirror_ = false;
|
tile_info.horizontal_mirror_ = false;
|
||||||
tile_info.vertical_mirror_ = false;
|
tile_info.vertical_mirror_ = false;
|
||||||
tile_info.over_ = false;
|
tile_info.over_ = false;
|
||||||
@@ -56,33 +58,30 @@ TEST_F(DungeonPaletteTest, PaletteOffsetIsCorrectFor8BPP) {
|
|||||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||||
|
|
||||||
// Check pixels
|
// Check pixels
|
||||||
// Dungeon tiles use 15-color sub-palettes (not 8 like overworld).
|
// Dungeon tiles use 16-color CGRAM banks with index 0 as transparent.
|
||||||
// Formula: final_color = (pixel - 1) + (palette * 15)
|
// Formula: final_color = pixel + ((palette - 2) * 16) for palette 2-7.
|
||||||
// For palette 1, offset is 15.
|
// Palette 2 maps to the first dungeon bank (offset 0).
|
||||||
// Pixel at (0,0) was 1. Result should be (1-1) + 15 = 15.
|
// Pixel at (0,0) was 1. Result should be 1.
|
||||||
// Pixel at (1,0) was 2. Result should be (2-1) + 15 = 16.
|
// Pixel at (1,0) was 2. Result should be 2.
|
||||||
|
|
||||||
const auto& data = bitmap.vector();
|
const auto& data = bitmap.vector();
|
||||||
// Bitmap data is row-major.
|
// Bitmap data is row-major.
|
||||||
// (0,0) is index 0.
|
// (0,0) is index 0.
|
||||||
EXPECT_EQ(data[0], 15); // (1-1) + 15 = 15
|
EXPECT_EQ(data[0], 1);
|
||||||
EXPECT_EQ(data[1], 16); // (2-1) + 15 = 16
|
EXPECT_EQ(data[1], 2);
|
||||||
|
|
||||||
// Test with palette 0
|
// Test with palette 3 (offset 16)
|
||||||
tile_info.palette_ = 0;
|
tile_info.palette_ = 3;
|
||||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||||
// Offset 0 * 15 = 0.
|
EXPECT_EQ(data[0], 17); // 1 + 16
|
||||||
// Pixel 1 -> (1-1) + 0 = 0
|
EXPECT_EQ(data[1], 18); // 2 + 16
|
||||||
// Pixel 2 -> (2-1) + 0 = 1
|
|
||||||
EXPECT_EQ(data[0], 0);
|
|
||||||
EXPECT_EQ(data[1], 1);
|
|
||||||
|
|
||||||
// Test with palette 7 (wraps to palette 1 due to 6 sub-palette limit)
|
// Test with palette 7 (last dungeon bank)
|
||||||
tile_info.palette_ = 7;
|
tile_info.palette_ = 7;
|
||||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||||
// Palette 7 wraps to 7 % 6 = 1, offset 1 * 15 = 15.
|
// Palette 7 maps to bank 5 (offset 80).
|
||||||
EXPECT_EQ(data[0], 15); // (1-1) + 15 = 15
|
EXPECT_EQ(data[0], 81); // 1 + 80
|
||||||
EXPECT_EQ(data[1], 16); // (2-1) + 15 = 16
|
EXPECT_EQ(data[1], 82); // 2 + 80
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DungeonPaletteTest, PaletteOffsetWorksWithConvertedData) {
|
TEST_F(DungeonPaletteTest, PaletteOffsetWorksWithConvertedData) {
|
||||||
@@ -100,7 +99,7 @@ TEST_F(DungeonPaletteTest, PaletteOffsetWorksWithConvertedData) {
|
|||||||
|
|
||||||
gfx::TileInfo tile_info;
|
gfx::TileInfo tile_info;
|
||||||
tile_info.id_ = 0;
|
tile_info.id_ = 0;
|
||||||
tile_info.palette_ = 2; // Palette 2 → offset 30 (2 * 15)
|
tile_info.palette_ = 4; // Palette 4 → offset 32 (2 * 16)
|
||||||
tile_info.horizontal_mirror_ = false;
|
tile_info.horizontal_mirror_ = false;
|
||||||
tile_info.vertical_mirror_ = false;
|
tile_info.vertical_mirror_ = false;
|
||||||
tile_info.over_ = false;
|
tile_info.over_ = false;
|
||||||
@@ -108,17 +107,21 @@ TEST_F(DungeonPaletteTest, PaletteOffsetWorksWithConvertedData) {
|
|||||||
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
drawer_->DrawTileToBitmap(bitmap, tile_info, 0, 0, tiledata.data());
|
||||||
|
|
||||||
const auto& data = bitmap.vector();
|
const auto& data = bitmap.vector();
|
||||||
// Dungeon tiles use 15-color sub-palettes.
|
// Dungeon tiles use 16-color CGRAM banks.
|
||||||
// Formula: final_color = (pixel - 1) + (palette * 15)
|
// Formula: final_color = pixel + ((palette - 2) * 16)
|
||||||
// Pixel 3: (3-1) + 30 = 32
|
// Pixel 3: 3 + 32 = 35
|
||||||
// Pixel 5: (5-1) + 30 = 34
|
// Pixel 5: 5 + 32 = 37
|
||||||
EXPECT_EQ(data[0], 32);
|
EXPECT_EQ(data[0], 35);
|
||||||
EXPECT_EQ(data[1], 34);
|
EXPECT_EQ(data[1], 37);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DungeonPaletteTest, InspectActualPaletteColors) {
|
TEST_F(DungeonPaletteTest, InspectActualPaletteColors) {
|
||||||
// Load actual ROM file
|
// Load actual ROM file
|
||||||
auto load_result = rom_->LoadFromFile("zelda3.sfc");
|
yaze::test::TestRomManager::SkipIfRomMissing(
|
||||||
|
yaze::test::RomRole::kVanilla, "DungeonPaletteTest.InspectActualPaletteColors");
|
||||||
|
const std::string rom_path =
|
||||||
|
yaze::test::TestRomManager::GetRomPath(yaze::test::RomRole::kVanilla);
|
||||||
|
auto load_result = rom_->LoadFromFile(rom_path);
|
||||||
if (!load_result.ok()) {
|
if (!load_result.ok()) {
|
||||||
GTEST_SKIP() << "ROM file not found, skipping";
|
GTEST_SKIP() << "ROM file not found, skipping";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "test/test_utils.h"
|
||||||
#include "zelda3/dungeon/room.h"
|
#include "zelda3/dungeon/room.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
@@ -14,8 +17,10 @@ class DungeonRoomTest : public ::testing::Test {
|
|||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
GTEST_SKIP() << "Dungeon room tests require ROM file (unavailable on Linux CI)";
|
GTEST_SKIP() << "Dungeon room tests require ROM file (unavailable on Linux CI)";
|
||||||
#else
|
#else
|
||||||
if (!rom_.LoadFromFile("./zelda3.sfc").ok()) {
|
TestRomManager::SkipIfRomMissing(RomRole::kVanilla, "DungeonRoomTest");
|
||||||
GTEST_SKIP() << "Failed to load test ROM (zelda3.sfc)";
|
const std::string rom_path = TestRomManager::GetRomPath(RomRole::kVanilla);
|
||||||
|
if (!rom_.LoadFromFile(rom_path).ok()) {
|
||||||
|
GTEST_SKIP() << "Failed to load test ROM (" << rom_path << ")";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "test/test_utils.h"
|
||||||
#include "zelda3/dungeon/room.h"
|
#include "zelda3/dungeon/room.h"
|
||||||
#include "zelda3/dungeon/room_object.h"
|
#include "zelda3/dungeon/room_object.h"
|
||||||
|
|
||||||
@@ -24,12 +27,10 @@ class RoomIntegrationTest : public ::testing::Test {
|
|||||||
// Load the ROM file
|
// Load the ROM file
|
||||||
rom_ = std::make_unique<Rom>();
|
rom_ = std::make_unique<Rom>();
|
||||||
|
|
||||||
// Check if ROM file exists
|
yaze::test::TestRomManager::SkipIfRomMissing(
|
||||||
const char* rom_path = std::getenv("YAZE_TEST_ROM_PATH");
|
yaze::test::RomRole::kVanilla, "RoomIntegrationTest");
|
||||||
if (!rom_path) {
|
const std::string rom_path =
|
||||||
rom_path = "zelda3.sfc";
|
yaze::test::TestRomManager::GetRomPath(yaze::test::RomRole::kVanilla);
|
||||||
}
|
|
||||||
|
|
||||||
auto status = rom_->LoadFromFile(rom_path);
|
auto status = rom_->LoadFromFile(rom_path);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
GTEST_SKIP() << "ROM file not available: " << status.message();
|
GTEST_SKIP() << "ROM file not available: " << status.message();
|
||||||
@@ -59,6 +60,7 @@ class RoomIntegrationTest : public ::testing::Test {
|
|||||||
TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
||||||
// Load room 0 (Hyrule Castle Entrance)
|
// Load room 0 (Hyrule Castle Entrance)
|
||||||
Room room1(0x00, rom_.get());
|
Room room1(0x00, rom_.get());
|
||||||
|
room1.LoadObjects();
|
||||||
|
|
||||||
// Get original object count
|
// Get original object count
|
||||||
size_t original_count = room1.GetTileObjects().size();
|
size_t original_count = room1.GetTileObjects().size();
|
||||||
@@ -73,6 +75,7 @@ TEST_F(RoomIntegrationTest, BasicLoadSaveRoundTrip) {
|
|||||||
|
|
||||||
// Load the room again
|
// Load the room again
|
||||||
Room room2(0x00, rom_.get());
|
Room room2(0x00, rom_.get());
|
||||||
|
room2.LoadObjects();
|
||||||
|
|
||||||
// Verify object count matches
|
// Verify object count matches
|
||||||
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
EXPECT_EQ(room2.GetTileObjects().size(), original_count);
|
||||||
@@ -108,6 +111,7 @@ TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
|||||||
|
|
||||||
// Load room
|
// Load room
|
||||||
Room room1(room_id, rom_.get());
|
Room room1(room_id, rom_.get());
|
||||||
|
room1.LoadObjects();
|
||||||
auto original_objects = room1.GetTileObjects();
|
auto original_objects = room1.GetTileObjects();
|
||||||
|
|
||||||
if (original_objects.empty()) {
|
if (original_objects.empty()) {
|
||||||
@@ -120,6 +124,7 @@ TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
|||||||
|
|
||||||
// Reload and verify
|
// Reload and verify
|
||||||
Room room2(room_id, rom_.get());
|
Room room2(room_id, rom_.get());
|
||||||
|
room2.LoadObjects();
|
||||||
auto reloaded_objects = room2.GetTileObjects();
|
auto reloaded_objects = room2.GetTileObjects();
|
||||||
|
|
||||||
EXPECT_EQ(reloaded_objects.size(), original_objects.size());
|
EXPECT_EQ(reloaded_objects.size(), original_objects.size());
|
||||||
@@ -146,6 +151,7 @@ TEST_F(RoomIntegrationTest, MultiRoomLoadSaveRoundTrip) {
|
|||||||
TEST_F(RoomIntegrationTest, LayerPreservation) {
|
TEST_F(RoomIntegrationTest, LayerPreservation) {
|
||||||
// Load a room known to have multiple layers
|
// Load a room known to have multiple layers
|
||||||
Room room(0x01, rom_.get());
|
Room room(0x01, rom_.get());
|
||||||
|
room.LoadObjects();
|
||||||
|
|
||||||
auto objects = room.GetTileObjects();
|
auto objects = room.GetTileObjects();
|
||||||
ASSERT_GT(objects.size(), 0);
|
ASSERT_GT(objects.size(), 0);
|
||||||
@@ -170,6 +176,7 @@ TEST_F(RoomIntegrationTest, LayerPreservation) {
|
|||||||
ASSERT_TRUE(room.SaveObjects().ok());
|
ASSERT_TRUE(room.SaveObjects().ok());
|
||||||
|
|
||||||
Room room2(0x01, rom_.get());
|
Room room2(0x01, rom_.get());
|
||||||
|
room2.LoadObjects();
|
||||||
auto reloaded = room2.GetTileObjects();
|
auto reloaded = room2.GetTileObjects();
|
||||||
|
|
||||||
// Verify layer counts match
|
// Verify layer counts match
|
||||||
@@ -199,6 +206,7 @@ TEST_F(RoomIntegrationTest, LayerPreservation) {
|
|||||||
|
|
||||||
TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
||||||
Room room(0x00, rom_.get());
|
Room room(0x00, rom_.get());
|
||||||
|
room.LoadObjects();
|
||||||
|
|
||||||
auto objects = room.GetTileObjects();
|
auto objects = room.GetTileObjects();
|
||||||
ASSERT_GT(objects.size(), 0);
|
ASSERT_GT(objects.size(), 0);
|
||||||
@@ -222,6 +230,7 @@ TEST_F(RoomIntegrationTest, ObjectTypeDistribution) {
|
|||||||
ASSERT_TRUE(room.SaveObjects().ok());
|
ASSERT_TRUE(room.SaveObjects().ok());
|
||||||
|
|
||||||
Room room2(0x00, rom_.get());
|
Room room2(0x00, rom_.get());
|
||||||
|
room2.LoadObjects();
|
||||||
auto reloaded = room2.GetTileObjects();
|
auto reloaded = room2.GetTileObjects();
|
||||||
|
|
||||||
// Verify type distribution matches
|
// Verify type distribution matches
|
||||||
@@ -250,6 +259,7 @@ TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
|||||||
// when no modifications are made
|
// when no modifications are made
|
||||||
|
|
||||||
Room room(0x02, rom_.get());
|
Room room(0x02, rom_.get());
|
||||||
|
room.LoadObjects();
|
||||||
|
|
||||||
// Get the ROM location where objects are stored
|
// Get the ROM location where objects are stored
|
||||||
auto rom_data = rom_->vector();
|
auto rom_data = rom_->vector();
|
||||||
@@ -309,6 +319,7 @@ TEST_F(RoomIntegrationTest, BinaryDataExactMatch) {
|
|||||||
TEST_F(RoomIntegrationTest, KnownRoomData) {
|
TEST_F(RoomIntegrationTest, KnownRoomData) {
|
||||||
// Room 0x00 (Hyrule Castle Entrance) - verify known objects exist
|
// Room 0x00 (Hyrule Castle Entrance) - verify known objects exist
|
||||||
Room room(0x00, rom_.get());
|
Room room(0x00, rom_.get());
|
||||||
|
room.LoadObjects();
|
||||||
|
|
||||||
auto objects = room.GetTileObjects();
|
auto objects = room.GetTileObjects();
|
||||||
ASSERT_GT(objects.size(), 0) << "Room 0x00 should have objects";
|
ASSERT_GT(objects.size(), 0) << "Room 0x00 should have objects";
|
||||||
|
|||||||
@@ -78,14 +78,14 @@ TEST_F(CustomObjectManagerTest, LoadSimpleObject) {
|
|||||||
|
|
||||||
TEST_F(CustomObjectManagerTest, LoadComplexLayout) {
|
TEST_F(CustomObjectManagerTest, LoadComplexLayout) {
|
||||||
// Two rows of 2 tiles
|
// Two rows of 2 tiles
|
||||||
// Row 1: 0xAAAA, 0xBBBB. Jump to next row (stride 64 bytes - 4 bytes used = 60 bytes jump)
|
// Row 1: 0xAAAA, 0xBBBB. Jump to next row (stride 128 bytes, jump 128)
|
||||||
// Header 1: Count=2, Jump=60 (0x3C). 0x3C02 -> LE: 02 3C
|
// Header 1: Count=2, Jump=128 (0x80). 0x8002 -> LE: 02 80
|
||||||
// Row 2: 0xCCCC, 0xDDDD.
|
// Row 2: 0xCCCC, 0xDDDD.
|
||||||
// Header 2: Count=2, Jump=0. 0x0002 -> LE: 02 00
|
// Header 2: Count=2, Jump=0. 0x0002 -> LE: 02 00
|
||||||
// Terminator
|
// Terminator
|
||||||
|
|
||||||
std::vector<uint8_t> data = {
|
std::vector<uint8_t> data = {
|
||||||
0x02, 0x3C, // Header 1
|
0x02, 0x80, // Header 1
|
||||||
0xAA, 0xAA, 0xBB, 0xBB, // Row 1 Tiles (LE: 0xAAAA, 0xBBBB)
|
0xAA, 0xAA, 0xBB, 0xBB, // Row 1 Tiles (LE: 0xAAAA, 0xBBBB)
|
||||||
0x02, 0x00, // Header 2
|
0x02, 0x00, // Header 2
|
||||||
0xCC, 0xCC, 0xDD, 0xDD, // Row 2 Tiles (LE: 0xCCCC, 0xDDDD)
|
0xCC, 0xCC, 0xDD, 0xDD, // Row 2 Tiles (LE: 0xCCCC, 0xDDDD)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ TEST_F(DrawRoutineMappingTest, VerifiesPhase4Step5SpecialMappings) {
|
|||||||
TEST_F(DrawRoutineMappingTest, VerifiesSubtype2Mappings) {
|
TEST_F(DrawRoutineMappingTest, VerifiesSubtype2Mappings) {
|
||||||
ObjectDrawer drawer(rom_.get(), 0);
|
ObjectDrawer drawer(rom_.get(), 0);
|
||||||
|
|
||||||
// 0x100-0x107 -> Routine 16 (4x4)
|
// 0x100-0x107 -> Routine 16 (RoomDraw_4x4)
|
||||||
EXPECT_EQ(drawer.GetDrawRoutineId(0x100), 16);
|
EXPECT_EQ(drawer.GetDrawRoutineId(0x100), 16);
|
||||||
|
|
||||||
// 0x108 -> Routine 35 (4x4 Corner BothBG)
|
// 0x108 -> Routine 35 (4x4 Corner BothBG)
|
||||||
|
|||||||
@@ -81,32 +81,31 @@ class DungeonSaveTest : public ::testing::Test {
|
|||||||
|
|
||||||
void SetupSpritePointers() {
|
void SetupSpritePointers() {
|
||||||
// 1. Setup kRoomsSpritePointer (0x4C298)
|
// 1. Setup kRoomsSpritePointer (0x4C298)
|
||||||
// Points to table in Bank 04. Let's put table at 0x20000 (04:8000)
|
// Points to table in Bank 09. Let's put table at 0x48000 (09:8000)
|
||||||
int ptr_loc = kRoomsSpritePointer;
|
int ptr_loc = kRoomsSpritePointer;
|
||||||
rom_->mutable_data()[ptr_loc] = 0x00;
|
rom_->mutable_data()[ptr_loc] = 0x00;
|
||||||
rom_->mutable_data()[ptr_loc + 1] = 0x80;
|
rom_->mutable_data()[ptr_loc + 1] = 0x80;
|
||||||
// Bank is hardcoded to 0x04 in code, so we only write low 2 bytes.
|
// Bank is hardcoded to 0x09 in code, so we only write low 2 bytes.
|
||||||
|
|
||||||
// 2. Setup Sprite Pointer Table at 0x20000
|
// 2. Setup Sprite Pointer Table at 0x48000 (09:8000)
|
||||||
// Room 0 pointer at 0x20000
|
// Room 0 pointer -> sprite list at 0x49000 (09:9000)
|
||||||
// Points to sprite list in Bank 09. Let's put sprites at 0x48000 (09:8000)
|
// Write 00 90 at 0x48000
|
||||||
// Write 00 80 at 0x20000
|
int table_loc = 0x48000;
|
||||||
int table_loc = 0x20000;
|
|
||||||
rom_->mutable_data()[table_loc] = 0x00;
|
rom_->mutable_data()[table_loc] = 0x00;
|
||||||
rom_->mutable_data()[table_loc + 1] = 0x80;
|
rom_->mutable_data()[table_loc + 1] = 0x90;
|
||||||
|
|
||||||
// Room 1 pointer at 0x20002 (for size calculation)
|
// Room 1 pointer at 0x48002 (for size calculation)
|
||||||
// Let's give 0x50 bytes for sprites.
|
// Let's give 0x50 bytes for sprites.
|
||||||
// Next room at 0x48050 (09:8050)
|
// Next room at 0x49050 (09:9050)
|
||||||
// Write 50 80 at 0x20002
|
// Write 50 90 at 0x48002
|
||||||
rom_->mutable_data()[table_loc + 2] = 0x50;
|
rom_->mutable_data()[table_loc + 2] = 0x50;
|
||||||
rom_->mutable_data()[table_loc + 3] = 0x80;
|
rom_->mutable_data()[table_loc + 3] = 0x90;
|
||||||
|
|
||||||
// 3. Setup Sprite Data at 0x48000
|
// 3. Setup Sprite Data at 0x49000
|
||||||
// Sortsprite byte (0 or 1)
|
// Sortsprite byte (0 or 1)
|
||||||
rom_->mutable_data()[0x48000] = 0x00;
|
rom_->mutable_data()[0x49000] = 0x00;
|
||||||
// End of sprites (0xFF)
|
// End of sprites (0xFF)
|
||||||
rom_->mutable_data()[0x48001] = 0xFF;
|
rom_->mutable_data()[0x49001] = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Rom> rom_;
|
std::unique_ptr<Rom> rom_;
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ TEST_F(ObjectDimensionTableTest, GetDimensionsAccountsForSize) {
|
|||||||
auto [w0, h0] = table.GetDimensions(0x00, 0);
|
auto [w0, h0] = table.GetDimensions(0x00, 0);
|
||||||
auto [w5, h5] = table.GetDimensions(0x00, 5);
|
auto [w5, h5] = table.GetDimensions(0x00, 5);
|
||||||
|
|
||||||
// Larger size should give larger width for horizontal walls
|
// Size 0 uses the 32-tile default, so width should be larger than size 5
|
||||||
EXPECT_GE(w5, w0);
|
EXPECT_GT(w0, w5);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ObjectDimensionTableTest, GetHitTestBoundsReturnsObjectPosition) {
|
TEST_F(ObjectDimensionTableTest, GetHitTestBoundsReturnsObjectPosition) {
|
||||||
@@ -143,20 +143,20 @@ TEST_F(ObjectDimensionsTest, CalculatesDimensionsForDiagonalWalls) {
|
|||||||
ObjectDrawer drawer(rom_.get(), 0);
|
ObjectDrawer drawer(rom_.get(), 0);
|
||||||
|
|
||||||
// Test object 0x10 (Diagonal Wall /)
|
// Test object 0x10 (Diagonal Wall /)
|
||||||
// Routine 17: DrawDiagonalAcute_1to16_BothBG
|
// Routine 5: DrawDiagonalAcute_1to16
|
||||||
// Logic: width = (size + 6) * 8
|
// Logic: width = (size + 7) * 8
|
||||||
|
|
||||||
RoomObject obj10(0x10, 10, 10, 0, 0); // Size 0
|
RoomObject obj10(0x10, 10, 10, 0, 0); // Size 0
|
||||||
// width = (0 + 6) * 8 = 48
|
// width = (0 + 7) * 8 = 56
|
||||||
auto dims = drawer.CalculateObjectDimensions(obj10);
|
auto dims = drawer.CalculateObjectDimensions(obj10);
|
||||||
EXPECT_EQ(dims.first, 48);
|
EXPECT_EQ(dims.first, 56);
|
||||||
EXPECT_EQ(dims.second, 48);
|
EXPECT_EQ(dims.second, 88);
|
||||||
|
|
||||||
RoomObject obj10_size10(0x10, 10, 10, 10, 0); // Size 10
|
RoomObject obj10_size10(0x10, 10, 10, 10, 0); // Size 10
|
||||||
// width = (10 + 6) * 8 = 128
|
// width = (10 + 7) * 8 = 136
|
||||||
dims = drawer.CalculateObjectDimensions(obj10_size10);
|
dims = drawer.CalculateObjectDimensions(obj10_size10);
|
||||||
EXPECT_EQ(dims.first, 128);
|
EXPECT_EQ(dims.first, 136);
|
||||||
EXPECT_EQ(dims.second, 128);
|
EXPECT_EQ(dims.second, 168);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ObjectDimensionsTest, CalculatesDimensionsForType2Corners) {
|
TEST_F(ObjectDimensionsTest, CalculatesDimensionsForType2Corners) {
|
||||||
@@ -164,10 +164,10 @@ TEST_F(ObjectDimensionsTest, CalculatesDimensionsForType2Corners) {
|
|||||||
|
|
||||||
// Test object 0x40 (Type 2 Corner)
|
// Test object 0x40 (Type 2 Corner)
|
||||||
// Routine 22: Edge 1x1
|
// Routine 22: Edge 1x1
|
||||||
// Width 8, Height 8
|
// Width 24, Height 8 (corner + middle + end)
|
||||||
RoomObject obj40(0x40, 10, 10, 0, 0);
|
RoomObject obj40(0x40, 10, 10, 0, 0);
|
||||||
auto dims = drawer.CalculateObjectDimensions(obj40);
|
auto dims = drawer.CalculateObjectDimensions(obj40);
|
||||||
EXPECT_EQ(dims.first, 8);
|
EXPECT_EQ(dims.first, 24);
|
||||||
EXPECT_EQ(dims.second, 8);
|
EXPECT_EQ(dims.second, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @brief Comprehensive tests for object drawing, parsing, and routine mapping
|
* @brief Comprehensive tests for object drawing, parsing, and routine mapping
|
||||||
*
|
*
|
||||||
* Tests the following areas:
|
* Tests the following areas:
|
||||||
* 1. Object type detection (Type 1: 0x00-0xFF, Type 2: 0x100-0x1FF, Type 3: 0xF80-0xFFF)
|
* 1. Object type detection (Type 1: 0x00-0xFF, Type 2: 0x100-0x13F, Type 3: 0xF80-0xFFF)
|
||||||
* 2. Tile count lookup table (kSubtype1TileLengths)
|
* 2. Tile count lookup table (kSubtype1TileLengths)
|
||||||
* 3. Draw routine mapping completeness
|
* 3. Draw routine mapping completeness
|
||||||
* 4. Type 3 object index calculation
|
* 4. Type 3 object index calculation
|
||||||
@@ -71,12 +71,18 @@ TEST_F(ObjectDrawingComprehensiveTest, DetectsType1Objects) {
|
|||||||
TEST_F(ObjectDrawingComprehensiveTest, DetectsType2Objects) {
|
TEST_F(ObjectDrawingComprehensiveTest, DetectsType2Objects) {
|
||||||
ObjectParser parser(rom_.get());
|
ObjectParser parser(rom_.get());
|
||||||
|
|
||||||
// Type 2: 0x100-0x1FF (64 fixed-size objects)
|
// Type 2: 0x100-0x13F (64 objects)
|
||||||
for (int id = 0x100; id <= 0x1FF; ++id) {
|
for (int id = 0x100; id <= 0x13F; ++id) {
|
||||||
auto info = parser.GetObjectSubtype(id);
|
auto info = parser.GetObjectSubtype(id);
|
||||||
ASSERT_TRUE(info.ok()) << "Failed for ID 0x" << std::hex << id;
|
ASSERT_TRUE(info.ok()) << "Failed for ID 0x" << std::hex << id;
|
||||||
EXPECT_EQ(info->subtype, 2) << "ID 0x" << std::hex << id << " should be Type 2";
|
EXPECT_EQ(info->subtype, 2) << "ID 0x" << std::hex << id << " should be Type 2";
|
||||||
EXPECT_EQ(info->max_tile_count, 8) << "Type 2 objects should have 8 tiles";
|
if (id >= 0x100 && id <= 0x10F) {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 16) << "Type 2 corners should have 16 tiles";
|
||||||
|
} else if (id >= 0x110 && id <= 0x117) {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 12) << "Type 2 weird corners should have 12 tiles";
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 8) << "Type 2 objects should have 8 tiles";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +94,16 @@ TEST_F(ObjectDrawingComprehensiveTest, DetectsType3Objects) {
|
|||||||
auto info = parser.GetObjectSubtype(id);
|
auto info = parser.GetObjectSubtype(id);
|
||||||
ASSERT_TRUE(info.ok()) << "Failed for ID 0x" << std::hex << id;
|
ASSERT_TRUE(info.ok()) << "Failed for ID 0x" << std::hex << id;
|
||||||
EXPECT_EQ(info->subtype, 3) << "ID 0x" << std::hex << id << " should be Type 3";
|
EXPECT_EQ(info->subtype, 3) << "ID 0x" << std::hex << id << " should be Type 3";
|
||||||
EXPECT_EQ(info->max_tile_count, 8) << "Type 3 objects should have 8 tiles";
|
if (id == 0xFB1 || id == 0xFB2 ||
|
||||||
|
id == 0xF94 || id == 0xFCE ||
|
||||||
|
(id >= 0xFE7 && id <= 0xFE8) ||
|
||||||
|
(id >= 0xFEC && id <= 0xFED)) {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 12) << "Type 3 objects should have 12 tiles";
|
||||||
|
} else if (id == 0xFC8 || id == 0xFE6 || id == 0xFEB || id == 0xFFA) {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 16) << "Type 3 objects should have 16 tiles";
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(info->max_tile_count, 8) << "Type 3 objects should have 8 tiles";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +173,8 @@ TEST_F(ObjectDrawingComprehensiveTest, Type3IndexCalculation_AllIndicesInRange)
|
|||||||
TEST_F(ObjectDrawingComprehensiveTest, Type2IndexCalculation_BoundaryValues) {
|
TEST_F(ObjectDrawingComprehensiveTest, Type2IndexCalculation_BoundaryValues) {
|
||||||
ObjectParser parser(rom_.get());
|
ObjectParser parser(rom_.get());
|
||||||
|
|
||||||
// Verify Type 2 index calculation: index = (object_id - 0x100) & 0xFF
|
// Verify Type 2 index calculation: index = (object_id - 0x100) & 0x3F
|
||||||
// This maps 0x100-0x1FF to table indices 0-255
|
// This maps 0x100-0x13F to table indices 0-63 (64-entry table)
|
||||||
|
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
int object_id;
|
int object_id;
|
||||||
@@ -170,8 +185,7 @@ TEST_F(ObjectDrawingComprehensiveTest, Type2IndexCalculation_BoundaryValues) {
|
|||||||
{0x100, 0}, // First Type 2 object -> index 0
|
{0x100, 0}, // First Type 2 object -> index 0
|
||||||
{0x101, 1}, // Second Type 2 object -> index 1
|
{0x101, 1}, // Second Type 2 object -> index 1
|
||||||
{0x10F, 15}, // Index 15
|
{0x10F, 15}, // Index 15
|
||||||
{0x13F, 63}, // Last commonly used Type 2 object
|
{0x13F, 63}, // Last Type 2 object -> index 63
|
||||||
{0x1FF, 255}, // Last possible Type 2 object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& tc : test_cases) {
|
for (const auto& tc : test_cases) {
|
||||||
@@ -253,15 +267,14 @@ TEST_F(ObjectDrawingComprehensiveTest, TileCountLookupTable_SpecialCases) {
|
|||||||
|
|
||||||
TEST_F(ObjectDrawingComprehensiveTest, DrawRoutineMapping_AllSubtype1ObjectsHaveRoutines) {
|
TEST_F(ObjectDrawingComprehensiveTest, DrawRoutineMapping_AllSubtype1ObjectsHaveRoutines) {
|
||||||
ObjectDrawer drawer(rom_.get(), 0);
|
ObjectDrawer drawer(rom_.get(), 0);
|
||||||
|
const int max_routine_id = drawer.GetDrawRoutineCount() - 1;
|
||||||
|
|
||||||
// Verify all Type 1 objects (0x00-0xF7) have valid routine mappings
|
// Verify all Type 1 objects (0x00-0xF7) have valid routine mappings
|
||||||
for (int id = 0; id <= 0xF7; ++id) {
|
for (int id = 0; id <= 0xF7; ++id) {
|
||||||
int routine_id = drawer.GetDrawRoutineId(id);
|
int routine_id = drawer.GetDrawRoutineId(id);
|
||||||
// Should return valid routine (0-82) or -1 for unmapped
|
// Should return valid routine (0..max) or -1 for unmapped
|
||||||
// Phase 4 added: SuperSquare routines 56-64, Step 2 variants 65-74,
|
|
||||||
// Step 3 diagonal ceilings 75-78, Step 5 special routines 79-82
|
|
||||||
EXPECT_GE(routine_id, -1) << "ID 0x" << std::hex << id;
|
EXPECT_GE(routine_id, -1) << "ID 0x" << std::hex << id;
|
||||||
EXPECT_LE(routine_id, 82) << "ID 0x" << std::hex << id;
|
EXPECT_LE(routine_id, max_routine_id) << "ID 0x" << std::hex << id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,11 +373,13 @@ TEST_F(ObjectDrawingComprehensiveTest, DrawRoutineMapping_Type3SpecialObjects) {
|
|||||||
// Type 3 objects (0xF80-0xFFF) - actual decoded IDs from ROM
|
// Type 3 objects (0xF80-0xFFF) - actual decoded IDs from ROM
|
||||||
// Index = (object_id - 0xF80) & 0x7F
|
// Index = (object_id - 0xF80) & 0x7F
|
||||||
|
|
||||||
// Water Face (indices 0-2)
|
// Water Face variants (indices 0-2)
|
||||||
for (int id : {0xF80, 0xF81, 0xF82}) {
|
EXPECT_EQ(drawer.GetDrawRoutineId(0xF80), 94)
|
||||||
EXPECT_EQ(drawer.GetDrawRoutineId(id), 34)
|
<< "ID 0xF80 should use routine 94 (Empty Water Face)";
|
||||||
<< "ID 0x" << std::hex << id << " should use routine 34 (Water Face)";
|
EXPECT_EQ(drawer.GetDrawRoutineId(0xF81), 95)
|
||||||
}
|
<< "ID 0xF81 should use routine 95 (Spitting Water Face)";
|
||||||
|
EXPECT_EQ(drawer.GetDrawRoutineId(0xF82), 96)
|
||||||
|
<< "ID 0xF82 should use routine 96 (Drenching Water Face)";
|
||||||
|
|
||||||
// Somaria Line (indices 3-9)
|
// Somaria Line (indices 3-9)
|
||||||
for (int id : {0xF83, 0xF84, 0xF85, 0xF86, 0xF87, 0xF88, 0xF89}) {
|
for (int id : {0xF83, 0xF84, 0xF85, 0xF86, 0xF87, 0xF88, 0xF89}) {
|
||||||
@@ -372,8 +387,12 @@ TEST_F(ObjectDrawingComprehensiveTest, DrawRoutineMapping_Type3SpecialObjects) {
|
|||||||
<< "ID 0x" << std::hex << id << " should use routine 33 (Somaria Line)";
|
<< "ID 0x" << std::hex << id << " should use routine 33 (Somaria Line)";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chests (indices 23-26 = 0x17-0x1A + 0xF80 = 0xF97-0xF9A)
|
// Prison Cell + Big Key Lock + Chests (indices 0x17-0x1A -> 0xF97-0xF9A)
|
||||||
for (int id : {0xF97, 0xF98, 0xF99, 0xF9A}) {
|
EXPECT_EQ(drawer.GetDrawRoutineId(0xF97), 97)
|
||||||
|
<< "ID 0xF97 should use routine 97 (Prison Cell)";
|
||||||
|
EXPECT_EQ(drawer.GetDrawRoutineId(0xF98), 92)
|
||||||
|
<< "ID 0xF98 should use routine 92 (Big Key Lock)";
|
||||||
|
for (int id : {0xF99, 0xF9A}) {
|
||||||
EXPECT_EQ(drawer.GetDrawRoutineId(id), 39)
|
EXPECT_EQ(drawer.GetDrawRoutineId(id), 39)
|
||||||
<< "ID 0x" << std::hex << id << " should use routine 39 (DrawChest)";
|
<< "ID 0x" << std::hex << id << " should use routine 39 (DrawChest)";
|
||||||
}
|
}
|
||||||
@@ -503,14 +522,14 @@ TEST_F(ObjectDrawingComprehensiveTest, DimensionCalculation_DiagonalPatterns) {
|
|||||||
// Diagonal walls use (size + 6) or (size + 7) count
|
// Diagonal walls use (size + 6) or (size + 7) count
|
||||||
RoomObject diagonal_size0(0x10, 0, 0, 0, 0);
|
RoomObject diagonal_size0(0x10, 0, 0, 0, 0);
|
||||||
auto dims = drawer.CalculateObjectDimensions(diagonal_size0);
|
auto dims = drawer.CalculateObjectDimensions(diagonal_size0);
|
||||||
// Diagonal: (size + 6) * 8 pixels each direction
|
// Diagonal: count = size + 7, width = count * 8, height = (count + 4) * 8
|
||||||
EXPECT_EQ(dims.first, 48); // 6 * 8 = 48
|
EXPECT_EQ(dims.first, 56); // 7 * 8 = 56
|
||||||
EXPECT_EQ(dims.second, 48);
|
EXPECT_EQ(dims.second, 88); // (7 + 4) * 8 = 88
|
||||||
|
|
||||||
RoomObject diagonal_size10(0x10, 0, 0, 10, 0);
|
RoomObject diagonal_size10(0x10, 0, 0, 10, 0);
|
||||||
dims = drawer.CalculateObjectDimensions(diagonal_size10);
|
dims = drawer.CalculateObjectDimensions(diagonal_size10);
|
||||||
EXPECT_EQ(dims.first, 128); // 16 * 8 = 128
|
EXPECT_EQ(dims.first, 136); // 17 * 8 = 136
|
||||||
EXPECT_EQ(dims.second, 128);
|
EXPECT_EQ(dims.second, 168); // (17 + 4) * 8 = 168
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -546,14 +565,10 @@ TEST_F(ObjectDrawingComprehensiveTest, EdgeCase_Size0HandledCorrectly) {
|
|||||||
auto dims00 = drawer.CalculateObjectDimensions(obj00_size0);
|
auto dims00 = drawer.CalculateObjectDimensions(obj00_size0);
|
||||||
EXPECT_EQ(dims00.first, 512); // 32 * 16 = 512
|
EXPECT_EQ(dims00.first, 512); // 32 * 16 = 512
|
||||||
|
|
||||||
// NOTE: Object 0x01 (routine 1) size=0 handling is NOT implemented in
|
// Object 0x01 (routine 1) size=0 uses GetSize_1to15or26 -> 26 repetitions.
|
||||||
// CalculateObjectDimensions. The draw routine uses size=26 when size=0,
|
|
||||||
// but CalculateObjectDimensions falls through to default case.
|
|
||||||
// This is a known limitation - see TODO in CalculateObjectDimensions.
|
|
||||||
RoomObject obj01_size0(0x01, 0, 0, 0, 0);
|
RoomObject obj01_size0(0x01, 0, 0, 0, 0);
|
||||||
auto dims01 = drawer.CalculateObjectDimensions(obj01_size0);
|
auto dims01 = drawer.CalculateObjectDimensions(obj01_size0);
|
||||||
// Current behavior: falls through to default, size_h=0, width=(0+1)*8=8
|
EXPECT_EQ(dims01.first, 416); // 26 * 16
|
||||||
EXPECT_EQ(dims01.first, 8); // Known limitation: should be 416 (26*16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -597,19 +612,19 @@ TEST_F(ObjectDrawingComprehensiveTest, TransparencyHandling_Pixel0IsSkipped) {
|
|||||||
const auto& data = bg.bitmap().vector();
|
const auto& data = bg.bitmap().vector();
|
||||||
EXPECT_EQ(data[0], 0xFF) << "Transparent pixel (0) should not overwrite bitmap";
|
EXPECT_EQ(data[0], 0xFF) << "Transparent pixel (0) should not overwrite bitmap";
|
||||||
|
|
||||||
// Verify pixel (1,0) WAS written with value (1-1) + palette_offset = 0
|
// Verify pixel (1,0) WAS written with value pixel + palette_offset = 1
|
||||||
EXPECT_EQ(data[1], 0) << "Non-transparent pixel should be written";
|
EXPECT_EQ(data[1], 1) << "Non-transparent pixel should be written";
|
||||||
|
|
||||||
// Verify pixel (2,0) WAS written with value (2-1) + palette_offset = 1
|
// Verify pixel (2,0) WAS written with value pixel + palette_offset = 2
|
||||||
EXPECT_EQ(data[2], 1) << "Non-transparent pixel should be written";
|
EXPECT_EQ(data[2], 2) << "Non-transparent pixel should be written";
|
||||||
|
|
||||||
// Verify pixel (0,1) WAS written with value (3-1) + palette_offset = 2
|
// Verify pixel (0,1) WAS written with value pixel + palette_offset = 3
|
||||||
EXPECT_EQ(data[64], 2) << "Non-transparent pixel at row 1 should be written";
|
EXPECT_EQ(data[64], 3) << "Non-transparent pixel at row 1 should be written";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ObjectDrawingComprehensiveTest, TileInfo_PaletteIndexMappingVerify) {
|
TEST_F(ObjectDrawingComprehensiveTest, TileInfo_PaletteIndexMappingVerify) {
|
||||||
// Verify palette index calculation:
|
// Verify palette index calculation:
|
||||||
// final_color = (pixel - 1) + (palette_ * 15)
|
// final_color = pixel + (palette_bank * 16)
|
||||||
|
|
||||||
// TileInfo with palette 0
|
// TileInfo with palette 0
|
||||||
gfx::TileInfo tile0;
|
gfx::TileInfo tile0;
|
||||||
@@ -619,13 +634,13 @@ TEST_F(ObjectDrawingComprehensiveTest, TileInfo_PaletteIndexMappingVerify) {
|
|||||||
gfx::TileInfo tile1;
|
gfx::TileInfo tile1;
|
||||||
tile1.palette_ = 1;
|
tile1.palette_ = 1;
|
||||||
|
|
||||||
// Palette 0 should use offsets 0-14
|
// Palette 0 should use offsets 0-15
|
||||||
// Palette 1 should use offsets 15-29
|
// Palette 1 should use offsets 16-31
|
||||||
// etc.
|
// etc.
|
||||||
|
|
||||||
// This is design verification - the actual color lookup happens in DrawTileToBitmap
|
// This is design verification - the actual color lookup happens in DrawTileToBitmap
|
||||||
EXPECT_EQ(tile0.palette_ * 15, 0);
|
EXPECT_EQ(tile0.palette_ * 16, 0);
|
||||||
EXPECT_EQ(tile1.palette_ * 15, 15);
|
EXPECT_EQ(tile1.palette_ * 16, 16);
|
||||||
|
|
||||||
// Test palette clamping - palettes 6,7 wrap to 0,1
|
// Test palette clamping - palettes 6,7 wrap to 0,1
|
||||||
gfx::TileInfo tile6;
|
gfx::TileInfo tile6;
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ TEST_F(RoomLayerManagerTest, BG2OnTopDrawOrderBG1First) {
|
|||||||
manager_.SetBG2OnTop(true);
|
manager_.SetBG2OnTop(true);
|
||||||
auto order = manager_.GetDrawOrder();
|
auto order = manager_.GetDrawOrder();
|
||||||
|
|
||||||
// BG1 should be drawn first when BG2 is on top
|
// Draw order remains BG2 then BG1; "BG2 on top" only affects color math
|
||||||
EXPECT_EQ(order[0], LayerType::BG1_Layout);
|
EXPECT_EQ(order[0], LayerType::BG2_Layout);
|
||||||
EXPECT_EQ(order[1], LayerType::BG1_Objects);
|
EXPECT_EQ(order[1], LayerType::BG2_Objects);
|
||||||
EXPECT_EQ(order[2], LayerType::BG2_Layout);
|
EXPECT_EQ(order[2], LayerType::BG1_Layout);
|
||||||
EXPECT_EQ(order[3], LayerType::BG2_Objects);
|
EXPECT_EQ(order[3], LayerType::BG1_Objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -143,8 +143,8 @@ TEST_F(RoomLayerManagerTest, ApplyLayerMergingOff) {
|
|||||||
LayerMergeType merge{0x00, "Off", false, false, false};
|
LayerMergeType merge{0x00, "Off", false, false, false};
|
||||||
manager_.ApplyLayerMerging(merge);
|
manager_.ApplyLayerMerging(merge);
|
||||||
|
|
||||||
EXPECT_EQ(manager_.GetLayerBlendMode(LayerType::BG2_Layout), LayerBlendMode::Off);
|
EXPECT_EQ(manager_.GetLayerBlendMode(LayerType::BG2_Layout), LayerBlendMode::Normal);
|
||||||
EXPECT_EQ(manager_.GetLayerBlendMode(LayerType::BG2_Objects), LayerBlendMode::Off);
|
EXPECT_EQ(manager_.GetLayerBlendMode(LayerType::BG2_Objects), LayerBlendMode::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ TEST_F(ObjectParserTest, ParseSubtype2Object) {
|
|||||||
ASSERT_TRUE(result.ok());
|
ASSERT_TRUE(result.ok());
|
||||||
|
|
||||||
const auto& tiles = result.value();
|
const auto& tiles = result.value();
|
||||||
EXPECT_EQ(tiles.size(), 8);
|
EXPECT_EQ(tiles.size(), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ObjectParserTest, ParseSubtype3Object) {
|
TEST_F(ObjectParserTest, ParseSubtype3Object) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "app/emu/snes.h"
|
#include "app/emu/snes.h"
|
||||||
#include "rom/rom.h"
|
#include "rom/rom.h"
|
||||||
|
#include "util/macro.h"
|
||||||
|
|
||||||
using namespace yaze;
|
using namespace yaze;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user