backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
@@ -6,10 +6,10 @@ doubt, link back to the relevant guide instead of duplicating long explanations.
|
||||
|
||||
## 1. Launching Common Editors
|
||||
```bash
|
||||
# Open YAZE directly in the Dungeon editor with room cards preset
|
||||
# Open YAZE directly in the Dungeon editor with room panels preset
|
||||
./build/bin/yaze --rom_file=zelda3.sfc \
|
||||
--editor=Dungeon \
|
||||
--cards="Rooms List,Room Graphics,Object Editor"
|
||||
--open_panels="Rooms List,Room Graphics,Object Editor"
|
||||
|
||||
# Jump to an Overworld map from the CLI/TUI companion
|
||||
./build/bin/z3ed overworld describe-map --map 0x80 --rom zelda3.sfc
|
||||
@@ -34,9 +34,9 @@ cmake --preset mac-dbg
|
||||
cmake --build --preset mac-dbg --target yaze yaze_test
|
||||
./build/bin/yaze_test --unit
|
||||
|
||||
# AI-focused build in a dedicated directory (recommended for assistants)
|
||||
cmake --preset mac-ai -B build_ai
|
||||
cmake --build build_ai --target yaze z3ed
|
||||
# AI-focused build
|
||||
cmake --preset mac-ai
|
||||
cmake --build --preset mac-ai --target yaze z3ed
|
||||
```
|
||||
|
||||
## 4. Quick Verification
|
||||
|
||||
182
docs/public/examples/patch_export_usage.cc
Normal file
182
docs/public/examples/patch_export_usage.cc
Normal file
@@ -0,0 +1,182 @@
|
||||
// Example: How to use WasmPatchExport in the yaze editor
|
||||
// This code would typically be integrated into the ROM file manager or editor menu
|
||||
|
||||
#include "app/platform/wasm/wasm_patch_export.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
// Example function that could be added to RomFileManager or MenuOrchestrator
|
||||
void ShowPatchExportDialog(Rom* rom) {
|
||||
static bool show_export_dialog = false;
|
||||
static int patch_format = 0; // 0 = BPS, 1 = IPS
|
||||
static char filename[256] = "my_hack";
|
||||
|
||||
// Menu item to trigger export
|
||||
if (ImGui::MenuItem("Export Patch...", nullptr, nullptr, rom->is_loaded())) {
|
||||
show_export_dialog = true;
|
||||
}
|
||||
|
||||
// Export dialog window
|
||||
if (show_export_dialog) {
|
||||
ImGui::OpenPopup("Export Patch");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("Export Patch", &show_export_dialog)) {
|
||||
// Get the original ROM data (assuming rom stores both original and modified)
|
||||
const auto& original_data = rom->original_data(); // Would need to add this
|
||||
const auto& modified_data = rom->data();
|
||||
|
||||
// Show patch preview information
|
||||
auto patch_info = platform::WasmPatchExport::GetPatchPreview(
|
||||
original_data, modified_data);
|
||||
|
||||
ImGui::Text("Patch Summary:");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Total changed bytes: %zu", patch_info.changed_bytes);
|
||||
ImGui::Text("Number of regions: %zu", patch_info.num_regions);
|
||||
|
||||
if (patch_info.changed_bytes == 0) {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "No changes detected!");
|
||||
}
|
||||
|
||||
// Show changed regions (limit to first 10 for UI)
|
||||
if (!patch_info.changed_regions.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Changed Regions:");
|
||||
int region_count = 0;
|
||||
for (const auto& region : patch_info.changed_regions) {
|
||||
if (region_count >= 10) {
|
||||
ImGui::Text("... and %zu more regions",
|
||||
patch_info.changed_regions.size() - 10);
|
||||
break;
|
||||
}
|
||||
ImGui::Text(" Offset: 0x%06X, Size: %zu bytes",
|
||||
static_cast<unsigned>(region.first), region.second);
|
||||
region_count++;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Format selection
|
||||
ImGui::Text("Patch Format:");
|
||||
ImGui::RadioButton("BPS (Beat)", &patch_format, 0);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("IPS", &patch_format, 1);
|
||||
|
||||
// Filename input
|
||||
ImGui::Text("Filename:");
|
||||
ImGui::InputText("##filename", filename, sizeof(filename));
|
||||
|
||||
// Export buttons
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Export", ImVec2(120, 0))) {
|
||||
if (patch_info.changed_bytes > 0) {
|
||||
absl::Status status;
|
||||
std::string full_filename = std::string(filename);
|
||||
|
||||
if (patch_format == 0) {
|
||||
// BPS format
|
||||
if (full_filename.find(".bps") == std::string::npos) {
|
||||
full_filename += ".bps";
|
||||
}
|
||||
status = platform::WasmPatchExport::ExportBPS(
|
||||
original_data, modified_data, full_filename);
|
||||
} else {
|
||||
// IPS format
|
||||
if (full_filename.find(".ips") == std::string::npos) {
|
||||
full_filename += ".ips";
|
||||
}
|
||||
status = platform::WasmPatchExport::ExportIPS(
|
||||
original_data, modified_data, full_filename);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
show_export_dialog = false;
|
||||
// Could show success toast here
|
||||
} else {
|
||||
// Show error message
|
||||
ImGui::OpenPopup("Export Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
show_export_dialog = false;
|
||||
}
|
||||
|
||||
// Error popup
|
||||
if (ImGui::BeginPopupModal("Export Error")) {
|
||||
ImGui::Text("Failed to export patch!");
|
||||
if (ImGui::Button("OK", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative: Quick export functions for toolbar/menu
|
||||
void QuickExportBPS(Rom* rom) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (!rom || !rom->is_loaded()) return;
|
||||
|
||||
const auto& original = rom->original_data(); // Would need to add this
|
||||
const auto& modified = rom->data();
|
||||
|
||||
// Generate default filename based on ROM name
|
||||
std::string filename = rom->filename();
|
||||
size_t dot_pos = filename.find_last_of('.');
|
||||
if (dot_pos != std::string::npos) {
|
||||
filename = filename.substr(0, dot_pos);
|
||||
}
|
||||
filename += ".bps";
|
||||
|
||||
auto status = platform::WasmPatchExport::ExportBPS(
|
||||
original, modified, filename);
|
||||
|
||||
if (!status.ok()) {
|
||||
// Show error toast or log
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void QuickExportIPS(Rom* rom) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (!rom || !rom->is_loaded()) return;
|
||||
|
||||
const auto& original = rom->original_data();
|
||||
const auto& modified = rom->data();
|
||||
|
||||
// Check IPS size limit
|
||||
if (modified.size() > 0xFFFFFF) {
|
||||
// Show error: ROM too large for IPS format
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filename = rom->filename();
|
||||
size_t dot_pos = filename.find_last_of('.');
|
||||
if (dot_pos != std::string::npos) {
|
||||
filename = filename.substr(0, dot_pos);
|
||||
}
|
||||
filename += ".ips";
|
||||
|
||||
auto status = platform::WasmPatchExport::ExportIPS(
|
||||
original, modified, filename);
|
||||
|
||||
if (!status.ok()) {
|
||||
// Show error toast or log
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
241
docs/public/examples/wasm_message_queue_usage.cc
Normal file
241
docs/public/examples/wasm_message_queue_usage.cc
Normal file
@@ -0,0 +1,241 @@
|
||||
// Example: Using WasmMessageQueue with WasmCollaboration
|
||||
// This example shows how the collaboration system can use the message queue
|
||||
// for offline support and automatic replay when reconnecting.
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
||||
#include "app/platform/wasm/wasm_collaboration.h"
|
||||
#include "app/platform/wasm/wasm_message_queue.h"
|
||||
#include <emscripten.h>
|
||||
#include <memory>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace platform {
|
||||
|
||||
// Example integration class that combines collaboration with offline queue
|
||||
class CollaborationWithOfflineSupport {
|
||||
public:
|
||||
CollaborationWithOfflineSupport()
|
||||
: collaboration_(std::make_unique<WasmCollaboration>()),
|
||||
message_queue_(std::make_unique<WasmMessageQueue>()) {
|
||||
|
||||
// Configure message queue
|
||||
message_queue_->SetAutoPersist(true);
|
||||
message_queue_->SetMaxQueueSize(500);
|
||||
message_queue_->SetMessageExpiry(86400.0); // 24 hours
|
||||
|
||||
// Set up callbacks
|
||||
SetupCallbacks();
|
||||
|
||||
// Load any previously queued messages
|
||||
auto status = message_queue_->LoadFromStorage();
|
||||
if (!status.ok()) {
|
||||
emscripten_log(EM_LOG_WARN, "Failed to load offline queue: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Send a change, queuing if offline
|
||||
void SendChange(uint32_t offset, const std::vector<uint8_t>& old_data,
|
||||
const std::vector<uint8_t>& new_data) {
|
||||
if (collaboration_->IsConnected()) {
|
||||
// Try to send directly
|
||||
auto status = collaboration_->BroadcastChange(offset, old_data, new_data);
|
||||
|
||||
if (!status.ok()) {
|
||||
// Failed to send, queue for later
|
||||
QueueChange(offset, old_data, new_data);
|
||||
}
|
||||
} else {
|
||||
// Not connected, queue for later
|
||||
QueueChange(offset, old_data, new_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Send cursor position, queuing if offline
|
||||
void SendCursorPosition(const std::string& editor_type, int x, int y, int map_id) {
|
||||
if (collaboration_->IsConnected()) {
|
||||
auto status = collaboration_->SendCursorPosition(editor_type, x, y, map_id);
|
||||
|
||||
if (!status.ok()) {
|
||||
QueueCursorPosition(editor_type, x, y, map_id);
|
||||
}
|
||||
} else {
|
||||
QueueCursorPosition(editor_type, x, y, map_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Called when connection is established
|
||||
void OnConnectionEstablished() {
|
||||
emscripten_log(EM_LOG_INFO, "Connection established, replaying queued messages...");
|
||||
|
||||
// Create sender function that uses the collaboration instance
|
||||
auto sender = [this](const std::string& message_type, const std::string& payload) -> absl::Status {
|
||||
// Parse the payload and send via collaboration
|
||||
try {
|
||||
nlohmann::json data = nlohmann::json::parse(payload);
|
||||
|
||||
if (message_type == "change") {
|
||||
uint32_t offset = data["offset"];
|
||||
std::vector<uint8_t> old_data = data["old_data"];
|
||||
std::vector<uint8_t> new_data = data["new_data"];
|
||||
return collaboration_->BroadcastChange(offset, old_data, new_data);
|
||||
} else if (message_type == "cursor") {
|
||||
std::string editor_type = data["editor_type"];
|
||||
int x = data["x"];
|
||||
int y = data["y"];
|
||||
int map_id = data.value("map_id", -1);
|
||||
return collaboration_->SendCursorPosition(editor_type, x, y, map_id);
|
||||
}
|
||||
|
||||
return absl::InvalidArgumentError("Unknown message type: " + message_type);
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InvalidArgumentError("Failed to parse payload: " + std::string(e.what()));
|
||||
}
|
||||
};
|
||||
|
||||
// Replay all queued messages
|
||||
message_queue_->ReplayAll(sender, 3); // Max 3 retries per message
|
||||
}
|
||||
|
||||
// Get queue status for UI display
|
||||
WasmMessageQueue::QueueStatus GetQueueStatus() const {
|
||||
return message_queue_->GetStatus();
|
||||
}
|
||||
|
||||
// Clear all queued messages
|
||||
void ClearQueue() {
|
||||
message_queue_->Clear();
|
||||
}
|
||||
|
||||
// Prune old messages
|
||||
void PruneOldMessages() {
|
||||
int removed = message_queue_->PruneExpiredMessages();
|
||||
if (removed > 0) {
|
||||
emscripten_log(EM_LOG_INFO, "Pruned %d expired messages", removed);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void SetupCallbacks() {
|
||||
// Set up replay complete callback
|
||||
message_queue_->SetOnReplayComplete([](int replayed, int failed) {
|
||||
emscripten_log(EM_LOG_INFO, "Replay complete: %d sent, %d failed", replayed, failed);
|
||||
|
||||
// Show notification to user
|
||||
EM_ASM({
|
||||
if (window.showNotification) {
|
||||
const message = `Synced ${$0} changes` + ($1 > 0 ? `, ${$1} failed` : '');
|
||||
window.showNotification(message, $1 > 0 ? 'warning' : 'success');
|
||||
}
|
||||
}, replayed, failed);
|
||||
});
|
||||
|
||||
// Set up status change callback
|
||||
message_queue_->SetOnStatusChange([](const WasmMessageQueue::QueueStatus& status) {
|
||||
// Update UI with queue status
|
||||
EM_ASM({
|
||||
if (window.updateQueueStatus) {
|
||||
window.updateQueueStatus({
|
||||
pendingCount: $0,
|
||||
failedCount: $1,
|
||||
totalBytes: $2,
|
||||
oldestMessageAge: $3,
|
||||
isPersisted: $4
|
||||
});
|
||||
}
|
||||
}, status.pending_count, status.failed_count, status.total_bytes,
|
||||
status.oldest_message_age, status.is_persisted);
|
||||
});
|
||||
|
||||
// Set up collaboration status callback
|
||||
collaboration_->SetStatusCallback([this](bool connected, const std::string& message) {
|
||||
if (connected) {
|
||||
// Connection established, replay queued messages
|
||||
OnConnectionEstablished();
|
||||
} else {
|
||||
// Connection lost
|
||||
emscripten_log(EM_LOG_INFO, "Connection lost: %s", message.c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void QueueChange(uint32_t offset, const std::vector<uint8_t>& old_data,
|
||||
const std::vector<uint8_t>& new_data) {
|
||||
nlohmann::json payload;
|
||||
payload["offset"] = offset;
|
||||
payload["old_data"] = old_data;
|
||||
payload["new_data"] = new_data;
|
||||
payload["timestamp"] = emscripten_get_now() / 1000.0;
|
||||
|
||||
std::string msg_id = message_queue_->Enqueue("change", payload.dump());
|
||||
emscripten_log(EM_LOG_DEBUG, "Queued change message: %s", msg_id.c_str());
|
||||
}
|
||||
|
||||
void QueueCursorPosition(const std::string& editor_type, int x, int y, int map_id) {
|
||||
nlohmann::json payload;
|
||||
payload["editor_type"] = editor_type;
|
||||
payload["x"] = x;
|
||||
payload["y"] = y;
|
||||
if (map_id >= 0) {
|
||||
payload["map_id"] = map_id;
|
||||
}
|
||||
payload["timestamp"] = emscripten_get_now() / 1000.0;
|
||||
|
||||
std::string msg_id = message_queue_->Enqueue("cursor", payload.dump());
|
||||
emscripten_log(EM_LOG_DEBUG, "Queued cursor message: %s", msg_id.c_str());
|
||||
}
|
||||
|
||||
std::unique_ptr<WasmCollaboration> collaboration_;
|
||||
std::unique_ptr<WasmMessageQueue> message_queue_;
|
||||
};
|
||||
|
||||
// JavaScript bindings for the enhanced collaboration
|
||||
extern "C" {
|
||||
|
||||
// Create collaboration instance with offline support
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void* create_collaboration_with_offline() {
|
||||
return new CollaborationWithOfflineSupport();
|
||||
}
|
||||
|
||||
// Send a change (with automatic queuing if offline)
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void send_change_with_queue(void* instance, uint32_t offset,
|
||||
uint8_t* old_data, int old_size,
|
||||
uint8_t* new_data, int new_size) {
|
||||
auto* collab = static_cast<CollaborationWithOfflineSupport*>(instance);
|
||||
std::vector<uint8_t> old_vec(old_data, old_data + old_size);
|
||||
std::vector<uint8_t> new_vec(new_data, new_data + new_size);
|
||||
collab->SendChange(offset, old_vec, new_vec);
|
||||
}
|
||||
|
||||
// Get queue status
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int get_pending_message_count(void* instance) {
|
||||
auto* collab = static_cast<CollaborationWithOfflineSupport*>(instance);
|
||||
return collab->GetQueueStatus().pending_count;
|
||||
}
|
||||
|
||||
// Clear offline queue
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void clear_offline_queue(void* instance) {
|
||||
auto* collab = static_cast<CollaborationWithOfflineSupport*>(instance);
|
||||
collab->ClearQueue();
|
||||
}
|
||||
|
||||
// Prune old messages
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void prune_old_messages(void* instance) {
|
||||
auto* collab = static_cast<CollaborationWithOfflineSupport*>(instance);
|
||||
collab->PruneOldMessages();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
} // namespace platform
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // __EMSCRIPTEN__
|
||||
255
docs/public/examples/web-terminal-integration.md
Normal file
255
docs/public/examples/web-terminal-integration.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Web Terminal Integration for Z3ed
|
||||
|
||||
This document describes how to integrate the z3ed terminal functionality into the WASM web build.
|
||||
|
||||
## Overview
|
||||
|
||||
The `wasm_terminal_bridge.cc` file provides C++ functions that can be called from JavaScript to enable z3ed command processing in the browser. This allows users to interact with ROM data and use AI-powered features directly in the web interface.
|
||||
|
||||
## Exported Functions
|
||||
|
||||
### Core Functions
|
||||
|
||||
```javascript
|
||||
// Process a z3ed command
|
||||
const char* Z3edProcessCommand(const char* command);
|
||||
|
||||
// Get command completions for autocomplete
|
||||
const char* Z3edGetCompletions(const char* partial);
|
||||
|
||||
// Set API key for AI services (Gemini)
|
||||
void Z3edSetApiKey(const char* api_key);
|
||||
|
||||
// Check if terminal bridge is ready
|
||||
int Z3edIsReady();
|
||||
|
||||
// Load ROM data from ArrayBuffer
|
||||
int Z3edLoadRomData(const uint8_t* data, size_t size);
|
||||
|
||||
// Get current ROM information as JSON
|
||||
const char* Z3edGetRomInfo();
|
||||
|
||||
// Execute resource queries
|
||||
const char* Z3edQueryResource(const char* query);
|
||||
```
|
||||
|
||||
## JavaScript Integration Example
|
||||
|
||||
```javascript
|
||||
// Initialize the terminal when module is ready
|
||||
Module.onRuntimeInitialized = function() {
|
||||
// Check if terminal is ready
|
||||
if (Module.ccall('Z3edIsReady', 'number', [], [])) {
|
||||
console.log('Z3ed terminal ready');
|
||||
}
|
||||
|
||||
// Set API key for AI features
|
||||
const apiKey = localStorage.getItem('gemini_api_key');
|
||||
if (apiKey) {
|
||||
Module.ccall('Z3edSetApiKey', null, ['string'], [apiKey]);
|
||||
}
|
||||
|
||||
// Create terminal interface
|
||||
const terminal = new Terminal({
|
||||
prompt: 'z3ed> ',
|
||||
onCommand: (cmd) => {
|
||||
const result = Module.ccall('Z3edProcessCommand', 'string', ['string'], [cmd]);
|
||||
terminal.print(result);
|
||||
},
|
||||
onTab: (partial) => {
|
||||
const completions = Module.ccall('Z3edGetCompletions', 'string', ['string'], [partial]);
|
||||
return JSON.parse(completions);
|
||||
}
|
||||
});
|
||||
|
||||
// Expose terminal globally
|
||||
window.z3edTerminal = terminal;
|
||||
};
|
||||
|
||||
// Load ROM file
|
||||
async function loadRomFile(file) {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const data = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Allocate memory in WASM heap
|
||||
const ptr = Module._malloc(data.length);
|
||||
Module.HEAPU8.set(data, ptr);
|
||||
|
||||
// Load ROM
|
||||
const success = Module.ccall('Z3edLoadRomData', 'number',
|
||||
['number', 'number'], [ptr, data.length]);
|
||||
|
||||
// Free memory
|
||||
Module._free(ptr);
|
||||
|
||||
if (success) {
|
||||
// Get ROM info
|
||||
const info = Module.ccall('Z3edGetRomInfo', 'string', [], []);
|
||||
console.log('ROM loaded:', JSON.parse(info));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Terminal UI Component
|
||||
|
||||
```javascript
|
||||
class Z3edTerminal {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.history = [];
|
||||
this.historyIndex = 0;
|
||||
this.setupUI();
|
||||
}
|
||||
|
||||
setupUI() {
|
||||
this.container.innerHTML = `
|
||||
<div class="terminal-output"></div>
|
||||
<div class="terminal-input">
|
||||
<span class="prompt">z3ed> </span>
|
||||
<input type="text" class="command-input" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.output = this.container.querySelector('.terminal-output');
|
||||
this.input = this.container.querySelector('.command-input');
|
||||
|
||||
this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
|
||||
}
|
||||
|
||||
handleKeydown(e) {
|
||||
if (e.key === 'Enter') {
|
||||
this.executeCommand(this.input.value);
|
||||
this.history.push(this.input.value);
|
||||
this.historyIndex = this.history.length;
|
||||
this.input.value = '';
|
||||
} else if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
this.handleAutocomplete();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
this.navigateHistory(-1);
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
this.navigateHistory(1);
|
||||
}
|
||||
}
|
||||
|
||||
executeCommand(cmd) {
|
||||
this.print(`z3ed> ${cmd}`, 'command');
|
||||
|
||||
if (!Module.ccall) {
|
||||
this.printError('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = Module.ccall('Z3edProcessCommand', 'string', ['string'], [cmd]);
|
||||
this.print(result);
|
||||
} catch (error) {
|
||||
this.printError(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleAutocomplete() {
|
||||
const partial = this.input.value;
|
||||
const completions = Module.ccall('Z3edGetCompletions', 'string', ['string'], [partial]);
|
||||
const options = JSON.parse(completions);
|
||||
|
||||
if (options.length === 1) {
|
||||
this.input.value = options[0];
|
||||
} else if (options.length > 1) {
|
||||
this.print(`Available commands: ${options.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
navigateHistory(direction) {
|
||||
this.historyIndex = Math.max(0, Math.min(this.history.length, this.historyIndex + direction));
|
||||
this.input.value = this.history[this.historyIndex] || '';
|
||||
}
|
||||
|
||||
print(text, className = 'output') {
|
||||
const line = document.createElement('div');
|
||||
line.className = className;
|
||||
line.textContent = text;
|
||||
this.output.appendChild(line);
|
||||
this.output.scrollTop = this.output.scrollHeight;
|
||||
}
|
||||
|
||||
printError(text) {
|
||||
this.print(text, 'error');
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.output.innerHTML = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
The WASM build includes a subset of z3ed commands that don't require native dependencies:
|
||||
|
||||
### Basic Commands
|
||||
- `help` - Show available commands
|
||||
- `help <category>` - Show commands in a category
|
||||
- `clear` - Clear terminal output
|
||||
- `version` - Show version information
|
||||
|
||||
### ROM Commands
|
||||
- `rom load <file>` - Load ROM from file
|
||||
- `rom info` - Display ROM information
|
||||
- `rom validate` - Validate ROM structure
|
||||
|
||||
### Resource Queries
|
||||
- `resource query dungeon.rooms` - List dungeon rooms
|
||||
- `resource query overworld.maps` - List overworld maps
|
||||
- `resource query graphics.sheets` - List graphics sheets
|
||||
- `resource query palettes` - List palettes
|
||||
|
||||
### AI Commands (requires API key)
|
||||
- `ai <prompt>` - Generate AI response
|
||||
- `ai analyze <resource>` - Analyze ROM resource
|
||||
- `ai suggest <context>` - Get suggestions
|
||||
|
||||
### Graphics Commands
|
||||
- `gfx list` - List graphics resources
|
||||
- `gfx export <id>` - Export graphics (returns base64)
|
||||
|
||||
## Build Configuration
|
||||
|
||||
The WASM terminal bridge is automatically included when building with Emscripten:
|
||||
|
||||
```bash
|
||||
# Configure for WASM with AI support
|
||||
cmake --preset wasm-ai
|
||||
|
||||
# Build
|
||||
cmake --build build --target yaze
|
||||
|
||||
# The resulting files will be:
|
||||
# - yaze.js (JavaScript loader)
|
||||
# - yaze.wasm (WebAssembly module)
|
||||
# - yaze.html (Example HTML page)
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **API Keys**: Store API keys in sessionStorage or localStorage, never hardcode them
|
||||
2. **ROM Data**: ROM data stays in browser memory, never sent to servers
|
||||
3. **CORS**: AI API requests go through browser fetch, respecting CORS policies
|
||||
4. **Sandboxing**: WASM runs in browser sandbox with limited filesystem access
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Module not loading
|
||||
- Ensure WASM files are served with correct MIME type: `application/wasm`
|
||||
- Check browser console for CORS errors
|
||||
- Verify SharedArrayBuffer support if using threads
|
||||
|
||||
### Commands not working
|
||||
- Check if ROM is loaded: `Z3edGetRomInfo()`
|
||||
- Verify terminal is ready: `Z3edIsReady()`
|
||||
- Check browser console for error messages
|
||||
|
||||
### AI features not working
|
||||
- Ensure API key is set: `Z3edSetApiKey()`
|
||||
- Check network tab for API request failures
|
||||
- Verify Gemini API quota and limits
|
||||
Reference in New Issue
Block a user