feat: Implement advanced routing system for agent tool responses

- Introduced AdvancedRouter class for routing hex analysis, map editing, and palette analysis responses.
- Added methods for generating GUI automation scripts and synthesizing multi-tool responses.
- Implemented knowledge modules for agent pretraining, covering ROM structure, hex analysis patterns, and tool usage examples.
- Enhanced data handling with structured responses, including summaries, detailed data, and next steps for user guidance.
- Refactored project build process to utilize std::filesystem for cross-platform patch application.
This commit is contained in:
scawful
2025-10-05 01:44:23 -04:00
parent dc89d7b818
commit 7309baa8c2
5 changed files with 592 additions and 18 deletions

View File

@@ -2,6 +2,7 @@
#include "cli/z3ed.h"
#include "util/file_util.h"
#include "util/bps.h"
#include <filesystem>
#ifndef _WIN32
#include <glob.h>
#endif
@@ -40,17 +41,21 @@ absl::Status ProjectBuild::Run(const std::vector<std::string>& arg_vec) {
return status;
}
#ifdef _WIN32
return absl::UnimplementedError(
"Project build with patches is not implemented on Windows yet.");
#else
// Apply BPS patches
glob_t glob_result;
std::string pattern = project.patches_folder + "/*.bps";
glob(pattern.c_str(), GLOB_TILDE, NULL, &glob_result);
for (unsigned int i = 0; i < glob_result.gl_pathc; ++i) {
std::string patch_file = glob_result.gl_pathv[i];
// Apply BPS patches - cross-platform with std::filesystem
namespace fs = std::filesystem;
std::vector<std::string> bps_files;
try {
for (const auto& entry : fs::directory_iterator(project.patches_folder)) {
if (entry.path().extension() == ".bps") {
bps_files.push_back(entry.path().string());
}
}
} catch (const fs::filesystem_error& e) {
// Patches folder doesn't exist or not accessible
}
for (const auto& patch_file : bps_files) {
std::vector<uint8_t> patch_data;
auto patch_contents = util::LoadFile(patch_file);
std::copy(patch_contents.begin(), patch_contents.end(),
@@ -60,13 +65,21 @@ absl::Status ProjectBuild::Run(const std::vector<std::string>& arg_vec) {
rom.LoadFromData(patched_rom);
}
// Run asar on assembly files
glob_t glob_result_asm;
std::string pattern_asm = project.patches_folder + "/*.asm";
glob(pattern_asm.c_str(), GLOB_TILDE, NULL, &glob_result_asm);
for (unsigned int i = 0; i < glob_result_asm.gl_pathc; ++i) {
// Run asar on assembly files - cross-platform
std::vector<std::string> asm_files;
try {
for (const auto& entry : fs::directory_iterator(project.patches_folder)) {
if (entry.path().extension() == ".asm") {
asm_files.push_back(entry.path().string());
}
}
} catch (const fs::filesystem_error& e) {
// No asm files
}
for (const auto& asm_file : asm_files) {
AsarPatch asar_patch;
auto status = asar_patch.Run({glob_result_asm.gl_pathv[i]});
auto status = asar_patch.Run({asm_file});
if (!status.ok()) {
return status;
}
@@ -82,7 +95,6 @@ absl::Status ProjectBuild::Run(const std::vector<std::string>& arg_vec) {
std::cout << " Output ROM: " << output_file << std::endl;
return absl::OkStatus();
#endif
}
} // namespace cli

View File

@@ -0,0 +1,208 @@
#include "cli/service/agent/advanced_routing.h"
#include <algorithm>
#include <sstream>
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
namespace yaze {
namespace cli {
namespace agent {
AdvancedRouter::RoutedResponse AdvancedRouter::RouteHexAnalysis(
const std::vector<uint8_t>& data,
uint32_t address,
const RouteContext& ctx) {
RoutedResponse response;
// Infer data type
std::string data_type = InferDataType(data);
auto patterns = ExtractPatterns(data);
// Summary for user
response.summary = absl::StrFormat(
"Address 0x%06X contains %s (%zu bytes)",
address, data_type, data.size());
// Detailed data for agent with structure hints
std::ostringstream detailed;
detailed << absl::StrFormat("Raw hex at 0x%06X:\n", address);
for (size_t i = 0; i < data.size(); i += 16) {
detailed << absl::StrFormat("%06X: ", address + i);
for (size_t j = i; j < std::min(i + 16, data.size()); ++j) {
detailed << absl::StrFormat("%02X ", data[j]);
}
detailed << " | ";
for (size_t j = i; j < std::min(i + 16, data.size()); ++j) {
char c = data[j];
detailed << (isprint(c) ? c : '.');
}
detailed << "\n";
}
if (!patterns.empty()) {
detailed << "\nDetected patterns:\n";
for (const auto& pattern : patterns) {
detailed << "- " << pattern << "\n";
}
}
response.detailed_data = detailed.str();
// Next steps based on data type
if (data_type.find("sprite") != std::string::npos) {
response.next_steps = "Use resource-list --type=sprite to identify sprite IDs";
} else if (data_type.find("tile") != std::string::npos) {
response.next_steps = "Use overworld-find-tile to see where this tile appears";
} else if (data_type.find("palette") != std::string::npos) {
response.next_steps = "Use palette-get-colors to see full palette";
} else {
response.next_steps = "Use hex-search to find similar patterns in ROM";
}
return response;
}
AdvancedRouter::RoutedResponse AdvancedRouter::RouteMapEdit(
const std::string& edit_intent,
const RouteContext& ctx) {
RoutedResponse response;
// Parse intent and generate action sequence
response.summary = "Preparing map edit operation";
response.needs_approval = true;
// Generate GUI automation steps
response.gui_actions = {
"Click(\"Overworld Editor\")",
"Wait(500)",
"Click(canvas, x, y)",
"SelectTile(tile_id)",
"Click(target_x, target_y)",
"Wait(100)",
"Screenshot(\"after_edit.png\")",
};
response.detailed_data = GenerateGUIScript(response.gui_actions);
response.next_steps = "Review proposed changes, then approve or modify";
return response;
}
AdvancedRouter::RoutedResponse AdvancedRouter::RoutePaletteAnalysis(
const std::vector<uint16_t>& colors,
const RouteContext& ctx) {
RoutedResponse response;
// Analyze color relationships
int unique_colors = 0;
std::map<uint16_t, int> color_counts;
for (uint16_t c : colors) {
color_counts[c]++;
}
unique_colors = color_counts.size();
response.summary = absl::StrFormat(
"Palette has %zu colors (%d unique)",
colors.size(), unique_colors);
// Detailed breakdown
std::ostringstream detailed;
detailed << "Color breakdown:\n";
for (size_t i = 0; i < colors.size(); ++i) {
uint16_t snes = colors[i];
uint8_t r = (snes & 0x1F) << 3;
uint8_t g = ((snes >> 5) & 0x1F) << 3;
uint8_t b = ((snes >> 10) & 0x1F) << 3;
detailed << absl::StrFormat(" [%zu] $%04X = #%02X%02X%02X\n", i, snes, r, g, b);
}
if (color_counts.size() < colors.size()) {
detailed << "\nDuplicates found - optimization possible\n";
}
response.detailed_data = detailed.str();
response.next_steps = "Use palette-set-color to modify colors";
return response;
}
AdvancedRouter::RoutedResponse AdvancedRouter::SynthesizeMultiToolResponse(
const std::vector<std::string>& tool_results,
const RouteContext& ctx) {
RoutedResponse response;
// Combine results intelligently
response.summary = absl::StrFormat("Analyzed %zu data sources", tool_results.size());
response.detailed_data = absl::StrJoin(tool_results, "\n---\n");
// Generate insights
response.next_steps = "Analysis complete. " + ctx.user_intent;
return response;
}
std::string AdvancedRouter::GenerateGUIScript(
const std::vector<std::string>& actions) {
std::ostringstream script;
script << "# Generated GUI Automation Script\n";
script << "test: \"Automated Edit\"\n";
script << "steps:\n";
for (const auto& action : actions) {
script << " - " << action << "\n";
}
return script.str();
}
std::string AdvancedRouter::InferDataType(const std::vector<uint8_t>& data) {
if (data.size() == 8) return "tile16 data";
if (data.size() % 3 == 0 && data.size() <= 48) return "sprite data";
if (data.size() == 32) return "palette data (16 colors)";
if (data.size() > 1000) return "compressed data block";
return "unknown data";
}
std::vector<std::string> AdvancedRouter::ExtractPatterns(
const std::vector<uint8_t>& data) {
std::vector<std::string> patterns;
// Check for repeating bytes
if (data.size() > 2) {
bool all_same = true;
for (size_t i = 1; i < data.size(); ++i) {
if (data[i] != data[0]) {
all_same = false;
break;
}
}
if (all_same) {
patterns.push_back(absl::StrFormat("Repeating byte: 0x%02X", data[0]));
}
}
// Check for ascending/descending sequences
if (data.size() > 3) {
bool ascending = true, descending = true;
for (size_t i = 1; i < data.size(); ++i) {
if (data[i] != data[i-1] + 1) ascending = false;
if (data[i] != data[i-1] - 1) descending = false;
}
if (ascending) patterns.push_back("Ascending sequence");
if (descending) patterns.push_back("Descending sequence");
}
return patterns;
}
std::string AdvancedRouter::FormatForAgent(const std::string& raw_data) {
// Format data for easy agent consumption
return "```\n" + raw_data + "\n```";
}
} // namespace agent
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,85 @@
#ifndef YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_
#define YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_
#include <string>
#include <vector>
#include <memory>
#include "absl/status/statusor.h"
namespace yaze {
class Rom;
namespace cli {
namespace agent {
/**
* @brief Advanced routing system for agent tool responses
*
* Optimizes information flow back to agent for:
* - Map editing with GUI automation
* - Hex data analysis and pattern recognition
* - Multi-step operations with context preservation
*/
class AdvancedRouter {
public:
struct RouteContext {
Rom* rom;
std::string user_intent;
std::vector<std::string> tool_calls_made;
std::string accumulated_knowledge;
};
struct RoutedResponse {
std::string summary; // High-level answer
std::string detailed_data; // Raw data for agent processing
std::string next_steps; // Suggested follow-up actions
std::vector<std::string> gui_actions; // For test harness
bool needs_approval; // For proposals
};
/**
* @brief Route hex data analysis response
*/
static RoutedResponse RouteHexAnalysis(
const std::vector<uint8_t>& data,
uint32_t address,
const RouteContext& ctx);
/**
* @brief Route map editing response
*/
static RoutedResponse RouteMapEdit(
const std::string& edit_intent,
const RouteContext& ctx);
/**
* @brief Route palette analysis response
*/
static RoutedResponse RoutePaletteAnalysis(
const std::vector<uint16_t>& colors,
const RouteContext& ctx);
/**
* @brief Synthesize multi-tool response
*/
static RoutedResponse SynthesizeMultiToolResponse(
const std::vector<std::string>& tool_results,
const RouteContext& ctx);
/**
* @brief Generate GUI automation script
*/
static std::string GenerateGUIScript(
const std::vector<std::string>& actions);
private:
static std::string InferDataType(const std::vector<uint8_t>& data);
static std::vector<std::string> ExtractPatterns(const std::vector<uint8_t>& data);
static std::string FormatForAgent(const std::string& raw_data);
};
} // namespace agent
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_

View File

@@ -0,0 +1,206 @@
#include "cli/service/agent/agent_pretraining.h"
#include "absl/strings/str_format.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace cli {
namespace agent {
std::vector<AgentPretraining::KnowledgeModule> AgentPretraining::GetModules() {
return {
{"rom_structure", GetRomStructureKnowledge(nullptr), true},
{"hex_analysis", GetHexAnalysisKnowledge(), true},
{"map_editing", GetMapEditingKnowledge(), true},
{"tool_usage", GetToolUsageExamples(), true},
};
}
std::string AgentPretraining::GetRomStructureKnowledge(Rom* rom) {
return R"(
# ALTTP ROM Structure Deep Dive
## Memory Map
- 0x00000-0x07FFF: Header + Low ROM
- 0x08000-0x0FFFF: Character data
- 0x10000-0x1FFFF: Overworld maps
- 0x1C800-0x1D7FF: Overworld tile16 data
- 0x20000-0x2FFFF: Dungeon rooms (296 rooms)
- 0xDE6C8-0xDEDC7: Overworld palettes
- 0xDD308-0xDD3C7: Dungeon palettes
## Overworld Structure
- 64 maps (0x00-0x3F): Light world + Dark world
- Each map: 32x32 tiles (1024 tile16s)
- Tile16 address = 0x1C800 + (tile_id * 8 bytes)
- Map data stored compressed
## Dungeon Structure
- 296 rooms total (0x00-0x127)
- Room header: 14 bytes
- Sprite data: 3 bytes per sprite, up to 16 sprites
- Layer 1/2 separate data
- Object encoding: Layer/Size/X/Y format
## Palette System
- 8 groups, 8 palettes per group
- 16 colors per palette (SNES 555 format)
- $0000-$7FFF range (5 bits per R/G/B channel)
- Conversion: RGB8 = (SNES_val & 0x1F) << 3
## Critical Addresses
- Sprite sheets: 0x80000+
- Entrance table: 0x02C000
- Item table: 0x0DC800
- Text data: 0x0E0000+
)";
}
std::string AgentPretraining::GetHexAnalysisKnowledge() {
return R"(
# Hex Data Analysis Patterns
## Pattern Recognition
### Sprite Data (3-byte pattern)
- Byte 0: Sprite ID (0x00-0xFF)
- Byte 1: X position in room
- Byte 2: Y position in room
- Example: "09 48 56" = Sprite 0x09 at (72, 86)
### Tile16 Structure (8 bytes)
- Bytes 0-1: Top-left tile8
- Bytes 2-3: Top-right tile8
- Bytes 4-5: Bottom-left tile8
- Bytes 6-7: Bottom-right tile8
- Format: tile8_id (lower byte) + properties (upper byte)
### Pointers (Little Endian)
- 2-byte pointer: Low byte first, high byte second
- Example: "00 1C" = 0x1C00 (address 0x001C00)
- SNES addressing: Add bank byte for 24-bit
## Search Strategies
### Finding Unused Space
- Pattern: "FF FF FF FF" (often unused)
- Pattern: "00 00 00 00" (zeroed regions)
### Finding Sprite Definitions
- Search for known sprite IDs
- Look for X/Y coordinate patterns (0x00-0x1F typical range)
### Finding Compressed Data
- Look for compression headers (0xE0-0xFF often signify compression)
- Data density changes (sparse vs dense byte values)
)";
}
std::string AgentPretraining::GetMapEditingKnowledge() {
return R"(
# Map Editing Workflow with Test Harness
## Tile Placement Flow
1. Parse natural language: "Place water tile at (5, 7)"
2. Tool chain:
a. overworld-find-tile to get water tile ID
b. Calculate screen coordinates from game coords
c. Generate GUI action: Click(overworld_canvas, x, y)
d. Generate GUI action: SelectTile(tile_id)
e. Generate GUI action: Click(target_x, target_y)
## Coordinate Systems
- Game coords: Tile-based (0-31 for 32x32 map)
- Screen coords: Pixel-based, depends on zoom/scroll
- Conversion: screen_x = game_x * tile_size * zoom + canvas_offset_x
## GUI Automation Best Practices
- Always wait for UI updates (Wait(100ms) between actions)
- Assert widget states before clicking
- Screenshot before/after for verification
- Use widget discovery to find exact IDs
## Proposal System Integration
- Read-only tools: Execute directly
- Write operations: Submit as proposal first
- Wait for approval before proceeding
- Show user what will change (diff view)
)";
}
std::string AgentPretraining::GetToolUsageExamples() {
return R"(
# Tool Usage Examples & Chaining
## Example 1: Find and Analyze Sprites
User: "What enemies are in Hyrule Castle?"
Tool chain:
1. resource-search --type=dungeon --query=hyrule
2. dungeon-list-sprites --dungeon=hyrule_castle
3. resource-list --type=sprite (to get sprite names)
Result: "Hyrule Castle (dungeon 0) has 3 guards (sprite 0x41), 2 knights..."
## Example 2: Palette Investigation
User: "What colors are used for grass?"
Tool chain:
1. overworld-find-tile --tile_id=0x20 (grass tile)
2. palette-get-colors --group=0 --palette=0
3. palette-analyze --type=palette --id=0/0
Result: "Grass uses palette 0/0 with colors: light green #98FB98..."
## Example 3: Hex Pattern Search
User: "Find all chests in the ROM"
Tool chain:
1. hex-search --pattern="20 ?? ??" (chest object code 0x20)
2. For each match, hex-read context bytes
3. Parse room numbers from addresses
Result: "Found 50 chests across 30 rooms..."
## Example 4: Complex Edit Planning
User: "Add a new enemy to room 5"
Steps:
1. dungeon-describe-room --room_id=5 (check current sprites)
2. resource-list --type=sprite (available enemies)
3. todo-create --title="Add sprite to room 5" --steps="find_sprite,get_coords,write_data"
4. hex-read --address=<room_5_sprite_addr> (check free slots)
5. Submit proposal with hex-write
)";
}
std::string AgentPretraining::GeneratePretrainingPrompt(Rom* rom) {
std::string prompt = "# Agent Pre-Training Session\n\n";
prompt += "You are being initialized with deep knowledge about this ROM.\n\n";
if (rom && rom->is_loaded()) {
prompt += absl::StrFormat("## Current ROM: %s\n", rom->title());
prompt += absl::StrFormat("Size: %zu bytes\n", rom->size());
prompt += absl::StrFormat("Type: %s\n\n", rom->is_expanded() ? "Expanded" : "Vanilla");
}
for (const auto& module : GetModules()) {
prompt += absl::StrFormat("## Module: %s\n", module.name);
prompt += module.content;
prompt += "\n---\n\n";
}
prompt += R"(
## Your Capabilities After Training
You now understand:
ROM memory layout and addressing
How to chain tools for complex analysis
Hex pattern recognition and data structures
GUI automation for map editing
Proposal system for safe ROM modification
**Test your knowledge**: When I ask about sprites, dungeons, or tiles,
use multiple tools in one response to give comprehensive answers.
)";
return prompt;
}
} // namespace agent
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,63 @@
#ifndef YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_
#define YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
namespace yaze {
class Rom;
namespace cli {
namespace agent {
/**
* @brief Pre-training system for AI agents
*
* Provides structured knowledge injection before interactive use.
* Teaches agent about ROM structure, common patterns, and tool usage.
*/
class AgentPretraining {
public:
struct KnowledgeModule {
std::string name;
std::string content;
bool required;
};
/**
* @brief Load all pre-training modules
*/
static std::vector<KnowledgeModule> GetModules();
/**
* @brief Get ROM structure explanation
*/
static std::string GetRomStructureKnowledge(Rom* rom);
/**
* @brief Get hex data analysis patterns
*/
static std::string GetHexAnalysisKnowledge();
/**
* @brief Get map editing workflow
*/
static std::string GetMapEditingKnowledge();
/**
* @brief Get tool usage examples
*/
static std::string GetToolUsageExamples();
/**
* @brief Generate pre-training prompt for agent
*/
static std::string GeneratePretrainingPrompt(Rom* rom);
};
} // namespace agent
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_