yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_e2e_test.cc
Go to the documentation of this file.
1#include <gtest/gtest.h>
2#include <filesystem>
3#include <memory>
4#include <vector>
5#include <string>
6
7#include "app/rom.h"
10#include "testing.h"
11
12namespace yaze {
13namespace test {
14
26class OverworldE2ETest : public ::testing::Test {
27 protected:
28 void SetUp() override {
29 // Skip tests if ROM is not available
30 if (getenv("YAZE_SKIP_ROM_TESTS")) {
31 GTEST_SKIP() << "ROM tests disabled";
32 }
33
34 // Get ROM path from environment or use default
35 const char* rom_path_env = getenv("YAZE_TEST_ROM_PATH");
36 vanilla_rom_path_ = rom_path_env ? rom_path_env : "zelda3.sfc";
37
38 if (!std::filesystem::exists(vanilla_rom_path_)) {
39 GTEST_SKIP() << "Test ROM not found: " << vanilla_rom_path_;
40 }
41
42 // Create test ROM copies
43 vanilla_test_path_ = "test_vanilla_e2e.sfc";
44 edited_test_path_ = "test_edited_e2e.sfc";
45 golden_data_path_ = "golden_data_e2e.h";
46
47 // Copy vanilla ROM for testing
48 std::filesystem::copy_file(vanilla_rom_path_, vanilla_test_path_);
49 }
50
51 void TearDown() override {
52 // Clean up test files
53 std::vector<std::string> test_files = {
55 };
56
57 for (const auto& file : test_files) {
58 if (std::filesystem::exists(file)) {
59 std::filesystem::remove(file);
60 }
61 }
62 }
63
64 // Helper to extract golden data from ROM
65 absl::Status ExtractGoldenData(const std::string& rom_path,
66 const std::string& output_path) {
67 // Run the golden data extractor
68 std::string command = "./overworld_golden_data_extractor " + rom_path + " " + output_path;
69 int result = system(command.c_str());
70
71 if (result != 0) {
72 return absl::InternalError("Failed to extract golden data");
73 }
74
75 return absl::OkStatus();
76 }
77
78 // Helper to validate ROM against golden data
79 bool ValidateROMAgainstGoldenData(Rom& rom, const std::string& /* golden_data_path */) {
80 // This would load the generated golden data header and compare values
81 // For now, we'll do basic validation
82
83 // Check basic ROM properties
84 if (rom.title().empty()) return false;
85 if (rom.size() < 1024*1024) return false; // At least 1MB
86
87 // Check ASM version
88 auto asm_version = rom.ReadByte(0x140145);
89 if (!asm_version.ok()) return false;
90
91 return true;
92 }
93
94 std::string vanilla_rom_path_;
95 std::string vanilla_test_path_;
96 std::string edited_test_path_;
97 std::string golden_data_path_;
98};
99
100// Test 1: Extract golden data from vanilla ROM
101TEST_F(OverworldE2ETest, ExtractVanillaGoldenData) {
102 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
103 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
104
105 // Extract golden data
106 ASSERT_OK(ExtractGoldenData(vanilla_test_path_, golden_data_path_));
107
108 // Verify golden data file was created
109 EXPECT_TRUE(std::filesystem::exists(golden_data_path_));
110
111 // Validate ROM against golden data
112 EXPECT_TRUE(ValidateROMAgainstGoldenData(*rom, golden_data_path_));
113}
114
115// Test 2: Load and validate vanilla overworld data
116TEST_F(OverworldE2ETest, LoadVanillaOverworldData) {
117 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
118 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
119
120 zelda3::Overworld overworld(rom.get());
121 auto status = overworld.Load(rom.get());
122 ASSERT_TRUE(status.ok());
123
124 // Validate basic overworld structure
125 EXPECT_TRUE(overworld.is_loaded());
126
127 const auto& maps = overworld.overworld_maps();
128 EXPECT_EQ(maps.size(), 160);
129
130 // Validate that we have a vanilla ROM (ASM version 0xFF)
131 auto asm_version = rom->ReadByte(0x140145);
132 ASSERT_TRUE(asm_version.ok());
133 EXPECT_EQ(*asm_version, 0xFF);
134
135 // Validate expansion flags for vanilla
136 EXPECT_FALSE(overworld.expanded_tile16());
137 EXPECT_FALSE(overworld.expanded_tile32());
138
139 // Validate data structures
140 const auto& entrances = overworld.entrances();
141 const auto& exits = overworld.exits();
142 const auto& holes = overworld.holes();
143 const auto& items = overworld.all_items();
144
145 EXPECT_EQ(entrances.size(), 129);
146 EXPECT_EQ(exits->size(), 0x4F);
147 EXPECT_EQ(holes.size(), 0x13);
148 EXPECT_GE(items.size(), 0);
149
150 // Validate sprite data (3 game states)
151 const auto& sprites = overworld.all_sprites();
152 EXPECT_EQ(sprites.size(), 3);
153}
154
155// Test 3: Apply ZSCustomOverworld v3 ASM and validate changes
156TEST_F(OverworldE2ETest, ApplyZSCustomOverworldV3) {
157 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
158 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
159
160 // Apply ZSCustomOverworld v3 ASM
161 // This would typically be done through the editor, but we can simulate it
162 ASSERT_OK(rom->WriteByte(0x140145, 0x03)); // Set ASM version to v3
163
164 // Enable v3 features
165 ASSERT_OK(rom->WriteByte(0x140146, 0x01)); // Enable main palettes
166 ASSERT_OK(rom->WriteByte(0x140147, 0x01)); // Enable area-specific BG
167 ASSERT_OK(rom->WriteByte(0x140148, 0x01)); // Enable subscreen overlay
168 ASSERT_OK(rom->WriteByte(0x140149, 0x01)); // Enable animated GFX
169 ASSERT_OK(rom->WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups
170 ASSERT_OK(rom->WriteByte(0x14014B, 0x01)); // Enable mosaic
171
172 // Save the modified ROM
173 ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
174
175 // Reload and validate
176 std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
177 ASSERT_OK(reloaded_rom->LoadFromFile(edited_test_path_));
178
179 // Validate ASM version was applied
180 auto asm_version = reloaded_rom->ReadByte(0x140145);
181 ASSERT_TRUE(asm_version.ok());
182 EXPECT_EQ(*asm_version, 0x03);
183
184 // Validate feature flags
185 auto main_palettes = reloaded_rom->ReadByte(0x140146);
186 auto area_bg = reloaded_rom->ReadByte(0x140147);
187 auto subscreen_overlay = reloaded_rom->ReadByte(0x140148);
188 auto animated_gfx = reloaded_rom->ReadByte(0x140149);
189 auto custom_tiles = reloaded_rom->ReadByte(0x14014A);
190 auto mosaic = reloaded_rom->ReadByte(0x14014B);
191
192 ASSERT_TRUE(main_palettes.ok());
193 ASSERT_TRUE(area_bg.ok());
194 ASSERT_TRUE(subscreen_overlay.ok());
195 ASSERT_TRUE(animated_gfx.ok());
196 ASSERT_TRUE(custom_tiles.ok());
197 ASSERT_TRUE(mosaic.ok());
198
199 EXPECT_EQ(*main_palettes, 0x01);
200 EXPECT_EQ(*area_bg, 0x01);
201 EXPECT_EQ(*subscreen_overlay, 0x01);
202 EXPECT_EQ(*animated_gfx, 0x01);
203 EXPECT_EQ(*custom_tiles, 0x01);
204 EXPECT_EQ(*mosaic, 0x01);
205
206 // Load overworld and validate v3 features are detected
207 zelda3::Overworld overworld(reloaded_rom.get());
208 auto status = overworld.Load(reloaded_rom.get());
209 ASSERT_TRUE(status.ok());
210
211 // v3 should have expanded features available
212 EXPECT_TRUE(overworld.expanded_tile16());
213 EXPECT_TRUE(overworld.expanded_tile32());
214}
215
216// Test 4: Make overworld edits and validate persistence
217TEST_F(OverworldE2ETest, OverworldEditPersistence) {
218 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
219 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
220
221 // Load overworld
222 zelda3::Overworld overworld(rom.get());
223 auto status = overworld.Load(rom.get());
224 ASSERT_TRUE(status.ok());
225
226 // Make some edits to overworld maps
227 auto* map0 = overworld.mutable_overworld_map(0);
228 uint8_t original_gfx = map0->area_graphics();
229 uint8_t original_palette = map0->main_palette();
230
231 // Change graphics and palette
232 map0->set_area_graphics(0x01);
233 map0->set_main_palette(0x02);
234
235 // Save the changes
236 auto save_maps_status = overworld.SaveOverworldMaps();
237 ASSERT_TRUE(save_maps_status.ok());
238 auto save_props_status = overworld.SaveMapProperties();
239 ASSERT_TRUE(save_props_status.ok());
240
241 // Save ROM
242 ASSERT_OK(rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
243
244 // Reload ROM and validate changes persisted
245 std::unique_ptr<Rom> reloaded_rom = std::make_unique<Rom>();
246 ASSERT_OK(reloaded_rom->LoadFromFile(edited_test_path_));
247
248 zelda3::Overworld reloaded_overworld(reloaded_rom.get());
249 ASSERT_OK(reloaded_overworld.Load(reloaded_rom.get()));
250
251 const auto& reloaded_map0 = reloaded_overworld.overworld_map(0);
252 EXPECT_EQ(reloaded_map0->area_graphics(), 0x01);
253 EXPECT_EQ(reloaded_map0->main_palette(), 0x02);
254}
255
256// Test 5: Validate coordinate calculations match ZScream exactly
257TEST_F(OverworldE2ETest, CoordinateCalculationValidation) {
258 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
259 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
260
261 zelda3::Overworld overworld(rom.get());
262 ASSERT_OK(overworld.Load(rom.get()));
263
264 const auto& entrances = overworld.entrances();
265 EXPECT_EQ(entrances.size(), 129);
266
267 // Test coordinate calculation for first 10 entrances
268 for (int i = 0; i < std::min(10, static_cast<int>(entrances.size())); i++) {
269 const auto& entrance = entrances[i];
270
271 // ZScream coordinate calculation logic
272 uint16_t map_pos = entrance.map_pos_;
273 uint16_t map_id = entrance.map_id_;
274
275 int position = map_pos >> 1;
276 int x_coord = position % 64;
277 int y_coord = position >> 6;
278 int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
279 int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
280
281 EXPECT_EQ(entrance.x_, expected_x) << "Entrance " << i << " X coordinate mismatch";
282 EXPECT_EQ(entrance.y_, expected_y) << "Entrance " << i << " Y coordinate mismatch";
283 }
284
285 // Test hole coordinate calculation with 0x400 offset
286 const auto& holes = overworld.holes();
287 EXPECT_EQ(holes.size(), 0x13);
288
289 for (int i = 0; i < std::min(5, static_cast<int>(holes.size())); i++) {
290 const auto& hole = holes[i];
291
292 // ZScream hole coordinate calculation with 0x400 offset
293 uint16_t map_pos = hole.map_pos_;
294 uint16_t map_id = hole.map_id_;
295
296 int position = map_pos >> 1;
297 int x_coord = position % 64;
298 int y_coord = position >> 6;
299 int expected_x = (x_coord * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512);
300 int expected_y = (y_coord * 16) + (((map_id % 64) / 8) * 512);
301
302 EXPECT_EQ(hole.x_, expected_x) << "Hole " << i << " X coordinate mismatch";
303 EXPECT_EQ(hole.y_, expected_y) << "Hole " << i << " Y coordinate mismatch";
304 EXPECT_TRUE(hole.is_hole_) << "Hole " << i << " should be marked as hole";
305 }
306}
307
308// Test 6: Comprehensive before/after validation
309TEST_F(OverworldE2ETest, BeforeAfterValidation) {
310 // Extract golden data from vanilla ROM
311 ASSERT_OK(ExtractGoldenData(vanilla_test_path_, golden_data_path_));
312
313 // Load vanilla ROM and make some changes
314 std::unique_ptr<Rom> vanilla_rom = std::make_unique<Rom>();
315 ASSERT_OK(vanilla_rom->LoadFromFile(vanilla_test_path_));
316
317 // Store some original values for comparison
318 auto original_asm_version = vanilla_rom->ReadByte(0x140145);
319 auto original_graphics_0 = vanilla_rom->ReadByte(0x7C9C); // First map graphics
320 auto original_palette_0 = vanilla_rom->ReadByte(0x7D1C); // First map palette
321
322 ASSERT_TRUE(original_asm_version.ok());
323 ASSERT_TRUE(original_graphics_0.ok());
324 ASSERT_TRUE(original_palette_0.ok());
325
326 // Make changes
327 auto write1 = vanilla_rom->WriteByte(0x140145, 0x03); // Apply v3 ASM
328 ASSERT_TRUE(write1.ok());
329 auto write2 = vanilla_rom->WriteByte(0x7C9C, 0x01); // Change first map graphics
330 ASSERT_TRUE(write2.ok());
331 auto write3 = vanilla_rom->WriteByte(0x7D1C, 0x02); // Change first map palette
332 ASSERT_TRUE(write3.ok());
333
334 // Save modified ROM
335 ASSERT_OK(vanilla_rom->SaveToFile(Rom::SaveSettings{.filename = edited_test_path_}));
336
337 // Reload and validate changes
338 std::unique_ptr<Rom> modified_rom = std::make_unique<Rom>();
339 ASSERT_OK(modified_rom->LoadFromFile(edited_test_path_));
340
341 auto modified_asm_version = modified_rom->ReadByte(0x140145);
342 auto modified_graphics_0 = modified_rom->ReadByte(0x7C9C);
343 auto modified_palette_0 = modified_rom->ReadByte(0x7D1C);
344
345 ASSERT_TRUE(modified_asm_version.ok());
346 ASSERT_TRUE(modified_graphics_0.ok());
347 ASSERT_TRUE(modified_palette_0.ok());
348
349 // Validate changes were applied
350 EXPECT_EQ(*modified_asm_version, 0x03);
351 EXPECT_EQ(*modified_graphics_0, 0x01);
352 EXPECT_EQ(*modified_palette_0, 0x02);
353
354 // Validate original values were different
355 EXPECT_NE(*original_asm_version, *modified_asm_version);
356 EXPECT_NE(*original_graphics_0, *modified_graphics_0);
357 EXPECT_NE(*original_palette_0, *modified_palette_0);
358}
359
360// Test 7: Integration with RomDependentTestSuite
361TEST_F(OverworldE2ETest, RomDependentTestSuiteIntegration) {
362 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
363 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
364
365 // Test that our overworld loading works with RomDependentTestSuite patterns
366 zelda3::Overworld overworld(rom.get());
367 auto status = overworld.Load(rom.get());
368 ASSERT_TRUE(status.ok());
369
370 // Validate ROM-dependent features work correctly
371 EXPECT_TRUE(overworld.is_loaded());
372
373 const auto& maps = overworld.overworld_maps();
374 EXPECT_EQ(maps.size(), 160);
375
376 // Test that we can access the same data structures as RomDependentTestSuite
377 for (int i = 0; i < std::min(10, static_cast<int>(maps.size())); i++) {
378 const auto& map = maps[i];
379
380 // Verify map properties are accessible
381 EXPECT_GE(map.area_graphics(), 0);
382 EXPECT_GE(map.main_palette(), 0);
383 EXPECT_GE(map.area_size(), zelda3::AreaSizeEnum::SmallArea);
384 EXPECT_LE(map.area_size(), zelda3::AreaSizeEnum::TallArea);
385 }
386
387 // Test that sprite data is accessible (matches RomDependentTestSuite expectations)
388 const auto& sprites = overworld.all_sprites();
389 EXPECT_EQ(sprites.size(), 3); // Three game states
390
391 // Test that item data is accessible
392 const auto& items = overworld.all_items();
393 EXPECT_GE(items.size(), 0);
394
395 // Test that entrance/exit data is accessible
396 const auto& entrances = overworld.entrances();
397 const auto& exits = overworld.exits();
398 EXPECT_EQ(entrances.size(), 129);
399 EXPECT_EQ(exits->size(), 0x4F);
400}
401
402// Test 8: Performance and stability testing
403TEST_F(OverworldE2ETest, PerformanceAndStability) {
404 std::unique_ptr<Rom> rom = std::make_unique<Rom>();
405 ASSERT_OK(rom->LoadFromFile(vanilla_test_path_));
406
407 // Test multiple load/unload cycles
408 for (int cycle = 0; cycle < 5; cycle++) {
409 zelda3::Overworld overworld(rom.get());
410 auto status = overworld.Load(rom.get());
411 ASSERT_TRUE(status.ok()) << "Load failed on cycle " << cycle;
412
413 // Validate basic structure
414 const auto& maps = overworld.overworld_maps();
415 EXPECT_EQ(maps.size(), 160) << "Map count mismatch on cycle " << cycle;
416
417 const auto& entrances = overworld.entrances();
418 EXPECT_EQ(entrances.size(), 129) << "Entrance count mismatch on cycle " << cycle;
419
420 const auto& exits = overworld.exits();
421 EXPECT_EQ(exits->size(), 0x4F) << "Exit count mismatch on cycle " << cycle;
422 }
423}
424
425} // namespace test
426} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
auto size() const
Definition rom.h:202
absl::StatusOr< uint8_t > ReadByte(int offset)
Definition rom.cc:658
auto title() const
Definition rom.h:201
Comprehensive End-to-End Overworld Test Suite.
bool ValidateROMAgainstGoldenData(Rom &rom, const std::string &)
absl::Status ExtractGoldenData(const std::string &rom_path, const std::string &output_path)
Represents the full Overworld data, light and dark world.
Definition overworld.h:135
absl::Status Load(Rom *rom)
Definition overworld.cc:27
auto expanded_tile32() const
Definition overworld.h:289
absl::Status SaveMapProperties()
const std::vector< OverworldEntrance > & holes() const
Definition overworld.h:273
auto all_sprites() const
Definition overworld.h:317
auto all_items() const
Definition overworld.h:313
auto is_loaded() const
Definition overworld.h:287
auto expanded_tile16() const
Definition overworld.h:288
auto overworld_map(int i) const
Definition overworld.h:258
auto mutable_overworld_map(int i)
Definition overworld.h:259
absl::Status SaveOverworldMaps()
Definition overworld.cc:960
const std::vector< OverworldEntrance > & entrances() const
Definition overworld.h:270
auto overworld_maps() const
Definition overworld.h:257
TEST_F(DungeonObjectRenderingE2ETests, RunAllTests)
Main namespace for the application.
#define ASSERT_OK(expr)
Definition testing.h:12