feat: Update README and z3ed documentation for AI-powered features and editor integration
refactor: Simplify dungeon editor integration tests by using real ROM data and removing mock implementations
This commit is contained in:
23
README.md
23
README.md
@@ -7,11 +7,11 @@ A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM
|
||||
|
||||
## Version 0.3.2 - Release
|
||||
|
||||
#### Asar 65816 Assembler Integration
|
||||
- **Cross-platform ROM patching** with assembly code support
|
||||
- **Symbol extraction** with addresses and opcodes from assembly files
|
||||
- **Assembly validation** with comprehensive error reporting
|
||||
- **Modern C++ API** with safe memory management
|
||||
#### z3ed agent - AI-powered CLI assistant
|
||||
- **AI-assisted ROM hacking** with ollama and Gemini support
|
||||
- **Natural language commands** for editing and querying ROM data
|
||||
- **Tool calling** for structured data extraction and modification
|
||||
- **Interactive chat** with conversation history and context
|
||||
|
||||
#### ZSCustomOverworld v3
|
||||
- **Enhanced overworld editing** capabilities
|
||||
@@ -19,6 +19,12 @@ A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM
|
||||
- **Custom graphics support** and tile management
|
||||
- **Improved compatibility** with existing projects
|
||||
|
||||
#### Asar 65816 Assembler Integration
|
||||
- **Cross-platform ROM patching** with assembly code support
|
||||
- **Symbol extraction** with addresses and opcodes from assembly files
|
||||
- **Assembly validation** with comprehensive error reporting
|
||||
- **Modern C++ API** with safe memory management
|
||||
|
||||
#### Advanced Features
|
||||
- **Theme Management**: Complete theme system with 5+ built-in themes and custom theme editor
|
||||
- **Multi-Session Support**: Work with multiple ROMs simultaneously in docked workspace
|
||||
@@ -28,13 +34,6 @@ A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM
|
||||
- **Modern CLI**: Enhanced z3ed tool with interactive TUI and subcommands
|
||||
- **Cross-Platform**: Full support for Windows, macOS, and Linux
|
||||
|
||||
### 🛠️ Technical Improvements
|
||||
- **Modern CMake 3.16+**: Target-based configuration and build system
|
||||
- **CMakePresets**: Development workflow presets for better productivity
|
||||
- **Cross-platform CI/CD**: Automated builds and testing for all platforms
|
||||
- **Professional packaging**: NSIS, DMG, and DEB/RPM installers
|
||||
- **Enhanced testing**: ROM-dependent test separation for CI compatibility
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build
|
||||
|
||||
@@ -11,7 +11,7 @@ This document is the **source of truth** for the z3ed CLI architecture, design,
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
1. **Conversational Agent**: Chat with an AI (Ollama or Gemini) to explore ROM contents and plan changes using natural language.
|
||||
1. **Conversational Agent**: Chat with an AI (Ollama or Gemini) to explore ROM contents and plan changes using natural language—available from the CLI, terminal UI, and now directly within the YAZE editor.
|
||||
2. **GUI Test Automation**: A gRPC-based test harness allows for widget discovery, test recording/replay, and introspection for debugging and AI-driven validation.
|
||||
3. **Proposal System**: A safe, sandboxed editing workflow where all changes are tracked as "proposals" that require human review and acceptance.
|
||||
4. **Resource-Oriented CLI**: A clean `z3ed <resource> <action>` command structure that is both human-readable and machine-parsable.
|
||||
@@ -150,8 +150,8 @@ Full-screen interactive terminal with table rendering, syntax highlighting, and
|
||||
### Simple Chat (`agent simple-chat`)
|
||||
Lightweight, scriptable text-based REPL that supports single messages, interactive sessions, piped input, and batch files.
|
||||
|
||||
### GUI Chat Widget (In Progress)
|
||||
An ImGui widget in the main YAZE editor that will provide the same functionality with a graphical interface.
|
||||
### GUI Chat Widget (Editor Integration Preview)
|
||||
Accessible from **Debug → Agent Chat** inside YAZE. Provides the same conversation loop as the CLI, including streaming history, JSON/table inspection, and ROM-aware tool dispatch. Current limitations: no proposal preview shortcuts yet, and the window state resets on restart.
|
||||
|
||||
## 7. AI Provider Configuration
|
||||
|
||||
@@ -170,13 +170,13 @@ Z3ED supports multiple AI providers. Configuration is resolved with command-line
|
||||
|
||||
- **Core Infrastructure**: Resource-oriented CLI, proposal workflow, sandbox manager, and resource catalog are all production-ready.
|
||||
- **AI Backends**: Both Ollama (local) and Gemini (cloud) are operational.
|
||||
- **Conversational Agent**: The agent service, tool dispatcher (with 5 read-only tools), and TUI/simple chat interfaces are complete.
|
||||
- **Conversational Agent**: The agent service, tool dispatcher (with 5 read-only tools), TUI/simple chat interfaces, and initial ImGui editor chat widget are complete.
|
||||
- **GUI Test Harness**: A comprehensive GUI testing platform with introspection, widget discovery, recording/replay, and CI integration support.
|
||||
|
||||
### 🚧 Active & Next Steps
|
||||
|
||||
1. **Live LLM Testing (1-2h)**: Verify function calling with real models (Ollama/Gemini).
|
||||
2. **GUI Chat Integration (6-8h)**: Wire the `AgentChatWidget` into the main YAZE editor.
|
||||
2. **GUI Chat Enhancements (4-6h)**: Persist chat state, surface proposal shortcuts, and add toast notifications when new proposals arrive from chats.
|
||||
3. **Expand Tool Coverage (8-10h)**: Add new read-only tools for inspecting dialogue, sprites, and regions.
|
||||
4. **Windows Cross-Platform Testing (8-10h)**: Validate `z3ed` and the test harness on Windows.
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
#include "integration/dungeon_editor_test.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/snes.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
|
||||
@@ -13,382 +8,26 @@ namespace test {
|
||||
|
||||
using namespace yaze::zelda3;
|
||||
|
||||
void DungeonEditorIntegrationTest::SetUp() {
|
||||
ASSERT_TRUE(CreateMockRom().ok());
|
||||
ASSERT_TRUE(LoadTestRoomData().ok());
|
||||
|
||||
dungeon_editor_ = std::make_unique<editor::DungeonEditor>(mock_rom_.get());
|
||||
dungeon_editor_->Initialize();
|
||||
}
|
||||
|
||||
void DungeonEditorIntegrationTest::TearDown() {
|
||||
dungeon_editor_.reset();
|
||||
mock_rom_.reset();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::CreateMockRom() {
|
||||
mock_rom_ = std::make_unique<MockRom>();
|
||||
|
||||
// Generate mock ROM data
|
||||
std::vector<uint8_t> mock_data(kMockRomSize, 0x00);
|
||||
|
||||
// Set up basic ROM structure
|
||||
// Header at 0x7FC0
|
||||
std::string title = "ZELDA3 TEST ROM";
|
||||
std::memcpy(&mock_data[0x7FC0], title.c_str(), std::min(title.length(), size_t(21)));
|
||||
|
||||
// Set ROM size and type
|
||||
mock_data[0x7FD7] = 0x21; // 2MB ROM
|
||||
mock_data[0x7FD8] = 0x00; // SRAM size
|
||||
mock_data[0x7FD9] = 0x00; // Country code (NTSC)
|
||||
mock_data[0x7FDA] = 0x00; // License code
|
||||
mock_data[0x7FDB] = 0x00; // Version
|
||||
|
||||
// Set up room header pointers
|
||||
mock_data[0xB5DD] = 0x00; // Room header pointer low
|
||||
mock_data[0xB5DE] = 0x00; // Room header pointer mid
|
||||
mock_data[0xB5DF] = 0x00; // Room header pointer high
|
||||
|
||||
// Set up object pointers
|
||||
mock_data[0x874C] = 0x00; // Object pointer low
|
||||
mock_data[0x874D] = 0x00; // Object pointer mid
|
||||
mock_data[0x874E] = 0x00; // Object pointer high
|
||||
|
||||
return mock_rom_->LoadAndOwnData(mock_data);
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::LoadTestRoomData() {
|
||||
// Generate test room data
|
||||
auto room_header = GenerateMockRoomHeader(kTestRoomId);
|
||||
auto object_data = GenerateMockObjectData();
|
||||
auto graphics_data = GenerateMockGraphicsData();
|
||||
|
||||
auto mock_rom = static_cast<MockRom*>(mock_rom_.get());
|
||||
mock_rom->SetMockRoomData(kTestRoomId, room_header);
|
||||
mock_rom->SetMockObjectData(kTestObjectId, object_data);
|
||||
mock_rom->SetMockGraphicsData(graphics_data);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::TestObjectParsing() {
|
||||
// Test object parsing without SNES emulation
|
||||
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
|
||||
|
||||
// Verify room was loaded correctly
|
||||
// Test cases using real ROM
|
||||
TEST_F(DungeonEditorIntegrationTest, LoadRoomFromRealRom) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
EXPECT_NE(room.rom(), nullptr);
|
||||
// Note: room_id_ is private, so we can't directly access it in tests
|
||||
|
||||
// Test object loading
|
||||
room.LoadObjects();
|
||||
EXPECT_FALSE(room.GetTileObjects().empty());
|
||||
|
||||
// Verify object properties
|
||||
for (const auto& obj : room.GetTileObjects()) {
|
||||
// Note: id_ is private, so we can't directly access it in tests
|
||||
EXPECT_LE(obj.x_, 31); // Room width limit
|
||||
EXPECT_LE(obj.y_, 31); // Room height limit
|
||||
// Note: rom() method is not const, so we can't call it on const objects
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::TestObjectRendering() {
|
||||
// Test object rendering without SNES emulation
|
||||
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
|
||||
TEST_F(DungeonEditorIntegrationTest, DungeonEditorInitialization) {
|
||||
ASSERT_TRUE(dungeon_editor_->Load().ok());
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectEncodingRoundTrip) {
|
||||
auto room = zelda3::LoadRoomFromRom(rom_.get(), kTestRoomId);
|
||||
room.LoadObjects();
|
||||
|
||||
// Test tile loading for objects
|
||||
for (auto& obj : room.GetTileObjects()) {
|
||||
obj.EnsureTilesLoaded();
|
||||
EXPECT_FALSE(obj.tiles_.empty());
|
||||
}
|
||||
|
||||
// Test room graphics rendering
|
||||
room.LoadRoomGraphics();
|
||||
room.RenderRoomGraphics();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::TestRoomGraphics() {
|
||||
// Test room graphics loading and rendering
|
||||
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
|
||||
|
||||
// Test graphics loading
|
||||
room.LoadRoomGraphics();
|
||||
EXPECT_FALSE(room.blocks().empty());
|
||||
|
||||
// Test graphics rendering
|
||||
room.RenderRoomGraphics();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorIntegrationTest::TestPaletteHandling() {
|
||||
// Test palette loading and application
|
||||
auto room = zelda3::LoadRoomFromRom(mock_rom_.get(), kTestRoomId);
|
||||
|
||||
// Verify palette is set
|
||||
EXPECT_GE(room.palette, 0);
|
||||
EXPECT_LE(room.palette, 0x47); // Max palette index
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockRoomHeader(int room_id) {
|
||||
std::vector<uint8_t> header(32, 0x00);
|
||||
|
||||
// Basic room properties
|
||||
header[0] = 0x00; // Background type, collision, light
|
||||
header[1] = 0x00; // Palette
|
||||
header[2] = 0x01; // Blockset
|
||||
header[3] = 0x01; // Spriteset
|
||||
header[4] = 0x00; // Effect
|
||||
header[5] = 0x00; // Tag1
|
||||
header[6] = 0x00; // Tag2
|
||||
header[7] = 0x00; // Staircase planes
|
||||
header[8] = 0x00; // Staircase planes continued
|
||||
header[9] = 0x00; // Hole warp
|
||||
header[10] = 0x00; // Staircase rooms
|
||||
header[11] = 0x00;
|
||||
header[12] = 0x00;
|
||||
header[13] = 0x00;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockObjectData() {
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
// Add a simple wall object
|
||||
data.push_back(0x08); // X position (2 tiles)
|
||||
data.push_back(0x08); // Y position (2 tiles)
|
||||
data.push_back(0x01); // Object ID (wall)
|
||||
|
||||
// Add layer separator
|
||||
data.push_back(0xFF);
|
||||
data.push_back(0xFF);
|
||||
|
||||
// Add door section
|
||||
data.push_back(0xF0);
|
||||
data.push_back(0xFF);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DungeonEditorIntegrationTest::GenerateMockGraphicsData() {
|
||||
std::vector<uint8_t> data(0x4000, 0x00);
|
||||
|
||||
// Generate basic tile data
|
||||
for (size_t i = 0; i < data.size(); i += 2) {
|
||||
data[i] = 0x00; // Tile low byte
|
||||
data[i + 1] = 0x00; // Tile high byte
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
absl::Status MockRom::SetMockData(const std::vector<uint8_t>& data) {
|
||||
backing_buffer_.assign(data.begin(), data.end());
|
||||
Expand(static_cast<int>(backing_buffer_.size()));
|
||||
if (!backing_buffer_.empty()) {
|
||||
std::memcpy(mutable_data(), backing_buffer_.data(), backing_buffer_.size());
|
||||
}
|
||||
ClearDirty();
|
||||
InitializeMemoryLayout();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status MockRom::LoadAndOwnData(const std::vector<uint8_t>& data) {
|
||||
backing_buffer_.assign(data.begin(), data.end());
|
||||
Expand(static_cast<int>(backing_buffer_.size()));
|
||||
if (!backing_buffer_.empty()) {
|
||||
std::memcpy(mutable_data(), backing_buffer_.data(), backing_buffer_.size());
|
||||
}
|
||||
ClearDirty();
|
||||
|
||||
// Minimal metadata setup via public API
|
||||
set_filename("mock_rom.sfc");
|
||||
auto& palette_groups = *mutable_palette_group();
|
||||
palette_groups.clear();
|
||||
|
||||
if (palette_groups.dungeon_main.size() == 0) {
|
||||
gfx::SnesPalette default_palette;
|
||||
default_palette.Resize(16);
|
||||
palette_groups.dungeon_main.AddPalette(default_palette);
|
||||
}
|
||||
|
||||
// Ensure graphics buffer is sized
|
||||
auto* gfx_buffer = mutable_graphics_buffer();
|
||||
gfx_buffer->assign(backing_buffer_.begin(), backing_buffer_.end());
|
||||
|
||||
InitializeMemoryLayout();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void MockRom::SetMockRoomData(int room_id, const std::vector<uint8_t>& data) {
|
||||
mock_room_data_[room_id] = data;
|
||||
|
||||
if (room_header_table_pc_ == 0 || room_header_data_base_pc_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t header_offset = room_header_data_base_pc_ + kRoomHeaderStride * static_cast<uint32_t>(room_id);
|
||||
EnsureBufferCapacity(header_offset + static_cast<uint32_t>(data.size()));
|
||||
|
||||
std::memcpy(backing_buffer_.data() + header_offset, data.data(), data.size());
|
||||
std::memcpy(mutable_data() + header_offset, data.data(), data.size());
|
||||
|
||||
uint32_t snes_offset = PcToSnes(header_offset);
|
||||
uint32_t pointer_entry = room_header_table_pc_ + static_cast<uint32_t>(room_id) * 2;
|
||||
EnsureBufferCapacity(pointer_entry + 2);
|
||||
backing_buffer_[pointer_entry] = static_cast<uint8_t>(snes_offset & 0xFF);
|
||||
backing_buffer_[pointer_entry + 1] = static_cast<uint8_t>((snes_offset >> 8) & 0xFF);
|
||||
mutable_data()[pointer_entry] = backing_buffer_[pointer_entry];
|
||||
mutable_data()[pointer_entry + 1] = backing_buffer_[pointer_entry + 1];
|
||||
}
|
||||
|
||||
void MockRom::SetMockObjectData(int object_id, const std::vector<uint8_t>& data) {
|
||||
mock_object_data_[object_id] = data;
|
||||
|
||||
if (room_object_table_pc_ == 0 || room_object_data_base_pc_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t object_offset = room_object_data_base_pc_ + kRoomObjectStride * static_cast<uint32_t>(object_id);
|
||||
EnsureBufferCapacity(object_offset + static_cast<uint32_t>(data.size()));
|
||||
|
||||
std::memcpy(backing_buffer_.data() + object_offset, data.data(), data.size());
|
||||
std::memcpy(mutable_data() + object_offset, data.data(), data.size());
|
||||
|
||||
uint32_t snes_offset = PcToSnes(object_offset);
|
||||
uint32_t entry = room_object_table_pc_ + static_cast<uint32_t>(object_id) * 3;
|
||||
EnsureBufferCapacity(entry + 3);
|
||||
backing_buffer_[entry] = static_cast<uint8_t>(snes_offset & 0xFF);
|
||||
backing_buffer_[entry + 1] = static_cast<uint8_t>((snes_offset >> 8) & 0xFF);
|
||||
backing_buffer_[entry + 2] = static_cast<uint8_t>((snes_offset >> 16) & 0xFF);
|
||||
mutable_data()[entry] = backing_buffer_[entry];
|
||||
mutable_data()[entry + 1] = backing_buffer_[entry + 1];
|
||||
mutable_data()[entry + 2] = backing_buffer_[entry + 2];
|
||||
}
|
||||
|
||||
void MockRom::SetMockGraphicsData(const std::vector<uint8_t>& data) {
|
||||
mock_graphics_data_ = data;
|
||||
if (auto* gfx_buffer = mutable_graphics_buffer(); gfx_buffer != nullptr) {
|
||||
gfx_buffer->assign(data.begin(), data.end());
|
||||
}
|
||||
}
|
||||
|
||||
void MockRom::EnsureBufferCapacity(uint32_t size) {
|
||||
if (size <= backing_buffer_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto old_size = backing_buffer_.size();
|
||||
backing_buffer_.resize(size, 0);
|
||||
Expand(static_cast<int>(size));
|
||||
std::memcpy(mutable_data(), backing_buffer_.data(), old_size);
|
||||
}
|
||||
|
||||
void MockRom::InitializeMemoryLayout() {
|
||||
if (backing_buffer_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
room_header_table_pc_ = SnesToPc(0x040000);
|
||||
room_header_data_base_pc_ = SnesToPc(0x040000 + 0x1000);
|
||||
room_object_table_pc_ = SnesToPc(0x050000);
|
||||
room_object_data_base_pc_ = SnesToPc(0x050000 + 0x2000);
|
||||
|
||||
EnsureBufferCapacity(room_header_table_pc_ + 2);
|
||||
EnsureBufferCapacity(room_object_table_pc_ + 3);
|
||||
|
||||
uint32_t header_table_snes = PcToSnes(room_header_table_pc_);
|
||||
EnsureBufferCapacity(kRoomHeaderPointer + 3);
|
||||
backing_buffer_[kRoomHeaderPointer] = static_cast<uint8_t>(header_table_snes & 0xFF);
|
||||
backing_buffer_[kRoomHeaderPointer + 1] = static_cast<uint8_t>((header_table_snes >> 8) & 0xFF);
|
||||
backing_buffer_[kRoomHeaderPointer + 2] = static_cast<uint8_t>((header_table_snes >> 16) & 0xFF);
|
||||
mutable_data()[kRoomHeaderPointer] = backing_buffer_[kRoomHeaderPointer];
|
||||
mutable_data()[kRoomHeaderPointer + 1] = backing_buffer_[kRoomHeaderPointer + 1];
|
||||
mutable_data()[kRoomHeaderPointer + 2] = backing_buffer_[kRoomHeaderPointer + 2];
|
||||
|
||||
EnsureBufferCapacity(kRoomHeaderPointerBank + 1);
|
||||
backing_buffer_[kRoomHeaderPointerBank] = static_cast<uint8_t>((header_table_snes >> 16) & 0xFF);
|
||||
mutable_data()[kRoomHeaderPointerBank] = backing_buffer_[kRoomHeaderPointerBank];
|
||||
|
||||
uint32_t object_table_snes = PcToSnes(room_object_table_pc_);
|
||||
EnsureBufferCapacity(room_object_pointer + 3);
|
||||
backing_buffer_[room_object_pointer] = static_cast<uint8_t>(object_table_snes & 0xFF);
|
||||
backing_buffer_[room_object_pointer + 1] = static_cast<uint8_t>((object_table_snes >> 8) & 0xFF);
|
||||
backing_buffer_[room_object_pointer + 2] = static_cast<uint8_t>((object_table_snes >> 16) & 0xFF);
|
||||
mutable_data()[room_object_pointer] = backing_buffer_[room_object_pointer];
|
||||
mutable_data()[room_object_pointer + 1] = backing_buffer_[room_object_pointer + 1];
|
||||
mutable_data()[room_object_pointer + 2] = backing_buffer_[room_object_pointer + 2];
|
||||
|
||||
for (const auto& [room_id, bytes] : mock_room_data_) {
|
||||
uint32_t offset = room_header_data_base_pc_ + kRoomHeaderStride * static_cast<uint32_t>(room_id);
|
||||
EnsureBufferCapacity(offset + static_cast<uint32_t>(bytes.size()));
|
||||
std::memcpy(backing_buffer_.data() + offset, bytes.data(), bytes.size());
|
||||
std::memcpy(mutable_data() + offset, bytes.data(), bytes.size());
|
||||
|
||||
uint32_t snes = PcToSnes(offset);
|
||||
uint32_t entry = room_header_table_pc_ + static_cast<uint32_t>(room_id) * 2;
|
||||
EnsureBufferCapacity(entry + 2);
|
||||
backing_buffer_[entry] = static_cast<uint8_t>(snes & 0xFF);
|
||||
backing_buffer_[entry + 1] = static_cast<uint8_t>((snes >> 8) & 0xFF);
|
||||
mutable_data()[entry] = backing_buffer_[entry];
|
||||
mutable_data()[entry + 1] = backing_buffer_[entry + 1];
|
||||
}
|
||||
|
||||
for (const auto& [object_id, bytes] : mock_object_data_) {
|
||||
uint32_t offset = room_object_data_base_pc_ + kRoomObjectStride * static_cast<uint32_t>(object_id);
|
||||
EnsureBufferCapacity(offset + static_cast<uint32_t>(bytes.size()));
|
||||
std::memcpy(backing_buffer_.data() + offset, bytes.data(), bytes.size());
|
||||
std::memcpy(mutable_data() + offset, bytes.data(), bytes.size());
|
||||
|
||||
uint32_t snes = PcToSnes(offset);
|
||||
uint32_t entry = room_object_table_pc_ + static_cast<uint32_t>(object_id) * 3;
|
||||
EnsureBufferCapacity(entry + 3);
|
||||
backing_buffer_[entry] = static_cast<uint8_t>(snes & 0xFF);
|
||||
backing_buffer_[entry + 1] = static_cast<uint8_t>((snes >> 8) & 0xFF);
|
||||
backing_buffer_[entry + 2] = static_cast<uint8_t>((snes >> 16) & 0xFF);
|
||||
mutable_data()[entry] = backing_buffer_[entry];
|
||||
mutable_data()[entry + 1] = backing_buffer_[entry + 1];
|
||||
mutable_data()[entry + 2] = backing_buffer_[entry + 2];
|
||||
}
|
||||
}
|
||||
|
||||
bool MockRom::ValidateRoomData(int room_id) const {
|
||||
return mock_room_data_.find(room_id) != mock_room_data_.end();
|
||||
}
|
||||
|
||||
bool MockRom::ValidateObjectData(int object_id) const {
|
||||
return mock_object_data_.find(object_id) != mock_object_data_.end();
|
||||
}
|
||||
|
||||
// Test cases
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectParsingTest) {
|
||||
EXPECT_TRUE(TestObjectParsing().ok());
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, ObjectRenderingTest) {
|
||||
EXPECT_TRUE(TestObjectRendering().ok());
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, RoomGraphicsTest) {
|
||||
EXPECT_TRUE(TestRoomGraphics().ok());
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, PaletteHandlingTest) {
|
||||
EXPECT_TRUE(TestPaletteHandling().ok());
|
||||
}
|
||||
|
||||
TEST_F(DungeonEditorIntegrationTest, MockRomValidation) {
|
||||
EXPECT_TRUE(static_cast<MockRom*>(mock_rom_.get())->ValidateRoomData(kTestRoomId));
|
||||
EXPECT_TRUE(static_cast<MockRom*>(mock_rom_.get())->ValidateObjectData(kTestObjectId));
|
||||
auto encoded = room.EncodeObjects();
|
||||
EXPECT_FALSE(encoded.empty());
|
||||
EXPECT_EQ(encoded[encoded.size()-1], 0xFF); // Terminator
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
} // namespace yaze
|
||||
|
||||
@@ -1,93 +1,42 @@
|
||||
#ifndef YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
|
||||
#define YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/editor/dungeon/dungeon_editor.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace test {
|
||||
|
||||
class MockRom;
|
||||
|
||||
/**
|
||||
* @brief Integration test framework for dungeon editor components
|
||||
*
|
||||
* This class provides a comprehensive testing framework for the dungeon editor,
|
||||
* allowing modular testing of individual components and their interactions.
|
||||
* @brief Integration test framework using real ROM data
|
||||
*/
|
||||
class DungeonEditorIntegrationTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
void SetUp() override {
|
||||
// Use the real ROM
|
||||
rom_ = std::make_unique<Rom>();
|
||||
ASSERT_TRUE(rom_->LoadFromFile("build/bin/zelda3.sfc").ok());
|
||||
dungeon_editor_ = std::make_unique<editor::DungeonEditor>();
|
||||
dungeon_editor_->set_rom(rom_.get());
|
||||
}
|
||||
|
||||
// Test data setup
|
||||
absl::Status CreateMockRom();
|
||||
absl::Status LoadTestRoomData();
|
||||
|
||||
// Component testing helpers
|
||||
absl::Status TestObjectParsing();
|
||||
absl::Status TestObjectRendering();
|
||||
absl::Status TestRoomGraphics();
|
||||
absl::Status TestPaletteHandling();
|
||||
|
||||
// Mock data generators
|
||||
std::vector<uint8_t> GenerateMockRoomHeader(int room_id);
|
||||
std::vector<uint8_t> GenerateMockObjectData();
|
||||
std::vector<uint8_t> GenerateMockGraphicsData();
|
||||
void TearDown() override {
|
||||
dungeon_editor_.reset();
|
||||
rom_.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<MockRom> mock_rom_;
|
||||
std::unique_ptr<Rom> rom_;
|
||||
std::unique_ptr<editor::DungeonEditor> dungeon_editor_;
|
||||
|
||||
// Test constants
|
||||
static constexpr int kTestRoomId = 0x01;
|
||||
static constexpr int kTestObjectId = 0x10;
|
||||
static constexpr size_t kMockRomSize = 0x200000; // 2MB mock ROM
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mock ROM class for testing without real ROM files
|
||||
*/
|
||||
class MockRom : public Rom {
|
||||
public:
|
||||
MockRom() = default;
|
||||
|
||||
// Test data injection
|
||||
absl::Status SetMockData(const std::vector<uint8_t>& data);
|
||||
absl::Status LoadAndOwnData(const std::vector<uint8_t>& data);
|
||||
void SetMockRoomData(int room_id, const std::vector<uint8_t>& data);
|
||||
void SetMockObjectData(int object_id, const std::vector<uint8_t>& data);
|
||||
void SetMockGraphicsData(const std::vector<uint8_t>& data);
|
||||
|
||||
// Validation helpers
|
||||
bool ValidateRoomData(int room_id) const;
|
||||
bool ValidateObjectData(int object_id) const;
|
||||
|
||||
private:
|
||||
void EnsureBufferCapacity(uint32_t size);
|
||||
void InitializeMemoryLayout();
|
||||
|
||||
std::vector<uint8_t> backing_buffer_;
|
||||
std::map<int, std::vector<uint8_t>> mock_room_data_;
|
||||
std::map<int, std::vector<uint8_t>> mock_object_data_;
|
||||
std::vector<uint8_t> mock_graphics_data_;
|
||||
|
||||
uint32_t room_header_table_pc_ = 0;
|
||||
uint32_t room_header_data_base_pc_ = 0;
|
||||
uint32_t room_object_table_pc_ = 0;
|
||||
uint32_t room_object_data_base_pc_ = 0;
|
||||
|
||||
static constexpr uint32_t kRoomHeaderStride = 0x40;
|
||||
static constexpr uint32_t kRoomObjectStride = 0x100;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
|
||||
#endif // YAZE_TEST_INTEGRATION_DUNGEON_EDITOR_TEST_H
|
||||
|
||||
Reference in New Issue
Block a user