backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
28
docs/public/README.md
Normal file
28
docs/public/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Public Docs Guide (Doxygen-Friendly)
|
||||
|
||||
Purpose: keep `docs/public` readable, accurate, and exportable via Doxygen.
|
||||
|
||||
## Authoring checklist
|
||||
- One H1 per page; keep openings short (2–3 lines) and outcome-focused.
|
||||
- Lead with “who/what/why” and a minimal quick-start or TL;DR when relevant.
|
||||
- Prefer short sections and bullet lists over walls of text; keep code blocks minimal and copy-pastable.
|
||||
- Use relative links within `docs/public`; point to `docs/internal` only when the public doc would otherwise be incomplete.
|
||||
- Label platform-specific steps explicitly (macOS/Linux/Windows) and validate against current `CMakePresets.json`.
|
||||
- Avoid agent-only workflow details; those belong in `docs/internal`.
|
||||
|
||||
## Doxygen structure
|
||||
- `docs/public/index.md` serves as the `@mainpage` with concise navigation.
|
||||
- Keep headings shallow (H1–H3) so Doxygen generates clean TOCs.
|
||||
- Include a short “New here?” or quick-start block on entry pages to aid scanning.
|
||||
|
||||
## Accuracy cadence
|
||||
- Re-verify build commands and presets after CMake or CI changes.
|
||||
- Review public docs at least once per release cycle; archive or rewrite stale guidance instead of adding new pages.
|
||||
|
||||
## Naming & formatting
|
||||
- Use kebab-case filenames; reserve ALL-CAPS for anchors like README/CONTRIBUTING/AGENTS/GEMINI/CLAUDE.
|
||||
- Prefer present tense, active voice, and consistent terminology (e.g., “yaze”, “z3ed”, “preset”).
|
||||
|
||||
## When to add vs. link
|
||||
- Add a new public page only if it benefits external readers; otherwise, link to the relevant internal doc.
|
||||
- For experimental or rapidly changing features, keep the source in `docs/internal` and expose a short, stable summary here.
|
||||
@@ -1,89 +1,124 @@
|
||||
# Build Instructions
|
||||
# Build from Source
|
||||
|
||||
yaze uses a modern CMake build system with presets for easy configuration. This guide explains the
|
||||
environment checks, dependencies, and platform-specific considerations. For concise per-platform
|
||||
commands, always start with the [Build & Test Quick Reference](quick-reference.md).
|
||||
YAZE uses a modern CMake build system with presets for easy configuration. This guide covers environment setup, dependencies, and platform-specific considerations.
|
||||
|
||||
> **Quick Start:** For concise build commands, see the [Build and Test Quick Reference](quick-reference.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Environment Verification
|
||||
|
||||
**Before your first build**, run the verification script to ensure your environment is configured correctly.
|
||||
Before your first build, run the verification script to ensure your environment is configured correctly.
|
||||
|
||||
### Windows (PowerShell)
|
||||
|
||||
```powershell
|
||||
.\scripts\verify-build-environment.ps1
|
||||
|
||||
# With automatic fixes
|
||||
.\scripts\verify-build-environment.ps1 -FixIssues
|
||||
```
|
||||
> Tip: After verification, run `.\scripts\setup-vcpkg-windows.ps1` to bootstrap vcpkg, ensure `clang-cl`/Ninja are installed, and cache the `x64-windows` triplet.
|
||||
|
||||
### macOS & Linux (Bash)
|
||||
> **Tip:** After verification, run `.\scripts\setup-vcpkg-windows.ps1` to bootstrap vcpkg, install clang-cl/Ninja, and cache the x64-windows triplet.
|
||||
|
||||
### macOS and Linux
|
||||
|
||||
```bash
|
||||
./scripts/verify-build-environment.sh
|
||||
|
||||
# With automatic fixes
|
||||
./scripts\verify-build-environment.sh --fix
|
||||
./scripts/verify-build-environment.sh --fix
|
||||
```
|
||||
|
||||
The script checks for required tools like CMake, a C++23 compiler, and platform-specific dependencies.
|
||||
The script checks for CMake, a C++23 compiler, and platform-specific dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 2. Using Presets
|
||||
|
||||
- Pick the preset that matches your platform/workflow (debug: `mac-dbg` / `lin-dbg` / `win-dbg`,
|
||||
AI-enabled: `mac-ai` / `win-ai`, release: `*-rel`, etc.).
|
||||
- Configure with `cmake --preset <name>` and build with `cmake --build --preset <name> [--target …]`.
|
||||
- Add `-v` to a preset name (e.g., `mac-dbg-v`) to surface compiler warnings.
|
||||
- Need a full matrix? See the [CMake Presets Guide](presets.md) for every preset and the quick
|
||||
reference for ready-to-run command snippets.
|
||||
Select a preset that matches your platform and workflow:
|
||||
|
||||
## Feature Toggles & Windows Profiles
|
||||
| Workflow | Presets |
|
||||
|----------|---------|
|
||||
| Debug builds | `mac-dbg`, `lin-dbg`, `win-dbg` |
|
||||
| AI-enabled builds | `mac-ai`, `lin-ai`, `win-ai` |
|
||||
| Release builds | `mac-rel`, `lin-rel`, `win-rel` |
|
||||
| Development (ROM tests) | `mac-dev`, `lin-dev`, `win-dev` |
|
||||
|
||||
**Build Commands:**
|
||||
```bash
|
||||
cmake --preset <name> # Configure
|
||||
cmake --build --preset <name> --target yaze # Build
|
||||
```
|
||||
|
||||
Add `-v` suffix (e.g., `mac-dbg-v`) to enable verbose compiler warnings.
|
||||
|
||||
See the [CMake Presets Guide](presets.md) for the complete preset reference.
|
||||
|
||||
---
|
||||
|
||||
## Feature Toggles
|
||||
|
||||
### Windows Presets
|
||||
|
||||
| Preset | Purpose |
|
||||
| --- | --- |
|
||||
| `win-dbg`, `win-rel`, `ci-windows` | Core builds without agent UI, gRPC, or AI runtimes. Fastest option for MSVC/clang-cl. |
|
||||
| `win-ai`, `win-vs-ai` | Full agent stack for local development (UI panels + remote automation + AI runtime). |
|
||||
| `ci-windows-ai` | Nightly/weekly CI preset that exercises the entire automation stack on Windows. |
|
||||
|--------|---------|
|
||||
| `win-dbg`, `win-rel` | Core builds without agent UI or AI. Fastest option. |
|
||||
| `win-ai`, `win-vs-ai` | Full agent stack (UI + automation + AI runtime) |
|
||||
| `ci-windows-ai` | CI preset for the complete automation stack |
|
||||
|
||||
### Agent Feature Flags
|
||||
### CMake Feature Flags
|
||||
|
||||
| Option | Default | Effect |
|
||||
| --- | --- | --- |
|
||||
| `YAZE_BUILD_AGENT_UI` | `ON` when `YAZE_BUILD_GUI=ON` | Builds the ImGui widgets used by the chat/agent panels. |
|
||||
| `YAZE_ENABLE_REMOTE_AUTOMATION` | `ON` for `*-ai` presets | Adds gRPC/protobuf services plus GUI automation clients. |
|
||||
| `YAZE_ENABLE_AI_RUNTIME` | `ON` for `*-ai` presets | Enables Gemini/Ollama transports, proposal planning, and advanced routing logic. |
|
||||
| `YAZE_ENABLE_AGENT_CLI` | `ON` when `YAZE_BUILD_CLI=ON` | Compiles the conversational agent stack consumed by `z3ed`. |
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `YAZE_BUILD_AGENT_UI` | ON with GUI | ImGui chat/agent panels |
|
||||
| `YAZE_ENABLE_REMOTE_AUTOMATION` | ON for `*-ai` | gRPC services and automation |
|
||||
| `YAZE_ENABLE_AI_RUNTIME` | ON for `*-ai` | Gemini/Ollama AI providers |
|
||||
| `YAZE_ENABLE_AGENT_CLI` | ON with CLI | z3ed agent commands |
|
||||
|
||||
Combine these switches to match your workflow: keep everything `OFF` for lightweight GUI hacking or turn them `ON` for automation-heavy work with sketchybar/yabai/skhd, tmux, or remote runners.
|
||||
Keep features `OFF` for lightweight GUI development, or enable them for automation workflows.
|
||||
|
||||
---
|
||||
|
||||
## 3. Dependencies
|
||||
|
||||
- **Required**: CMake 3.16+, C++23 Compiler (GCC 13+, Clang 16+, MSVC 2019+), Git.
|
||||
- **Bundled**: All other dependencies (SDL2, ImGui, Asar, nlohmann/json, cpp-httplib, GoogleTest, etc.) live under the `ext/` directory or are managed by CMake's `FetchContent`. No external package manager is required for a basic build.
|
||||
- **Optional**:
|
||||
- **gRPC**: For GUI test automation. Can be enabled with `-DYAZE_WITH_GRPC=ON`.
|
||||
- **vcpkg (Windows)**: Can be used for faster gRPC builds on Windows (optional).
|
||||
### Required
|
||||
|
||||
- **CMake** 3.16 or later
|
||||
- **C++23 Compiler**: GCC 13+, Clang 16+, or MSVC 2022 17.4+
|
||||
- **Git**
|
||||
|
||||
### Bundled (No Installation Required)
|
||||
|
||||
All core dependencies are included in the `ext/` directory or fetched automatically:
|
||||
- SDL2, ImGui, Asar, nlohmann/json, cpp-httplib, GoogleTest
|
||||
|
||||
### Optional
|
||||
|
||||
- **gRPC**: Required for GUI automation and AI features. Enable with `-DYAZE_ENABLE_GRPC=ON`.
|
||||
- **vcpkg (Windows)**: Speeds up gRPC builds on Windows.
|
||||
|
||||
---
|
||||
|
||||
## 4. Platform Setup
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
# Install Xcode Command Line Tools
|
||||
xcode-select --install
|
||||
|
||||
# Recommended: Install build tools via Homebrew
|
||||
# Install build tools via Homebrew
|
||||
brew install cmake pkg-config
|
||||
|
||||
# For sandboxed/offline builds: Install dependencies to avoid network fetch
|
||||
# Optional: For sandboxed/offline builds
|
||||
brew install yaml-cpp googletest
|
||||
```
|
||||
|
||||
**Note**: When building in sandboxed/offline environments (e.g., via Claude Code or restricted networks), install `yaml-cpp` and `googletest` via Homebrew to avoid GitHub fetch failures. The build system automatically detects Homebrew installations and uses them as fallback:
|
||||
- yaml-cpp: `/opt/homebrew/opt/yaml-cpp`, `/usr/local/opt/yaml-cpp`
|
||||
- googletest: `/opt/homebrew/opt/googletest`, `/usr/local/opt/googletest`
|
||||
> **Note:** In sandboxed environments, install yaml-cpp and googletest via Homebrew to avoid network fetch failures. The build system auto-detects Homebrew installations at `/opt/homebrew/opt/` (Apple Silicon) or `/usr/local/opt/` (Intel).
|
||||
|
||||
### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake ninja-build pkg-config \
|
||||
@@ -91,15 +126,21 @@ sudo apt-get install -y build-essential cmake ninja-build pkg-config \
|
||||
```
|
||||
|
||||
### Windows
|
||||
1. **Install Visual Studio 2022** with the “Desktop development with C++” workload (requires MSVC + MSBuild).
|
||||
2. **Install Ninja** (recommended): `choco install ninja` or enable the “CMake tools for Windows” optional component.
|
||||
3. Run the verifier: `.\scripts\verify-build-environment.ps1 -FixIssues` – this checks Visual Studio workloads, Ninja, clang-cl, Git settings, and vcpkg cache.
|
||||
4. Bootstrap vcpkg once: `.\scripts\setup-vcpkg-windows.ps1` (prefetches SDL2, yaml-cpp, etc.).
|
||||
5. Use the `win-*` presets (Ninja) or `win-vs-*` presets (Visual Studio generator) as needed. For AI/gRPC features, prefer `win-ai` / `win-vs-ai`.
|
||||
6. For quick validation, run the PowerShell helper:
|
||||
|
||||
1. **Install Visual Studio 2022** with "Desktop development with C++" workload
|
||||
2. **Install Ninja** (recommended): `choco install ninja` or via Visual Studio Installer
|
||||
3. **Run the verifier:**
|
||||
```powershell
|
||||
pwsh -File scripts/agents/windows-smoke-build.ps1 -Preset win-ai -Target z3ed
|
||||
.\scripts\verify-build-environment.ps1 -FixIssues
|
||||
```
|
||||
4. **Bootstrap vcpkg:**
|
||||
```powershell
|
||||
.\scripts\setup-vcpkg-windows.ps1
|
||||
```
|
||||
5. **Build:**
|
||||
- Use `win-*` presets with Ninja generator
|
||||
- Use `win-vs-*` presets for Visual Studio IDE
|
||||
- For AI features, use `win-ai` or `win-vs-ai`
|
||||
|
||||
## 5. Testing
|
||||
|
||||
|
||||
71
docs/public/build/install-options.md
Normal file
71
docs/public/build/install-options.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# YAZE Installation Options (Distribution Guide)
|
||||
|
||||
Status: Draft
|
||||
Audience: Users/distributors who want alternatives to direct GitHub release binaries.
|
||||
|
||||
## Overview
|
||||
YAZE is distributed primarily via GitHub release binaries. This guide summarizes current install paths and outlines packaging-friendly options per platform. Use the table to pick what is available today vs. what would require packaging work.
|
||||
|
||||
## Platform Matrix
|
||||
| Platform | Status | Recommended Path | Notes |
|
||||
|----------|--------|------------------|-------|
|
||||
| macOS (Intel/Apple) | Available | GitHub release tarball; custom Homebrew tap (see below) | Prefer Apple silicon builds; Intel works under Rosetta. |
|
||||
| Windows (x64) | Available | GitHub release zip; vcpkg-from-source (community) | No official winget/choco package yet. |
|
||||
| Linux (x86_64) | Available | GitHub release AppImage (if provided) or build from source | Test on Ubuntu/Debian/Fedora; Wayland users may need XWayland. |
|
||||
| Web (WASM) | Preview | Hosted demo or local `npm http-server` of `build-wasm` artifact | Requires modern browser; no install. |
|
||||
|
||||
## macOS
|
||||
### 1) Release binary (recommended)
|
||||
1. Download the macOS tarball from GitHub releases.
|
||||
2. `tar -xf yaze-<version>-macos.tar.gz && cd yaze-<version>-macos`
|
||||
3. Run `./yaze.app/Contents/MacOS/yaze` (GUI) or `./bin/z3ed` (CLI).
|
||||
|
||||
### 2) Homebrew (custom tap)
|
||||
- If you publish a tap: `brew tap <your/tap>` then `brew install yaze`.
|
||||
- Sample formula inputs:
|
||||
- URL: GitHub release tarball.
|
||||
- Dependencies: `cmake`, `ninja`, `pkg-config`, `sdl2`, `glew`, `glm`, `ftxui`, `abseil`, `protobuf`, `gtest`.
|
||||
- For development builds: `cmake --preset mac-dbg` then `cmake --build --preset mac-dbg`.
|
||||
|
||||
## Windows
|
||||
### 1) Release zip (recommended)
|
||||
1. Download the Windows zip from GitHub releases.
|
||||
2. Extract to a writable directory.
|
||||
3. Run `yaze.exe` (GUI) or `z3ed.exe` (CLI) from the `bin` folder.
|
||||
|
||||
### 2) vcpkg-from-source (DIY)
|
||||
If you prefer source builds with vcpkg dependencies:
|
||||
1. Install vcpkg and integrate: `vcpkg integrate install`.
|
||||
2. Configure: `cmake --preset win-dbg -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake`.
|
||||
3. Build: `cmake --build --preset win-dbg`.
|
||||
4. Run tests (optional): `ctest --test-dir build`.
|
||||
|
||||
## Linux
|
||||
### 1) Release AppImage (if available)
|
||||
- `chmod +x yaze-<version>-linux.AppImage && ./yaze-<version>-linux.AppImage`
|
||||
- If graphics fail under Wayland, try `XWAYLAND_FORCE=1 ./yaze-<version>-linux.AppImage`.
|
||||
|
||||
### 2) Build from source
|
||||
Prereqs: `cmake`, `ninja-build`, `pkg-config`, `libsdl2-dev`, `libglew-dev`, `libglm-dev`, `protobuf-compiler`, `libprotobuf-dev`, `libabsl-dev`, `libftxui-dev` (or build from source), `zlib1g-dev`.
|
||||
```
|
||||
cmake --preset lin-dbg
|
||||
cmake --build --preset lin-dbg
|
||||
ctest --test-dir build -L stable # optional
|
||||
```
|
||||
|
||||
## Web (WASM Preview)
|
||||
- Use the published web build (if provided) or self-host the `build-wasm` output:
|
||||
```
|
||||
cd build-wasm && npx http-server .
|
||||
```
|
||||
- Open the local URL in a modern browser; no installation required.
|
||||
|
||||
## Packaging Notes
|
||||
- Prefer static/runtime-complete bundles for end users (AppImage on Linux, app bundle on macOS, zip on Windows).
|
||||
- When creating packages (Homebrew/Chocolatey/winget), pin the release URL and checksum and align dependencies to the CMake presets (`mac-*/lin-*/win-*`).
|
||||
- Keep CLI and GUI in the same archive to avoid mismatched versions; CLI entry is `z3ed`, GUI entry is `yaze`.
|
||||
|
||||
## Quick Links
|
||||
- Build quick reference: `docs/public/build/quick-reference.md`
|
||||
- CMake presets: `CMakePresets.json`
|
||||
- Tests (optional after build): `ctest --test-dir build -L stable`
|
||||
@@ -1,6 +1,6 @@
|
||||
# Platform Compatibility & CI/CD Fixes
|
||||
# Platform Compatibility & CI/CD
|
||||
|
||||
**Last Updated**: October 9, 2025
|
||||
**Last Updated**: November 27, 2025
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -114,8 +114,7 @@ By default, all presets suppress compiler warnings with `-w` for a cleaner build
|
||||
|
||||
## Build Directories
|
||||
|
||||
Most presets use `build/` directory. Exceptions:
|
||||
- `mac-rooms`: Uses `build_rooms/` to avoid conflicts
|
||||
Most presets use `build/`. WASM presets use `build-wasm/`. Use `CMakeUserPresets.json` for custom directories.
|
||||
|
||||
## Feature Flags
|
||||
|
||||
|
||||
@@ -1,149 +1,185 @@
|
||||
# Build & Test Quick Reference
|
||||
# Build and Test Quick Reference
|
||||
|
||||
Use this document as the single source of truth for configuring, building, and testing YAZE across
|
||||
platforms. Other guides (README, CLAUDE.md, GEMINI.md, etc.) should link here instead of duplicating
|
||||
steps.
|
||||
This document is the single source of truth for configuring, building, and testing YAZE on all platforms. Other documentation files link here rather than duplicating these instructions.
|
||||
|
||||
## 1. Environment Prep
|
||||
- Clone with submodules: `git clone --recursive https://github.com/scawful/yaze.git`
|
||||
- Run the verifier once per machine:
|
||||
- macOS/Linux: `./scripts/verify-build-environment.sh --fix`
|
||||
- Windows PowerShell: `.\scripts\verify-build-environment.ps1 -FixIssues`
|
||||
---
|
||||
|
||||
## 1. Environment Setup
|
||||
|
||||
### Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone --recursive https://github.com/scawful/yaze.git
|
||||
cd yaze
|
||||
```
|
||||
|
||||
### Verify Your Environment
|
||||
|
||||
Run the verification script once per machine to check dependencies and fix common issues:
|
||||
|
||||
**macOS / Linux:**
|
||||
```bash
|
||||
./scripts/verify-build-environment.sh --fix
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
.\scripts\verify-build-environment.ps1 -FixIssues
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Build Presets
|
||||
Use `cmake --preset <name>` followed by `cmake --build --preset <name> [--target …]`.
|
||||
|
||||
| Preset | Platform(s) | Notes |
|
||||
|-------------|-------------|-------|
|
||||
| `mac-dbg`, `lin-dbg`, `win-dbg` | macOS/Linux/Windows | Standard debug builds, tests on by default. |
|
||||
| `mac-ai`, `lin-ai`, `win-ai` | macOS/Linux/Windows | Enables gRPC, agent UI, `z3ed`, and AI runtime. |
|
||||
| `mac-rel`, `lin-rel`, `win-rel` | macOS/Linux/Windows | Optimized release builds. |
|
||||
| `mac-dev`, `lin-dev`, `win-dev` | macOS/Linux/Windows | Development builds with ROM-dependent tests enabled. |
|
||||
| `mac-uni` | macOS | Universal binary (ARM64 + x86_64) for distribution. |
|
||||
| `ci-*` presets | Platform-specific | Mirrors CI matrix; see `CMakePresets.json`. |
|
||||
YAZE uses CMake presets for consistent builds. Configure with `cmake --preset <name>`, then build with `cmake --build --preset <name>`.
|
||||
|
||||
**Verbose builds**: add `-v` suffix (e.g., `mac-dbg-v`, `lin-dbg-v`, `win-dbg-v`) to turn off compiler warning suppression.
|
||||
### Available Presets
|
||||
|
||||
## 3. AI/Assistant Build Policy
|
||||
- Human developers typically use `build` or `build_test` directories.
|
||||
- AI assistants **must use dedicated directories** (`build_ai`, `build_agent`, etc.) to avoid
|
||||
clobbering user builds.
|
||||
- When enabling AI features, prefer the `*-ai` presets and target only the binaries you need
|
||||
(`yaze`, `z3ed`, `yaze_test`, …).
|
||||
- Windows helpers: use `scripts/agents/windows-smoke-build.ps1` for quick builds and `scripts/agents/run-tests.sh` (or its PowerShell equivalent) for test runs so preset + generator settings stay consistent.
|
||||
| Preset | Platform | Description |
|
||||
|--------|----------|-------------|
|
||||
| `mac-dbg`, `lin-dbg`, `win-dbg` | macOS / Linux / Windows | Standard debug builds with tests enabled |
|
||||
| `mac-ai`, `lin-ai`, `win-ai` | macOS / Linux / Windows | Full AI stack: gRPC, agent UI, z3ed CLI, AI runtime |
|
||||
| `mac-rel`, `lin-rel`, `win-rel` | macOS / Linux / Windows | Optimized release builds |
|
||||
| `mac-dev`, `lin-dev`, `win-dev` | macOS / Linux / Windows | Development builds with ROM-dependent tests |
|
||||
| `mac-uni` | macOS | Universal binary (ARM64 + x86_64) for distribution |
|
||||
| `mac-test`, `lin-test`, `win-test` | All | Optimized builds for fast test iteration |
|
||||
| `ci-*` | Platform-specific | CI/CD configurations (see CMakePresets.json) |
|
||||
|
||||
## 4. Common Commands
|
||||
**Tip:** Add `-v` suffix (e.g., `mac-dbg-v`) to enable verbose compiler warnings.
|
||||
|
||||
---
|
||||
|
||||
## 3. Build Directory Policy
|
||||
|
||||
| Build Type | Default Directory |
|
||||
|------------|-------------------|
|
||||
| Native (desktop/CLI) | `build/` |
|
||||
| WASM | `build-wasm/` |
|
||||
|
||||
If you need per-user or per-agent isolation, create a local `CMakeUserPresets.json` that points `binaryDir` to a custom path.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
cp CMakeUserPresets.json.example CMakeUserPresets.json
|
||||
export YAZE_BUILD_ROOT="$HOME/.cache/yaze"
|
||||
cmake --preset dev-local
|
||||
cmake --build --preset dev-local --target yaze
|
||||
```
|
||||
|
||||
For AI-enabled builds, use the `*-ai` presets and specify only the targets you need:
|
||||
```bash
|
||||
cmake --build --preset mac-ai --target yaze z3ed
|
||||
```
|
||||
|
||||
**Windows Helper Scripts:**
|
||||
- Quick builds: `scripts/agents/windows-smoke-build.ps1`
|
||||
- Test runs: `scripts/agents/run-tests.sh` (or PowerShell equivalent)
|
||||
|
||||
---
|
||||
|
||||
## 4. Common Build Commands
|
||||
|
||||
### Standard Debug Build
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
# Debug GUI build (macOS)
|
||||
cmake --preset mac-dbg
|
||||
cmake --build --preset mac-dbg --target yaze
|
||||
```
|
||||
|
||||
# Debug GUI build (Linux)
|
||||
**Linux:**
|
||||
```bash
|
||||
cmake --preset lin-dbg
|
||||
cmake --build --preset lin-dbg --target yaze
|
||||
```
|
||||
|
||||
# Debug GUI build (Windows)
|
||||
**Windows:**
|
||||
```bash
|
||||
cmake --preset win-dbg
|
||||
cmake --build --preset win-dbg --target yaze
|
||||
```
|
||||
|
||||
# AI-enabled build with gRPC (macOS)
|
||||
### AI-Enabled Build (with gRPC and z3ed CLI)
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
cmake --preset mac-ai
|
||||
cmake --build --preset mac-ai --target yaze z3ed
|
||||
```
|
||||
|
||||
# AI-enabled build with gRPC (Linux)
|
||||
**Linux:**
|
||||
```bash
|
||||
cmake --preset lin-ai
|
||||
cmake --build --preset lin-ai --target yaze z3ed
|
||||
```
|
||||
|
||||
# AI-enabled build with gRPC (Windows)
|
||||
**Windows:**
|
||||
```bash
|
||||
cmake --preset win-ai
|
||||
cmake --build --preset win-ai --target yaze z3ed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing
|
||||
|
||||
### Default Tests (Always Available)
|
||||
YAZE uses CTest with GoogleTest. Tests are organized by category using labels.
|
||||
|
||||
Default test suites run automatically with debug/dev presets. Include stable unit/integration tests and GUI smoke tests:
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Build stable test suite (always included in debug presets)
|
||||
cmake --build --preset mac-dbg --target yaze_test_stable
|
||||
# Run stable tests (fast, no ROM required)
|
||||
ctest --test-dir build -L stable -j4
|
||||
|
||||
# Run with ctest (recommended approach)
|
||||
ctest --preset mac-dbg -L stable # Stable tests only
|
||||
ctest --preset mac-dbg -L gui # GUI smoke tests
|
||||
ctest --test-dir build -L "stable|gui" # Both stable + GUI
|
||||
# Run all enabled tests
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
# Run tests matching a pattern
|
||||
ctest --test-dir build -R "Dungeon"
|
||||
```
|
||||
|
||||
### Optional: ROM-Dependent Tests
|
||||
### Test Categories
|
||||
|
||||
For tests requiring Zelda3 ROM file (ASAR ROM tests, complete edit workflows, ZSCustomOverworld upgrades):
|
||||
| Category | Command | Description |
|
||||
|----------|---------|-------------|
|
||||
| Stable | `ctest --test-dir build -L stable` | Core unit tests, always available |
|
||||
| GUI | `ctest --test-dir build -L gui` | GUI smoke tests |
|
||||
| ROM-dependent | `ctest --test-dir build -L rom_dependent` | Requires a Zelda 3 ROM |
|
||||
| Experimental | `ctest --test-dir build -L experimental` | AI/experimental features |
|
||||
|
||||
### Enabling ROM-Dependent Tests
|
||||
|
||||
```bash
|
||||
# Configure with ROM path
|
||||
cmake --preset mac-dbg -DYAZE_ENABLE_ROM_TESTS=ON -DYAZE_TEST_ROM_PATH=~/zelda3.sfc
|
||||
cmake --preset mac-dev -DYAZE_TEST_ROM_PATH=/path/to/zelda3.sfc
|
||||
|
||||
# Build ROM test suite
|
||||
cmake --build --preset mac-dbg --target yaze_test_rom_dependent
|
||||
|
||||
# Run ROM tests
|
||||
# Build and run
|
||||
cmake --build --preset mac-dev --target yaze_test
|
||||
ctest --test-dir build -L rom_dependent
|
||||
```
|
||||
|
||||
### Optional: Experimental AI Tests
|
||||
|
||||
For AI-powered feature tests (requires `YAZE_ENABLE_AI_RUNTIME=ON`):
|
||||
|
||||
```bash
|
||||
# Use AI-enabled preset
|
||||
cmake --preset mac-ai
|
||||
|
||||
# Build experimental test suite
|
||||
cmake --build --preset mac-ai --target yaze_test_experimental
|
||||
|
||||
# Run AI tests
|
||||
ctest --test-dir build -L experimental
|
||||
```
|
||||
|
||||
### Test Commands Reference
|
||||
|
||||
```bash
|
||||
# Stable tests only (recommended for quick iteration)
|
||||
ctest --test-dir build -L stable -j4
|
||||
|
||||
# All enabled tests (respects preset configuration)
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
# GUI smoke tests
|
||||
ctest --test-dir build -L gui
|
||||
|
||||
# Headless GUI tests (CI mode)
|
||||
ctest --test-dir build -L headless_gui
|
||||
|
||||
# Tests matching pattern
|
||||
ctest --test-dir build -R "Dungeon"
|
||||
|
||||
# Verbose output
|
||||
ctest --test-dir build --verbose
|
||||
```
|
||||
|
||||
### Test Organization by Preset
|
||||
### Test Coverage by Preset
|
||||
|
||||
| Preset | Stable | GUI | ROM-Dep | Experimental |
|
||||
|--------|--------|-----|---------|--------------|
|
||||
| `mac-dbg`, `lin-dbg`, `win-dbg` | Yes | Yes | No | No |
|
||||
| `mac-ai`, `lin-ai`, `win-ai` | Yes | Yes | No | Yes |
|
||||
| `mac-dev`, `lin-dev`, `win-dev` | Yes | Yes | Yes | No |
|
||||
| `mac-rel`, `lin-rel`, `win-rel` | No | No | No | No |
|
||||
|--------|:------:|:---:|:-------:|:------------:|
|
||||
| `*-dbg` | Yes | Yes | No | No |
|
||||
| `*-ai` | Yes | Yes | No | Yes |
|
||||
| `*-dev` | Yes | Yes | Yes | No |
|
||||
| `*-rel` | No | No | No | No |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `YAZE_TEST_ROM_PATH` - Set ROM path for ROM-dependent tests (or use `-DYAZE_TEST_ROM_PATH=...` in CMake)
|
||||
- `YAZE_SKIP_ROM_TESTS` - Skip ROM tests if set (useful for CI without ROM)
|
||||
- `YAZE_ENABLE_UI_TESTS` - Enable GUI tests (default if display available)
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `YAZE_TEST_ROM_PATH` | Path to ROM for ROM-dependent tests |
|
||||
| `YAZE_SKIP_ROM_TESTS` | Skip ROM tests (useful for CI) |
|
||||
| `YAZE_ENABLE_UI_TESTS` | Enable GUI tests (auto-detected if display available) |
|
||||
|
||||
## 6. Troubleshooting & References
|
||||
- Detailed troubleshooting: `docs/public/build/troubleshooting.md`
|
||||
- Platform compatibility: `docs/public/build/platform-compatibility.md`
|
||||
- Internal agents must follow coordination protocol in
|
||||
`docs/internal/agents/coordination-board.md` before running builds/tests.
|
||||
---
|
||||
|
||||
## 6. Further Reading
|
||||
|
||||
- **[Build Troubleshooting](troubleshooting.md)** - Solutions for common build issues
|
||||
- **[Platform Compatibility](platform-compatibility.md)** - Platform-specific notes and CI/CD details
|
||||
- **[CMake Presets Guide](presets.md)** - Complete preset reference
|
||||
- **[Testing Guide](../developer/testing-guide.md)** - Comprehensive testing documentation
|
||||
|
||||
84
docs/public/cli/README.md
Normal file
84
docs/public/cli/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# z3ed CLI Reference
|
||||
|
||||
The `z3ed` command-line tool provides ROM inspection, validation, AI-assisted editing, and automation capabilities.
|
||||
|
||||
---
|
||||
|
||||
## Command Categories
|
||||
|
||||
### Doctor Suite (Diagnostics)
|
||||
|
||||
Validate and repair ROM data integrity.
|
||||
|
||||
- [Doctor Commands](doctor-commands.md) - `rom-doctor`, `dungeon-doctor`, `overworld-doctor`, `rom-compare`
|
||||
|
||||
### Test Infrastructure
|
||||
|
||||
Machine-readable test discovery and execution.
|
||||
|
||||
- [Test Commands](test-commands.md) - `test-list`, `test-run`, `test-status`
|
||||
|
||||
### Inspection Tools
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `hex-read` | Read raw bytes from ROM |
|
||||
| `hex-search` | Search for byte patterns |
|
||||
| `palette-get-colors` | Extract palette data |
|
||||
| `sprite-list` | List sprites |
|
||||
| `music-list` | List music tracks |
|
||||
| `dialogue-list` | List dialogue entries |
|
||||
|
||||
### Overworld Tools
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `overworld-find-tile` | Find tile usage across maps |
|
||||
| `overworld-describe-map` | Describe map properties |
|
||||
| `overworld-list-warps` | List warp points |
|
||||
| `overworld-list-sprites` | List overworld sprites |
|
||||
|
||||
### Dungeon Tools
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `dungeon-list-sprites` | List dungeon sprites |
|
||||
| `dungeon-describe-room` | Describe room properties |
|
||||
| `dungeon-list-objects` | List room objects |
|
||||
|
||||
---
|
||||
|
||||
## Common Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--rom <path>` | Path to ROM file |
|
||||
| `--format json\|text` | Output format |
|
||||
| `--verbose` | Detailed output |
|
||||
| `--help` | Show command help |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# List all commands
|
||||
z3ed help
|
||||
|
||||
# Get help for a command
|
||||
z3ed help rom-doctor
|
||||
|
||||
# JSON output for scripting
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json
|
||||
|
||||
# Parse with jq
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json | jq '.checksum_valid'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [z3ed CLI Guide](../usage/z3ed-cli.md) - Usage tutorials and workflows
|
||||
- [Getting Started](../overview/getting-started.md) - Quick start guide
|
||||
|
||||
190
docs/public/cli/doctor-commands.md
Normal file
190
docs/public/cli/doctor-commands.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# z3ed Doctor Commands
|
||||
|
||||
The doctor command suite provides diagnostic and repair tools for ROM data integrity. All commands support structured JSON output for automation.
|
||||
|
||||
## Available Doctor Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `overworld-doctor` | Diagnose/repair overworld data (tile16, pointers, ZSCustom features) |
|
||||
| `overworld-validate` | Validate map32 pointers and decompression |
|
||||
| `dungeon-doctor` | Diagnose dungeon room data (objects, sprites, chests) |
|
||||
| `rom-doctor` | Validate ROM file integrity (header, checksums, expansions) |
|
||||
| `rom-compare` | Compare two ROMs for differences |
|
||||
|
||||
## Common Flags
|
||||
|
||||
All doctor commands support:
|
||||
- `--rom <path>` - Path to ROM file (required)
|
||||
- `--format json|text` - Output format (default: text)
|
||||
- `--verbose` - Show detailed output
|
||||
|
||||
## overworld-doctor
|
||||
|
||||
Diagnose and repair overworld data corruption.
|
||||
|
||||
```bash
|
||||
# Basic diagnosis
|
||||
z3ed overworld-doctor --rom zelda3.sfc
|
||||
|
||||
# Compare against vanilla baseline
|
||||
z3ed overworld-doctor --rom zelda3.sfc --baseline vanilla.sfc
|
||||
|
||||
# Apply fixes with dry-run preview
|
||||
z3ed overworld-doctor --rom zelda3.sfc --fix --output fixed.sfc --dry-run
|
||||
|
||||
# JSON output for agents
|
||||
z3ed overworld-doctor --rom zelda3.sfc --format json
|
||||
```
|
||||
|
||||
### Detects
|
||||
- ZSCustomOverworld version (Vanilla, v2, v3)
|
||||
- Expanded tile16/tile32 regions
|
||||
- Expanded pointer tables (tail map support)
|
||||
- Tile16 corruption at known problem addresses
|
||||
- Map pointer validity for all 160+ maps
|
||||
|
||||
## dungeon-doctor
|
||||
|
||||
Diagnose dungeon room data integrity.
|
||||
|
||||
```bash
|
||||
# Sample key rooms (fast)
|
||||
z3ed dungeon-doctor --rom zelda3.sfc
|
||||
|
||||
# Analyze all 296 rooms
|
||||
z3ed dungeon-doctor --rom zelda3.sfc --all
|
||||
|
||||
# Analyze specific room
|
||||
z3ed dungeon-doctor --rom zelda3.sfc --room 0x10
|
||||
|
||||
# JSON output
|
||||
z3ed dungeon-doctor --rom zelda3.sfc --format json --verbose
|
||||
```
|
||||
|
||||
### Validates
|
||||
- Room header pointers
|
||||
- Object counts (max 400 before lag)
|
||||
- Sprite counts (max 64 per room)
|
||||
- Chest counts (max 6 per room for item flags)
|
||||
- Object bounds (0-63 for x/y coordinates)
|
||||
|
||||
### Sample Output (Text)
|
||||
```
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ DUNGEON DOCTOR ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ Rooms Analyzed: 19 ║
|
||||
║ Valid Rooms: 19 ║
|
||||
║ Rooms with Warnings: 0 ║
|
||||
║ Rooms with Errors: 0 ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ Total Objects: 890 ║
|
||||
║ Total Sprites: 98 ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
## rom-doctor
|
||||
|
||||
Validate ROM file integrity and expansion status.
|
||||
|
||||
```bash
|
||||
# Basic validation
|
||||
z3ed rom-doctor --rom zelda3.sfc
|
||||
|
||||
# Verbose with all findings
|
||||
z3ed rom-doctor --rom zelda3.sfc --verbose
|
||||
|
||||
# JSON output for CI/automation
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json
|
||||
```
|
||||
|
||||
### Validates
|
||||
- SNES header (title, map mode, country)
|
||||
- Checksum verification (complement XOR checksum = 0xFFFF)
|
||||
- ROM size (vanilla 1MB vs expanded 2MB)
|
||||
- ZSCustomOverworld version detection
|
||||
- Expansion flags (tile16, tile32, pointer tables)
|
||||
- Free space analysis in expansion region
|
||||
|
||||
### Sample Output (Text)
|
||||
```
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ROM DOCTOR ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ ROM Title: THE LEGEND OF ZELDA ║
|
||||
║ Size: 0x200000 bytes (2048 KB) ║
|
||||
║ Map Mode: LoROM ║
|
||||
║ Country: USA ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ Checksum: 0xAF0D (complement: 0x50F2) - VALID ║
|
||||
║ ZSCustomOverworld: Vanilla ║
|
||||
║ Expanded Tile16: NO ║
|
||||
║ Expanded Tile32: NO ║
|
||||
║ Expanded Ptr Tables: NO ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
## rom-compare
|
||||
|
||||
Compare two ROMs to identify differences.
|
||||
|
||||
```bash
|
||||
# Basic comparison
|
||||
z3ed rom-compare --rom my_rom.sfc --baseline vanilla.sfc
|
||||
|
||||
# Show detailed byte differences
|
||||
z3ed rom-compare --rom my_rom.sfc --baseline vanilla.sfc --show-diff
|
||||
|
||||
# JSON output
|
||||
z3ed rom-compare --rom my_rom.sfc --baseline vanilla.sfc --format json
|
||||
```
|
||||
|
||||
## Diagnostic Schema
|
||||
|
||||
All doctor commands produce findings with consistent structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"findings": [
|
||||
{
|
||||
"id": "tile16_corruption",
|
||||
"severity": "error",
|
||||
"message": "Corrupted tile16 at 0x1E878B",
|
||||
"location": "0x1E878B",
|
||||
"suggested_action": "Run with --fix to zero corrupted entries",
|
||||
"fixable": true
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"total_findings": 1,
|
||||
"critical": 0,
|
||||
"errors": 1,
|
||||
"warnings": 0,
|
||||
"info": 0,
|
||||
"fixable": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Severity Levels
|
||||
- `info` - Informational, no action needed
|
||||
- `warning` - Potential issue, may need attention
|
||||
- `error` - Problem detected, should be fixed
|
||||
- `critical` - Severe issue, requires immediate attention
|
||||
|
||||
## Agent Usage
|
||||
|
||||
For AI agents consuming doctor output:
|
||||
|
||||
```bash
|
||||
# Get structured JSON for parsing
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json
|
||||
|
||||
# Chain with jq for specific fields
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json | jq '.checksum_valid'
|
||||
|
||||
# Check exit code for pass/fail
|
||||
z3ed rom-doctor --rom zelda3.sfc --format json && echo "ROM OK"
|
||||
```
|
||||
|
||||
237
docs/public/cli/test-commands.md
Normal file
237
docs/public/cli/test-commands.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# z3ed Test Commands
|
||||
|
||||
The test command suite provides machine-readable test discovery and execution for CI/CD and agent automation.
|
||||
|
||||
## Available Test Commands
|
||||
|
||||
| Command | Description | Requires ROM |
|
||||
|---------|-------------|--------------|
|
||||
| `test-list` | List available test suites with labels and requirements | No |
|
||||
| `test-run` | Run tests with structured output | No |
|
||||
| `test-status` | Show test configuration status | No |
|
||||
|
||||
## test-list
|
||||
|
||||
Discover available test suites and their requirements.
|
||||
|
||||
```bash
|
||||
# Human-readable list
|
||||
z3ed test-list
|
||||
|
||||
# Machine-readable JSON for agents
|
||||
z3ed test-list --format json
|
||||
|
||||
# Filter by label
|
||||
z3ed test-list --label stable
|
||||
```
|
||||
|
||||
### Sample Output (Text)
|
||||
```
|
||||
=== Available Test Suites ===
|
||||
|
||||
stable Core unit and integration tests (fast, reliable)
|
||||
Requirements: None
|
||||
|
||||
gui GUI smoke tests (ImGui framework validation)
|
||||
Requirements: SDL display or headless
|
||||
|
||||
z3ed z3ed CLI self-test and smoke tests
|
||||
Requirements: z3ed target built
|
||||
|
||||
headless_gui GUI tests in headless mode (CI-safe)
|
||||
Requirements: None
|
||||
|
||||
rom_dependent Tests requiring actual Zelda3 ROM
|
||||
Requirements: YAZE_ENABLE_ROM_TESTS=ON + ROM path
|
||||
⚠ Requires ROM file
|
||||
|
||||
experimental AI runtime features and experiments
|
||||
Requirements: YAZE_ENABLE_AI_RUNTIME=ON
|
||||
⚠ Requires AI runtime
|
||||
|
||||
benchmark Performance and optimization tests
|
||||
Requirements: None
|
||||
```
|
||||
|
||||
### Sample Output (JSON)
|
||||
```json
|
||||
{
|
||||
"suites": [
|
||||
{
|
||||
"label": "stable",
|
||||
"description": "Core unit and integration tests (fast, reliable)",
|
||||
"requirements": "None",
|
||||
"requires_rom": false,
|
||||
"requires_ai": false
|
||||
},
|
||||
{
|
||||
"label": "rom_dependent",
|
||||
"description": "Tests requiring actual Zelda3 ROM",
|
||||
"requirements": "YAZE_ENABLE_ROM_TESTS=ON + ROM path",
|
||||
"requires_rom": true,
|
||||
"requires_ai": false
|
||||
}
|
||||
],
|
||||
"total_tests_discovered": 42,
|
||||
"build_directory": "build"
|
||||
}
|
||||
```
|
||||
|
||||
## test-run
|
||||
|
||||
Run tests and get structured results.
|
||||
|
||||
```bash
|
||||
# Run stable tests (default)
|
||||
z3ed test-run
|
||||
|
||||
# Run specific label
|
||||
z3ed test-run --label gui
|
||||
|
||||
# Run with preset
|
||||
z3ed test-run --label stable --preset mac-test
|
||||
|
||||
# Verbose output
|
||||
z3ed test-run --label stable --verbose
|
||||
|
||||
# JSON output for CI
|
||||
z3ed test-run --label stable --format json
|
||||
```
|
||||
|
||||
### Sample Output (JSON)
|
||||
```json
|
||||
{
|
||||
"build_directory": "build",
|
||||
"label": "stable",
|
||||
"preset": "default",
|
||||
"tests_passed": 42,
|
||||
"tests_failed": 0,
|
||||
"tests_total": 42,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### Exit Codes
|
||||
- `0` - All tests passed
|
||||
- `1` - One or more tests failed or error occurred
|
||||
|
||||
## test-status
|
||||
|
||||
Show current test configuration.
|
||||
|
||||
```bash
|
||||
# Human-readable status
|
||||
z3ed test-status
|
||||
|
||||
# JSON for agents
|
||||
z3ed test-status --format json
|
||||
```
|
||||
|
||||
### Sample Output (Text)
|
||||
```
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ TEST CONFIGURATION ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ ROM Path: (not set) ║
|
||||
║ Skip ROM Tests: NO ║
|
||||
║ UI Tests Enabled: NO ║
|
||||
║ Active Preset: mac-test (fast) ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ Available Build Directories: ║
|
||||
║ ✓ build ║
|
||||
╠═══════════════════════════════════════════════════════════════╣
|
||||
║ Available Test Suites: ║
|
||||
║ ✓ stable ║
|
||||
║ ✓ gui ║
|
||||
║ ✓ z3ed ║
|
||||
║ ✗ rom_dependent (needs ROM) ║
|
||||
║ ✓ experimental (needs AI) ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
### Sample Output (JSON)
|
||||
```json
|
||||
{
|
||||
"rom_path": "not set",
|
||||
"skip_rom_tests": false,
|
||||
"ui_tests_enabled": false,
|
||||
"build_directories": ["build"],
|
||||
"active_preset": "mac-test (fast)",
|
||||
"available_suites": ["stable", "gui", "z3ed", "headless_gui", "experimental", "benchmark"]
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The test commands respect these environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `YAZE_TEST_ROM_PATH` | Path to Zelda3 ROM for ROM-dependent tests |
|
||||
| `YAZE_SKIP_ROM_TESTS` | Set to `1` to skip ROM tests |
|
||||
| `YAZE_ENABLE_UI_TESTS` | Set to `1` to enable UI tests |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### For Developers
|
||||
```bash
|
||||
# Configure fast test build
|
||||
cmake --preset mac-test
|
||||
|
||||
# Build test targets
|
||||
cmake --build --preset mac-test
|
||||
|
||||
# Run stable tests
|
||||
z3ed test-run --label stable
|
||||
```
|
||||
|
||||
### For CI/Agents
|
||||
```bash
|
||||
# Check what's available
|
||||
z3ed test-list --format json
|
||||
|
||||
# Run stable suite and capture results
|
||||
z3ed test-run --label stable --format json > test-results.json
|
||||
|
||||
# Check exit code
|
||||
if [ $? -eq 0 ]; then echo "All tests passed"; fi
|
||||
```
|
||||
|
||||
### With ROM Tests
|
||||
```bash
|
||||
# Set ROM path
|
||||
export YAZE_TEST_ROM_PATH=/path/to/zelda3.sfc
|
||||
|
||||
# Configure with ROM tests enabled
|
||||
cmake --preset mac-dev -DYAZE_ENABLE_ROM_TESTS=ON
|
||||
|
||||
# Run ROM-dependent tests
|
||||
z3ed test-run --label rom_dependent
|
||||
```
|
||||
|
||||
## Integration with ctest
|
||||
|
||||
The test commands wrap `ctest` internally. You can also use ctest directly:
|
||||
|
||||
```bash
|
||||
# Equivalent to z3ed test-run --label stable
|
||||
ctest --test-dir build -L stable
|
||||
|
||||
# Run all tests
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
# Run specific pattern
|
||||
ctest --test-dir build -R "RomTest"
|
||||
```
|
||||
|
||||
## Test Labels Reference
|
||||
|
||||
| Label | Description | CI Stage |
|
||||
|-------|-------------|----------|
|
||||
| `stable` | Core unit + integration tests | PR/Push |
|
||||
| `gui` | GUI smoke tests | PR/Push |
|
||||
| `z3ed` | CLI self-tests | PR/Push |
|
||||
| `headless_gui` | CI-safe GUI tests | PR/Push |
|
||||
| `rom_dependent` | Tests requiring ROM | Nightly |
|
||||
| `experimental` | AI features | Nightly |
|
||||
| `benchmark` | Performance tests | Nightly |
|
||||
572
docs/public/deployment/collaboration-server-setup.md
Normal file
572
docs/public/deployment/collaboration-server-setup.md
Normal file
@@ -0,0 +1,572 @@
|
||||
# Collaboration Server Setup Guide
|
||||
|
||||
This guide explains how to set up a WebSocket server for yaze's real-time collaboration feature, enabling multiple users to edit ROMs together.
|
||||
|
||||
## Quick Start with yaze-server
|
||||
|
||||
The official collaboration server is **[yaze-server](https://github.com/scawful/yaze-server)**, a Node.js WebSocket server with:
|
||||
- Real-time session management
|
||||
- AI agent integration (Gemini/Genkit)
|
||||
- ROM synchronization and diff broadcasting
|
||||
- Rate limiting and security features
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
git clone https://github.com/scawful/yaze-server.git
|
||||
cd yaze-server
|
||||
npm ci
|
||||
npm start
|
||||
# Server runs on ws://localhost:8765 (default port 8765)
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
For production, deploy yaze-server behind an SSL proxy when possible:
|
||||
- **halext-server**: `ws://org.halext.org:8765` (pm2 process `yaze-collab`, no TLS on 8765 today; front with nginx/Caddy for `wss://` if desired)
|
||||
- **Self-hosted**: Deploy to Railway, Render, Fly.io, or your own VPS
|
||||
|
||||
### Current halext deployment (ssh halext-server)
|
||||
- Process: pm2 `yaze-collab`
|
||||
- Port: `8765` (plain WS/HTTP; add TLS proxy for WSS)
|
||||
- Health: `http://org.halext.org:8765/health`, metrics at `/metrics`
|
||||
- AI: enable with `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` + `ENABLE_AI_AGENT=true`
|
||||
|
||||
### Server v2.1 Features
|
||||
- **Persistence**: Configurable SQLite storage (`SQLITE_DB_PATH` env var)
|
||||
- **Admin API**: Protected endpoints for session/room management
|
||||
- **Enhanced Health**: AI status, TLS detection, persistence info in `/health`
|
||||
- **Configurable Limits**: Tunable rate limits via environment variables
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The yaze web app (WASM build) supports real-time collaboration through WebSocket connections. Since GitHub Pages only serves static files, you'll need a separate WebSocket server to enable this feature.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ User A │ │ WebSocket │ │ User B │
|
||||
│ (Browser) │◄───►│ Server │◄───►│ (Browser) │
|
||||
│ yaze WASM │ │ (Your Server) │ │ yaze WASM │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Protocol Specification
|
||||
|
||||
The halext deployment (`yaze-collab` pm2 on port 8765) runs the official **yaze-server v2.0** and speaks two compatible protocols:
|
||||
- **WASM compatibility protocol** – used by the current web/WASM build (flat `type` JSON messages, no `payload` wrapper).
|
||||
- **Session/AI protocol** – used by advanced clients (desktop/editor + AI/ROM sync/proposals) with `{ type, payload }` envelopes.
|
||||
|
||||
### WASM compatibility protocol (web build)
|
||||
All messages are JSON with a `type` field and flat attributes.
|
||||
|
||||
**Client → Server**
|
||||
- `create`: `room`, `name?`, `user`, `user_id`, `color?`, `password?`
|
||||
- `join`: `room`, `user`, `user_id`, `color?`, `password?`
|
||||
- `leave`: `room`, `user_id`
|
||||
- `change`: `room`, `user_id`, `offset`, `old_data`, `new_data`, `timestamp?`
|
||||
- `cursor`: `room`, `user_id`, `editor`, `x`, `y`, `map_id`
|
||||
- `ping`: optional keep-alive (`{ "type": "ping" }`)
|
||||
|
||||
**Server → Client**
|
||||
- `create_response`: `{ "type": "create_response", "success": true, "session_name": "..." }`
|
||||
- `join_response`: `{ "type": "join_response", "success": true, "session_name": "..." }`
|
||||
- `users`: `{ "type": "users", "list": [{ "id": "...", "name": "...", "color": "#4ECDC4", "active": true }] }`
|
||||
- `change`: Echoed to room with `timestamp` added
|
||||
- `cursor`: Broadcast presence updates
|
||||
- `error`: `{ "type": "error", "message": "...", "payload": { "error": "..." } }`
|
||||
- `pong`: `{ "type": "pong", "payload": { "timestamp": 1700000000000 } }`
|
||||
|
||||
**Notes**
|
||||
- Passwords are supported (`password` hashed server-side); rooms are deleted when empty.
|
||||
- Rate limits: 100 messages/min/IP; 10 join/host attempts/min/IP.
|
||||
- Size limits: ROM diffs ≤ 5 MB, snapshots ≤ 10 MB. Heartbeat every 30s terminates dead sockets.
|
||||
|
||||
### Session/AI protocol (advanced clients)
|
||||
Messages use `{ "type": "...", "payload": { ... } }`.
|
||||
|
||||
**Key client messages**
|
||||
- `host_session`: `session_name`, `username`, `rom_hash?`, `ai_enabled? (default true)`, `session_password?`
|
||||
- `join_session`: `session_code`, `username`, `session_password?`
|
||||
- `chat_message`: `sender`, `message`, `message_type?`, `metadata?`
|
||||
- `rom_sync`: `sender`, `diff_data` (base64), `rom_hash`
|
||||
- `snapshot_share`: `sender`, `snapshot_data` (base64), `snapshot_type`
|
||||
- `proposal_share` / `proposal_vote` / `proposal_update`
|
||||
- `ai_query`: `username`, `query` (requires `ENABLE_AI_AGENT` plus `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT`)
|
||||
- `leave_session`, `ping`
|
||||
|
||||
**Key server broadcasts**
|
||||
- `session_hosted`, `session_joined`, `participant_joined`, `participant_left`
|
||||
- `chat_message`, `rom_sync`, `snapshot_shared`
|
||||
- `proposal_shared`, `proposal_vote_received`, `proposal_updated`
|
||||
- `ai_response` (only when AI is enabled and configured)
|
||||
- `pong`, `error`, `server_shutdown`
|
||||
|
||||
See `yaze-server/README.md` for full payload examples.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### Self-Hosted (VPS/Dedicated Server)
|
||||
|
||||
1. Install Node.js 18+
|
||||
2. Clone/copy the server code and install deps:
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
3. Configure environment variables:
|
||||
|
||||
**Core Settings:**
|
||||
```bash
|
||||
PORT=8765 # WebSocket/HTTP port (default: 8765)
|
||||
ENABLE_AI_AGENT=true # Enable AI query handling (default: true)
|
||||
GEMINI_API_KEY=your_api_key # Gemini API key for AI responses
|
||||
AI_AGENT_ENDPOINT=http://... # Alternative: external AI endpoint
|
||||
```
|
||||
|
||||
**Persistence (v2.1+):**
|
||||
```bash
|
||||
SQLITE_DB_PATH=/var/lib/yaze-collab.db # File-based persistence (default: :memory:)
|
||||
```
|
||||
|
||||
**Rate Limiting:**
|
||||
```bash
|
||||
RATE_LIMIT_MAX_MESSAGES=100 # Messages per minute per IP (default: 100)
|
||||
JOIN_LIMIT_MAX_ATTEMPTS=10 # Join/host attempts per minute per IP (default: 10)
|
||||
```
|
||||
|
||||
**Admin API:**
|
||||
```bash
|
||||
ADMIN_API_KEY=your_secret_key # Protect admin endpoints (optional)
|
||||
```
|
||||
|
||||
4. Run with PM2 for process management:
|
||||
```bash
|
||||
npm install -g pm2
|
||||
pm2 start server.js --name yaze-collab --env production
|
||||
pm2 save
|
||||
```
|
||||
|
||||
5. Add TLS reverse proxy (recommended)
|
||||
|
||||
**nginx example (translate `wss://` to local `ws://localhost:8765`):**
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name collab.yourdomain.com;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://localhost:8765;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 86400;
|
||||
proxy_send_timeout 86400;
|
||||
}
|
||||
}
|
||||
```
|
||||
**Caddy example:**
|
||||
```caddy
|
||||
collab.yourdomain.com {
|
||||
reverse_proxy /ws localhost:8765 {
|
||||
header_up Upgrade {>Upgrade}
|
||||
header_up Connection {>Connection}
|
||||
}
|
||||
tls you@example.com
|
||||
}
|
||||
```
|
||||
**Why:** avoids mixed-content errors in browsers, encrypts ROM diffs/chat/passwords, and centralizes cert/ALPN handling.
|
||||
|
||||
### Platform-as-a-Service
|
||||
|
||||
| Platform | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| **Railway** | Easy deploy, free tier | Limited free hours |
|
||||
| **Render** | Free tier, auto-deploy | Spins down on inactivity |
|
||||
| **Fly.io** | Global edge, generous free | More complex setup |
|
||||
| **Deno Deploy** | Free, edge deployment | Deno runtime only |
|
||||
| **Cloudflare Workers** | Free tier, global edge | Durable Objects cost |
|
||||
|
||||
---
|
||||
|
||||
## Client Configuration
|
||||
|
||||
### Method 1: JavaScript Configuration (Recommended)
|
||||
|
||||
Add before loading yaze:
|
||||
```html
|
||||
<script>
|
||||
window.YAZE_CONFIG = {
|
||||
collaboration: {
|
||||
serverUrl: 'ws://org.halext.org:8765' // use wss:// if you front with TLS
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### Method 2: Meta Tag
|
||||
|
||||
```html
|
||||
<meta name="yaze-collab-server" content="ws://org.halext.org:8765">
|
||||
```
|
||||
|
||||
### Method 3: Runtime Configuration
|
||||
|
||||
In your integration code:
|
||||
```cpp
|
||||
auto& collab = WasmCollaboration::GetInstance();
|
||||
collab.SetWebSocketUrl("ws://org.halext.org:8765");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Transport: terminate TLS in front of the server (`wss://`). The halext deployment currently runs plain `ws://org.halext.org:8765`; add nginx/Caddy to secure it.
|
||||
- Built-in guardrails: 100 messages/min/IP, 10 join/host attempts/min/IP, 5 MB ROM diff limit, 10 MB snapshot limit, 30s heartbeat that drops dead sockets.
|
||||
- Passwords: supported on both protocols (`password` for WASM, `session_password` for full sessions). Hashing is SHA-256 on the server side.
|
||||
- AI: only enabled when `ENABLE_AI_AGENT=true` **and** `GEMINI_API_KEY` or `AI_AGENT_ENDPOINT` is set. Leave unset to disable AI endpoints.
|
||||
- Persistence: halext uses in-memory SQLite (sessions reset on restart). For durability, run sqlite on disk (`SQLITE_DB_PATH=/var/lib/yaze-collab.db`) or swap to Postgres/MySQL with a lightweight adapter. Add backups/retention for audit.
|
||||
- Authentication: front the service with an auth gateway if you need verified identities; yaze-server does not issue tokens itself.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Handshake issues:** Match the scheme to the deployment. halext runs `ws://org.halext.org:8765`; use `wss://` only when you have a TLS proxy forwarding `Upgrade` headers.
|
||||
- **Health checks:** `curl http://org.halext.org:8765/health` and `/metrics` to confirm the service is live.
|
||||
- **TLS errors:** If you front with nginx/Caddy, ensure HTTP/1.1, `Upgrade`/`Connection` headers, and a valid certificate. Remove `wss://` if you have not enabled TLS.
|
||||
- **Disconnects/rate limits:** Server sends heartbeats every 30s and enforces limits. Check `pm2 logs yaze-collab` on halext for details.
|
||||
- **Performance:** Keep diffs under 5 MB, snapshots under 10 MB, and batch cursor updates on the client. Enable compression at the proxy if needed.
|
||||
|
||||
---
|
||||
|
||||
## Operations Playbook (halext-friendly)
|
||||
|
||||
- **Status:** `curl http://org.halext.org:8765/health` and `/metrics`; add `/metrics` scrape to Prometheus if available.
|
||||
- **Logs:** `pm2 logs yaze-collab` (rotate externally if needed).
|
||||
- **Restart/Redeploy:** `pm2 restart yaze-collab`; `pm2 list` to verify uptime.
|
||||
- **Admin actions:** Use Admin API (see below) or block abusive IPs at the proxy.
|
||||
- **Scaling path:** add Redis pub/sub for multi-instance broadcast; place proxy in front with sticky room affinity if you shard.
|
||||
|
||||
---
|
||||
|
||||
## Admin API (v2.1+)
|
||||
|
||||
Protected endpoints for server administration. Set `ADMIN_API_KEY` to require authentication.
|
||||
|
||||
### Authentication
|
||||
Include the key in requests:
|
||||
```bash
|
||||
curl -H "X-Admin-Key: your_secret_key" http://localhost:8765/admin/sessions
|
||||
# Or as query param: http://localhost:8765/admin/sessions?admin_key=your_secret_key
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
**List all sessions/rooms:**
|
||||
```bash
|
||||
GET /admin/sessions
|
||||
# Response: { sessions: [...], wasm_rooms: [...], total_connections: N }
|
||||
```
|
||||
|
||||
**List users in a session:**
|
||||
```bash
|
||||
GET /admin/sessions/:code/users
|
||||
# Response: { code: "ABC123", type: "full"|"wasm", users: [...] }
|
||||
```
|
||||
|
||||
**Close a session (kick all users):**
|
||||
```bash
|
||||
DELETE /admin/sessions/:code
|
||||
# Body: { "reason": "Maintenance" } (optional)
|
||||
# Response: { success: true, code: "ABC123", reason: "..." }
|
||||
```
|
||||
|
||||
**Kick a specific user:**
|
||||
```bash
|
||||
DELETE /admin/sessions/:code/users/:userId
|
||||
# Body: { "reason": "Violation of rules" } (optional)
|
||||
# Response: { success: true, code: "ABC123", userId: "user-123", reason: "..." }
|
||||
```
|
||||
|
||||
**Broadcast message to session:**
|
||||
```bash
|
||||
POST /admin/sessions/:code/broadcast
|
||||
# Body: { "message": "Server maintenance in 5 minutes", "message_type": "admin" }
|
||||
# Response: { success: true, code: "ABC123", recipients: N }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Halext TLS Deployment Guide
|
||||
|
||||
Step-by-step guide to add WSS (TLS) to the halext deployment.
|
||||
|
||||
### Prerequisites
|
||||
- SSH access to halext-server
|
||||
- Domain DNS pointing to server (e.g., `collab.halext.org` or use existing `org.halext.org`)
|
||||
- Certbot or existing SSL certificates
|
||||
|
||||
### Option A: nginx reverse proxy
|
||||
|
||||
1. **Install nginx (if not present):**
|
||||
```bash
|
||||
sudo apt update && sudo apt install nginx certbot python3-certbot-nginx
|
||||
```
|
||||
|
||||
2. **Create nginx config:**
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/yaze-collab
|
||||
```
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name collab.halext.org; # or org.halext.org
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name collab.halext.org;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/collab.halext.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/collab.halext.org/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
# WebSocket proxy
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8765;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
proxy_send_timeout 86400;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Enable site and get certificate:**
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/yaze-collab /etc/nginx/sites-enabled/
|
||||
sudo certbot --nginx -d collab.halext.org
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
4. **Update client config to use WSS:**
|
||||
```javascript
|
||||
window.YAZE_CONFIG = {
|
||||
collaboration: { serverUrl: 'wss://collab.halext.org' }
|
||||
};
|
||||
```
|
||||
|
||||
### Option B: Caddy (simpler, auto-TLS)
|
||||
|
||||
1. **Install Caddy:**
|
||||
```bash
|
||||
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
||||
sudo apt update && sudo apt install caddy
|
||||
```
|
||||
|
||||
2. **Create Caddyfile:**
|
||||
```bash
|
||||
sudo nano /etc/caddy/Caddyfile
|
||||
```
|
||||
```caddy
|
||||
collab.halext.org {
|
||||
reverse_proxy localhost:8765
|
||||
# Caddy handles TLS automatically
|
||||
}
|
||||
```
|
||||
|
||||
3. **Reload Caddy:**
|
||||
```bash
|
||||
sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
### Verify TLS is working
|
||||
|
||||
```bash
|
||||
# Check health endpoint shows TLS detected
|
||||
curl -s https://collab.halext.org/health | jq '.tls'
|
||||
# Expected: { "detected": true, "note": "Request via TLS proxy" }
|
||||
|
||||
# Test WebSocket connection
|
||||
wscat -c wss://collab.halext.org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PM2 Ecosystem File
|
||||
|
||||
For more control, use a PM2 ecosystem file:
|
||||
|
||||
**ecosystem.config.js:**
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'yaze-collab',
|
||||
script: 'server.js',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '500M',
|
||||
env: {
|
||||
NODE_ENV: 'development',
|
||||
PORT: 8765
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 8765,
|
||||
SQLITE_DB_PATH: '/var/lib/yaze-collab/sessions.db',
|
||||
ENABLE_AI_AGENT: 'true',
|
||||
// GEMINI_API_KEY: 'your_key_here',
|
||||
// ADMIN_API_KEY: 'your_admin_key'
|
||||
}
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
pm2 start ecosystem.config.js --env production
|
||||
pm2 save
|
||||
pm2 startup # Enable startup on boot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Persistence & Backup
|
||||
|
||||
### Enable file-based persistence
|
||||
```bash
|
||||
# Create data directory
|
||||
sudo mkdir -p /var/lib/yaze-collab
|
||||
sudo chown $(whoami) /var/lib/yaze-collab
|
||||
|
||||
# Set environment variable
|
||||
export SQLITE_DB_PATH=/var/lib/yaze-collab/sessions.db
|
||||
pm2 restart yaze-collab --update-env
|
||||
```
|
||||
|
||||
### Backup strategy
|
||||
```bash
|
||||
# Daily backup cron job
|
||||
echo "0 3 * * * sqlite3 /var/lib/yaze-collab/sessions.db '.backup /backups/yaze-collab-$(date +%Y%m%d).db'" | crontab -
|
||||
|
||||
# Retain last 7 days
|
||||
echo "0 4 * * * find /backups -name 'yaze-collab-*.db' -mtime +7 -delete" | crontab -e
|
||||
```
|
||||
|
||||
### Health endpoint (v2.1+)
|
||||
The `/health` endpoint now reports persistence status:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"version": "2.1",
|
||||
"persistence": {
|
||||
"type": "file",
|
||||
"path": "/var/lib/yaze-collab/sessions.db"
|
||||
},
|
||||
"ai": {
|
||||
"enabled": true,
|
||||
"configured": true,
|
||||
"provider": "gemini"
|
||||
},
|
||||
"tls": {
|
||||
"detected": true,
|
||||
"note": "Request via TLS proxy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Client UX hints
|
||||
|
||||
- Surface server status in the web UI by calling `/health` once on load and showing: server reachable, AI enabled/disabled (from health/metrics), and whether TLS is in use.
|
||||
- Default `window.YAZE_CONFIG.collaboration.serverUrl` to `wss://collab.yourdomain.com/ws` when a TLS proxy is present; fall back to `ws://localhost:8765` for local dev.
|
||||
- Show a small banner when AI is disabled or when the connection is downgraded to plain WS to set user expectations.
|
||||
|
||||
---
|
||||
|
||||
## Example: Complete Docker Deployment
|
||||
|
||||
**Dockerfile:**
|
||||
```dockerfile
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
COPY server.js .
|
||||
EXPOSE 8765
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
collab:
|
||||
build: .
|
||||
ports:
|
||||
- "8765:8765"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENABLE_AI_AGENT=true
|
||||
# Uncomment one of the following if AI responses are desired
|
||||
# - GEMINI_API_KEY=your_api_key
|
||||
# - AI_AGENT_ENDPOINT=http://ai-service:5000
|
||||
```
|
||||
|
||||
Deploy:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Server
|
||||
|
||||
Use wscat to test:
|
||||
```bash
|
||||
npm install -g wscat
|
||||
wscat -c ws://org.halext.org:8765
|
||||
|
||||
# Send create message
|
||||
{"type":"create","room":"TEST01","name":"Test","user":"TestUser","user_id":"test-123","color":"#FF0000"}
|
||||
|
||||
# Check response
|
||||
# < {"type":"create_response","success":true,"session_name":"Test"}
|
||||
|
||||
# Test full session protocol (AI disabled)
|
||||
{"type":"host_session","payload":{"session_name":"DocsCheck","username":"tester","ai_enabled":false}}
|
||||
```
|
||||
|
||||
Health check:
|
||||
```bash
|
||||
curl http://org.halext.org:8765/health
|
||||
curl http://org.halext.org:8765/metrics
|
||||
```
|
||||
|
||||
Or use the browser console on your yaze deployment:
|
||||
```javascript
|
||||
window.YAZE_CONFIG = {
|
||||
collaboration: { serverUrl: 'ws://org.halext.org:8765' }
|
||||
};
|
||||
// Then use the collaboration UI in yaze
|
||||
```
|
||||
@@ -11,6 +11,16 @@ YAZE includes two primary AI assistance modes:
|
||||
|
||||
Both modes use the same underlying AI service (Ollama or Gemini) and tool infrastructure, but target different workflows.
|
||||
|
||||
## Choosing the right agent persona
|
||||
- Personas live in `.claude/agents/<agent-id>.md`; open the matching file as your system prompt before a session (available to all agents, not just Claude).
|
||||
- **ai-infra-architect**: AI/agent infra, MCP/gRPC, z3ed tooling, model plumbing.
|
||||
- **backend-infra-engineer**: Build/packaging/toolchains, CI reliability, release plumbing.
|
||||
- **imgui-frontend-engineer**: ImGui/editor UI, renderer/backends, canvas/docking UX.
|
||||
- **snes-emulator-expert**: Emulator core (CPU/APU/PPU), performance/accuracy/debugging.
|
||||
- **zelda3-hacking-expert**: Gameplay/ROM logic, data formats, hacking workflows.
|
||||
- **test-infrastructure-expert**: Test harnesses, CTest/gMock infra, flake/bloat triage.
|
||||
- **docs-janitor**: Docs/process hygiene, onboarding, checklists.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Build Requirements
|
||||
@@ -78,7 +88,7 @@ cmake --build --preset mac-ai --target z3ed
|
||||
|
||||
```bash
|
||||
# You encounter a compilation error
|
||||
cmake --build build_ai
|
||||
cmake --build build
|
||||
# [ERROR] src/app/gfx/snes_color.cc:45: error: 'Arena' was not declared
|
||||
|
||||
# Use z3ed to analyze and suggest fixes
|
||||
@@ -141,7 +151,7 @@ The agent automatically analyzes compilation failures:
|
||||
|
||||
```bash
|
||||
z3ed agent chat --rom zelda3.sfc
|
||||
> cmake --build build_ai failed with:
|
||||
> cmake --build build failed with:
|
||||
> error: 'gfx::Arena' has not been declared in snes_color.cc:45
|
||||
|
||||
# AI will:
|
||||
|
||||
@@ -2,25 +2,33 @@
|
||||
|
||||
This guide summarizes the architecture and implementation standards used across the editor codebase.
|
||||
|
||||
## Editor Status (October 2025)
|
||||
## Editor Status (November 2025)
|
||||
|
||||
| Editor | State | Notes |
|
||||
|-------------------|--------------|-------|
|
||||
| Overworld | Stable | Full feature set; continue regression testing after palette fixes. |
|
||||
| Message | Stable | Re-test rendering after recent palette work. |
|
||||
| Emulator | Stable | UI and core subsystems aligned with production builds. |
|
||||
| Palette | Stable | Serves as the source of truth for palette helpers. |
|
||||
| Assembly | Stable | No outstanding refactors. |
|
||||
| Dungeon | Experimental | Requires thorough manual coverage before release. |
|
||||
| Graphics | Experimental | Large rendering changes in flight; validate texture pipeline. |
|
||||
| Sprite | Experimental | UI patterns still migrating to the new card system. |
|
||||
| Editor | State | Panels | Notes |
|
||||
|-------------------|--------------|--------|-------|
|
||||
| Overworld | Stable | 8 | Full feature set with tile16 editor, scratch space. |
|
||||
| Message | Stable | 4 | Message list, editor, font atlas, dictionary panels. |
|
||||
| Emulator | Stable | 10 | CPU, PPU, Memory debuggers; AI agent integration. |
|
||||
| Palette | Stable | 11 | Source of truth for palette helpers. |
|
||||
| Assembly | Stable | 2 | File browser and editor panels. |
|
||||
| Dungeon | Stable | 8 | Room selector, matrix, graphics, object editor. |
|
||||
| Graphics | Stable | 4 | Sheet editor, browser, player animations. |
|
||||
| Sprite | Stable | 2 | Vanilla and custom sprite panels. |
|
||||
| Screen | Stable | 5 | Dungeon maps, inventory, title screen, etc. |
|
||||
| Music | Experimental | 3 | Tracker, instrument editor, assembly view. |
|
||||
|
||||
### Screen Editor Notes
|
||||
### Recent Improvements (v0.3.9)
|
||||
|
||||
- **Title screen**: Vanilla ROM tilemap parsing remains broken. Implement a DMA
|
||||
parser and confirm the welcome screen renders before enabling painting.
|
||||
- **Overworld map**: Mode 7 tiling, palette switching, and custom map import/export are in place. The next milestone is faster tile painting.
|
||||
- **Dungeon map**: Rendering is wired up; tile painting and ROM write-back are still pending.
|
||||
- **EditorManager Refactoring**: 90% feature parity with 44% code reduction
|
||||
- **Panel-Based UI**: All 34 editor panels (formerly cards) with X-button close, multi-session support
|
||||
- **SDL3 Backend Infrastructure**: 17 abstraction files for future migration
|
||||
- **WASM Web Port**: Real-time collaboration via WebSocket
|
||||
- **AI Agent Tools**: Phases 1-4 complete (meta-tools, schemas, validation)
|
||||
|
||||
### Known Issues
|
||||
|
||||
- **Dungeon object rendering**: Regression with object visibility
|
||||
- **ZSOW v3 palettes**: Large-area palette issues being investigated
|
||||
|
||||
## 1. Core Architectural Patterns
|
||||
|
||||
@@ -139,18 +147,18 @@ Google-style C++23 guidelines while accommodating ROM hacking patterns.
|
||||
|
||||
### 5.1. Quick Debugging with Startup Flags
|
||||
|
||||
To accelerate your debugging workflow, use command-line flags to jump directly to specific editors and open relevant UI cards:
|
||||
To accelerate your debugging workflow, use command-line flags to jump directly to specific editors and open relevant UI panels:
|
||||
|
||||
```bash
|
||||
# Quick dungeon room testing
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0"
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0"
|
||||
|
||||
# Compare multiple rooms
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room 1,Room 105"
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0,Room 1,Room 105"
|
||||
|
||||
# Full dungeon workspace
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon \
|
||||
--cards="Rooms List,Room Matrix,Object Editor,Palette Editor"
|
||||
--open_panels="Rooms List,Room Matrix,Object Editor,Palette Editor"
|
||||
|
||||
# Enable debug logging
|
||||
./yaze --debug --log_file=debug.log --rom_file=zelda3.sfc --editor=Dungeon
|
||||
@@ -158,9 +166,9 @@ To accelerate your debugging workflow, use command-line flags to jump directly t
|
||||
|
||||
**Available Editors**: Assembly, Dungeon, Graphics, Music, Overworld, Palette, Screen, Sprite, Message, Hex, Agent, Settings
|
||||
|
||||
**Dungeon Editor Cards**: Rooms List, Room Matrix, Entrances List, Room Graphics, Object Editor, Palette Editor, Room N (where N is room ID 0-319)
|
||||
**Dungeon Editor Panels**: Rooms List, Room Matrix, Entrances List, Room Graphics, Object Editor, Palette Editor, Room N (where N is room ID 0-319)
|
||||
|
||||
See [debugging-startup-flags.md](debugging-startup-flags.md) for complete documentation.
|
||||
See [Startup Debugging Flags](debug-flags.md) for complete documentation, including panel visibility overrides (`--startup_welcome/--startup_dashboard/--startup_sidebar`).
|
||||
|
||||
### 5.2. Testing Strategies
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# YAZE Startup Debugging Flags
|
||||
|
||||
This guide explains how to use command-line flags to quickly open specific editors and cards during development for faster debugging workflows.
|
||||
This guide explains how to use command-line flags to quickly open specific editors and panels during development for faster debugging workflows.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -24,6 +24,13 @@ Enable debug logging with verbose output.
|
||||
./yaze --debug --log_file=yaze_debug.log
|
||||
```
|
||||
|
||||
### `--log_level`, `--log_categories`, `--log_to_console`
|
||||
Control verbosity and filter by subsystem. Categories can be allowlisted or blocked by prefixing with `-`:
|
||||
|
||||
```bash
|
||||
./yaze --log_level=debug --log_categories="EditorManager,-Audio" --log_to_console
|
||||
```
|
||||
|
||||
### `--editor`
|
||||
Open a specific editor on startup. This saves time by skipping manual navigation through the UI.
|
||||
|
||||
@@ -46,10 +53,12 @@ Open a specific editor on startup. This saves time by skipping manual navigation
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon
|
||||
```
|
||||
|
||||
### `--cards`
|
||||
Open specific cards/panels within an editor. Most useful with the Dungeon editor.
|
||||
### `--open_panels`
|
||||
Open specific panels within an editor. Matching is case-insensitive and accepts either display names
|
||||
or stable panel IDs (e.g., `dungeon.room_list`, `emulator.cpu_debugger`). `Room N` tokens will open
|
||||
the corresponding dungeon room card.
|
||||
|
||||
**Dungeon Editor Cards:**
|
||||
**Dungeon Editor Panels:**
|
||||
- `Rooms List` - Shows the list of all dungeon rooms
|
||||
- `Room Matrix` - Shows the dungeon room layout matrix
|
||||
- `Entrances List` - Shows dungeon entrance configurations
|
||||
@@ -60,7 +69,15 @@ Open specific cards/panels within an editor. Most useful with the Dungeon editor
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Rooms List,Room 0"
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Rooms List,Room 0"
|
||||
```
|
||||
|
||||
### `--startup_welcome`, `--startup_dashboard`, `--startup_sidebar`
|
||||
Control startup chrome visibility. Each accepts `auto`, `show`, or `hide`:
|
||||
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --editor=Overworld \
|
||||
--startup_welcome=hide --startup_dashboard=show --startup_sidebar=hide
|
||||
```
|
||||
|
||||
## Common Debugging Scenarios
|
||||
@@ -69,14 +86,14 @@ Open specific cards/panels within an editor. Most useful with the Dungeon editor
|
||||
Open a specific dungeon room for testing:
|
||||
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room Graphics"
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0,Room Graphics"
|
||||
```
|
||||
|
||||
### 2. Multiple Room Comparison
|
||||
Compare multiple rooms side-by-side:
|
||||
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room 1,Room 105"
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0,Room 1,Room 105"
|
||||
```
|
||||
|
||||
### 3. Full Dungeon Editor Workspace
|
||||
@@ -84,7 +101,7 @@ Open all dungeon editor tools:
|
||||
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon \
|
||||
--cards="Rooms List,Room Matrix,Room Graphics,Object Editor,Palette Editor"
|
||||
--open_panels="Rooms List,Room Matrix,Room Graphics,Object Editor,Palette Editor"
|
||||
```
|
||||
|
||||
### 4. Debug Mode with Logging
|
||||
@@ -92,7 +109,7 @@ Enable full debug output while working:
|
||||
|
||||
```bash
|
||||
./yaze --rom_file=zelda3.sfc --debug --log_file=debug.log \
|
||||
--editor=Dungeon --cards="Room 0"
|
||||
--editor=Dungeon --open_panels="Room 0"
|
||||
```
|
||||
|
||||
### 5. Quick Overworld Editing
|
||||
@@ -123,16 +140,15 @@ All flags can be combined for powerful debugging setups:
|
||||
--debug \
|
||||
--log_file=room_105_debug.log \
|
||||
--editor=Dungeon \
|
||||
--cards="Room 105,Room Graphics,Palette Editor,Object Editor"
|
||||
--open_panels="Room 105,Room Graphics,Palette Editor,Object Editor"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Card names are case-sensitive and must match exactly
|
||||
- Use quotes around comma-separated card lists
|
||||
- Invalid editor or card names will be logged as warnings but won't crash the application
|
||||
- The `--cards` flag is currently only implemented for the Dungeon editor
|
||||
- Room IDs range from 0-319 in the vanilla game
|
||||
- Panel tokens are matched case-insensitively against IDs and display names
|
||||
- Use quotes around comma-separated panel lists
|
||||
- Invalid editor or panel names will be logged as warnings but won't crash the application
|
||||
- `Room N` shortcuts use the dungeon room ID range (0-319 in vanilla)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -141,11 +157,10 @@ All flags can be combined for powerful debugging setups:
|
||||
- Verify ROM loaded successfully
|
||||
- Check log output with `--debug`
|
||||
|
||||
**Cards don't appear:**
|
||||
**Panels don't appear:**
|
||||
- Ensure editor is set (e.g., `--editor=Dungeon`)
|
||||
- Check card name spelling
|
||||
- Some cards require a loaded ROM
|
||||
|
||||
**Want to add more card support?**
|
||||
See `EditorManager::OpenEditorAndCardsFromFlags()` in `src/app/editor/editor_manager.cc`
|
||||
- Check panel name spelling
|
||||
- Some panels require a loaded ROM
|
||||
|
||||
**Want to add more panel support?**
|
||||
See `EditorManager::OpenEditorAndPanelsFromFlags()` in `src/app/editor/editor_manager.cc`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# E5 - Debugging and Testing Guide
|
||||
|
||||
**Last Updated**: October 9, 2025
|
||||
**Last Updated**: December 5, 2025
|
||||
**Status**: Active
|
||||
|
||||
This document provides a comprehensive guide to debugging and testing the `yaze` application. It covers strategies for developers and provides the necessary information for AI agents to interact with, test, and validate the application.
|
||||
@@ -30,20 +30,19 @@ Categories allow you to filter logs to focus on a specific subsystem. Common cat
|
||||
|
||||
You can control logging behavior using command-line flags when launching `yaze` or `yaze_test`.
|
||||
|
||||
- **Enable Verbose Debug Logging**:
|
||||
- **Set Log Level & Categories** (allowlist or blocklist by prefixing with `-`; tokens are trimmed and case-sensitive):
|
||||
```bash
|
||||
./build/bin/yaze --debug
|
||||
./build/bin/yaze --log_level=debug --log_categories="OverworldEditor,-Audio"
|
||||
```
|
||||
|
||||
- **Log to a File**:
|
||||
- **Log to a File (and mirror to console if needed)**:
|
||||
```bash
|
||||
./build/bin/yaze --log_file=yaze_debug.log
|
||||
./build/bin/yaze --log_file=yaze_debug.log --log_to_console
|
||||
```
|
||||
|
||||
- **Filter by Category**:
|
||||
- **Enable Verbose Debug Logging** (forces console logging and `debug` level):
|
||||
```bash
|
||||
# Only show logs from the APU and CPU emulator components
|
||||
./build/bin/yaze_emu --emu_debug_apu=true --emu_debug_cpu=true
|
||||
./build/bin/yaze --debug --log_file=yaze_debug.log
|
||||
```
|
||||
|
||||
**Best Practice**: When debugging a specific component, add detailed `LOG_DEBUG` statements with a unique category. Then, run `yaze` with the appropriate flags to isolate the output.
|
||||
@@ -66,35 +65,33 @@ The `yaze` ecosystem provides several executables and flags to streamline testin
|
||||
./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness
|
||||
```
|
||||
|
||||
- **Open a Specific Editor and Cards**: To quickly test a specific editor and its components, use the `--editor` and `--cards` flags. This is especially useful for debugging complex UIs like the Dungeon Editor.
|
||||
- **Open a Specific Editor and Panels**: Use `--editor` and `--open_panels` (panel IDs or display names, comma-separated, case-insensitive) to land exactly where you need. Combine with startup visibility flags to hide chrome for automation:
|
||||
```bash
|
||||
# Open the Dungeon Editor with the Room Matrix and two specific room cards
|
||||
./build/bin/yaze --rom_file zelda3.sfc --editor=Dungeon --cards="Room Matrix,Room 0,Room 105"
|
||||
|
||||
# Available editors: Assembly, Dungeon, Graphics, Music, Overworld, Palette,
|
||||
# Screen, Sprite, Message, Hex, Agent, Settings
|
||||
|
||||
# Dungeon editor cards: Rooms List, Room Matrix, Entrances List, Room Graphics,
|
||||
# Object Editor, Palette Editor, Room N (where N is room ID)
|
||||
# Open the Dungeon editor with a couple panels pre-visible
|
||||
./build/bin/yaze --rom_file zelda3.sfc --editor=Dungeon --open_panels="dungeon.room_list,Room 105"
|
||||
|
||||
# You can also hide startup chrome for automation runs
|
||||
./build/bin/yaze --rom_file zelda3.sfc --editor=Overworld \
|
||||
--open_panels="overworld.map_canvas" --startup_welcome=hide --startup_dashboard=hide
|
||||
```
|
||||
|
||||
**Quick Examples**:
|
||||
```bash
|
||||
# Fast dungeon room testing
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0"
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0"
|
||||
|
||||
# Compare multiple rooms side-by-side
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room 1,Room 105"
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0,Room 1,Room 105"
|
||||
|
||||
# Full dungeon workspace with all tools
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon \
|
||||
--cards="Rooms List,Room Matrix,Object Editor,Palette Editor"
|
||||
--open_panels="Rooms List,Room Matrix,Object Editor,Palette Editor"
|
||||
|
||||
# Jump straight to overworld editing
|
||||
./build/bin/yaze --rom_file=zelda3.sfc --editor=Overworld
|
||||
```
|
||||
|
||||
For a complete reference, see [docs/debugging-startup-flags.md](debugging-startup-flags.md).
|
||||
For a complete reference, see [Startup Debugging Flags](debug-flags.md).
|
||||
|
||||
### Running Automated C++ Tests
|
||||
|
||||
@@ -102,22 +99,22 @@ The `yaze_test` executable is used to run the project's suite of unit, integrati
|
||||
|
||||
- **Run All Tests**:
|
||||
```bash
|
||||
./build_ai/bin/yaze_test
|
||||
./build/bin/yaze_test
|
||||
```
|
||||
|
||||
- **Run Specific Categories**:
|
||||
```bash
|
||||
# Run only fast, dependency-free unit tests
|
||||
./build_ai/bin/yaze_test --unit
|
||||
./build/bin/yaze_test --unit
|
||||
|
||||
# Run tests that require a ROM file
|
||||
./build_ai/bin/yaze_test --rom-dependent --rom-path /path/to/zelda3.sfc
|
||||
./build/bin/yaze_test --rom-dependent --rom-path /path/to/zelda3.sfc
|
||||
```
|
||||
|
||||
- **Run GUI-based E2E Tests**:
|
||||
```bash
|
||||
# Run E2E tests and watch the GUI interactions
|
||||
./build_ai/bin/yaze_test --e2e --show-gui
|
||||
./build/bin/yaze_test --e2e --show-gui
|
||||
```
|
||||
|
||||
### Inspecting ROMs with `z3ed`
|
||||
@@ -168,7 +165,7 @@ This will return a list of widget IDs (e.g., `Dungeon/Canvas/Map`) that can be u
|
||||
|
||||
**Tip**: You can also launch `yaze` with the `--editor` flag to automatically open a specific editor:
|
||||
```bash
|
||||
./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness --editor=Dungeon --cards="Room 0"
|
||||
./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness --editor=Dungeon --open_panels="Room 0"
|
||||
```
|
||||
|
||||
#### Step 3: Record or Write a Test Script
|
||||
@@ -193,7 +190,7 @@ An agent can either generate a test script from scratch or use a pre-recorded on
|
||||
```bash
|
||||
# Start yaze with the room already open
|
||||
./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness \
|
||||
--editor=Dungeon --cards="Room 105"
|
||||
--editor=Dungeon --open_panels="Room 105"
|
||||
|
||||
# Then your test script just needs to validate the state
|
||||
{"action": "assert_visible", "target": "Room Card 105"}
|
||||
|
||||
@@ -1248,8 +1248,8 @@ class MusicEditor {
|
||||
|
||||
```bash
|
||||
cd /Users/scawful/Code/yaze
|
||||
cmake --build build_ai --target yaze -j12
|
||||
./build_ai/bin/yaze.app/Contents/MacOS/yaze
|
||||
cmake --build build --target yaze -j12
|
||||
./build/bin/yaze.app/Contents/MacOS/yaze
|
||||
```
|
||||
|
||||
### Platform-Specific
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# G5 - GUI Consistency and Card-Based Architecture Guide
|
||||
# G5 - GUI Consistency and Panel-Based Architecture Guide
|
||||
|
||||
> Note: The project is migrating from **Card** terminology to **Panel**
|
||||
> (`PanelWindow`, `PanelManager`, `PanelDescriptor`). This guide still shows
|
||||
> legacy names; mentally substitute Panel for Card while Phase 2 lands.
|
||||
|
||||
This guide establishes standards for GUI consistency across all yaze editors, focusing on the modern card-based architecture, theming system, and layout patterns.
|
||||
|
||||
@@ -11,13 +15,14 @@ This guide establishes standards for GUI consistency across all yaze editors, fo
|
||||
5. [GUI Library Architecture](#5-gui-library-architecture)
|
||||
6. [Themed Widget System](#6-themed-widget-system)
|
||||
7. [Begin/End Patterns](#7-beginend-patterns)
|
||||
8. [Currently Integrated Editors](#8-currently-integrated-editors)
|
||||
9. [Layout Helpers](#9-layout-helpers)
|
||||
10. [Workspace Management](#10-workspace-management)
|
||||
11. [Future Editor Improvements](#11-future-editor-improvements)
|
||||
12. [Migration Checklist](#12-migration-checklist)
|
||||
13. [Code Examples](#13-code-examples)
|
||||
14. [Common Pitfalls](#14-common-pitfalls)
|
||||
8. [Avoiding Duplicate Rendering](#8-avoiding-duplicate-rendering)
|
||||
9. [Currently Integrated Editors](#9-currently-integrated-editors)
|
||||
10. [Layout Helpers](#10-layout-helpers)
|
||||
11. [Workspace Management](#11-workspace-management)
|
||||
12. [Future Editor Improvements](#12-future-editor-improvements)
|
||||
13. [Migration Checklist](#13-migration-checklist)
|
||||
14. [Code Examples](#14-code-examples)
|
||||
15. [Common Pitfalls](#15-common-pitfalls)
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
@@ -532,17 +537,43 @@ if (ImGui::BeginTable("##MyTable", 3, ImGuiTableFlags_Borders)) {
|
||||
}
|
||||
```
|
||||
|
||||
**Child Window Pattern:**
|
||||
**Child Window Pattern (CRITICAL):**
|
||||
|
||||
⚠️ **This is the most commonly misused pattern.** Unlike tables, `EndChild()` must ALWAYS be called after `BeginChild()`, regardless of the return value.
|
||||
|
||||
```cpp
|
||||
// ✅ CORRECT: EndChild OUTSIDE the if block
|
||||
if (ImGui::BeginChild("##ScrollRegion", ImVec2(0, 200), true)) {
|
||||
// Scrollable content
|
||||
// Scrollable content - only drawn when visible
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ImGui::Text("Item %d", i);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndChild(); // ALWAYS called, even if BeginChild returned false
|
||||
|
||||
// ❌ WRONG: EndChild INSIDE the if block - causes state corruption!
|
||||
if (ImGui::BeginChild("##ScrollRegion", ImVec2(0, 200), true)) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ImGui::Text("Item %d", i);
|
||||
}
|
||||
ImGui::EndChild(); // BUG: Not called when BeginChild returns false!
|
||||
}
|
||||
```
|
||||
|
||||
**Why this matters:** When `BeginChild()` returns false (child window is clipped or not visible), ImGui still expects `EndChild()` to be called to properly clean up internal state. Failing to call it corrupts ImGui's window stack, which can cause seemingly unrelated errors like table assertions or missing UI elements.
|
||||
|
||||
**Pattern Comparison:**
|
||||
|
||||
| Function | Call End when Begin returns false? |
|
||||
|----------|-----------------------------------|
|
||||
| `BeginChild()` / `EndChild()` | ✅ **YES - ALWAYS** |
|
||||
| `Begin()` / `End()` (windows) | ✅ **YES - ALWAYS** |
|
||||
| `BeginTable()` / `EndTable()` | ❌ **NO - only if Begin returned true** |
|
||||
| `BeginTabBar()` / `EndTabBar()` | ❌ **NO - only if Begin returned true** |
|
||||
| `BeginTabItem()` / `EndTabItem()` | ❌ **NO - only if Begin returned true** |
|
||||
| `BeginPopup()` / `EndPopup()` | ❌ **NO - only if Begin returned true** |
|
||||
| `BeginMenu()` / `EndMenu()` | ❌ **NO - only if Begin returned true** |
|
||||
|
||||
### Toolset Begin/End
|
||||
|
||||
```cpp
|
||||
@@ -582,7 +613,169 @@ struct ScopedCard {
|
||||
};
|
||||
```
|
||||
|
||||
## 8. Currently Integrated Editors
|
||||
## 8. Avoiding Duplicate Rendering
|
||||
|
||||
### Overview
|
||||
|
||||
Duplicate rendering occurs when the same UI content is drawn multiple times per frame. This wastes GPU resources and can cause visual glitches, flickering, or assertion errors in ImGui.
|
||||
|
||||
### Common Causes
|
||||
|
||||
1. **Calling draw functions from multiple places**
|
||||
2. **Forgetting to check visibility flags**
|
||||
3. **Shared functions called by different cards**
|
||||
4. **Rendering in callbacks that fire every frame**
|
||||
|
||||
### Pattern 1: Shared Draw Functions
|
||||
|
||||
When multiple cards need similar content, don't call the same draw function from multiple places:
|
||||
|
||||
```cpp
|
||||
// ❌ WRONG: DrawMetadata called twice when both cards are visible
|
||||
void DrawCardA() {
|
||||
gui::EditorCard card("Card A", ICON_MD_A);
|
||||
if (card.Begin()) {
|
||||
DrawCanvas();
|
||||
DrawMetadata(); // Called here...
|
||||
}
|
||||
card.End();
|
||||
}
|
||||
|
||||
void DrawCardB() {
|
||||
gui::EditorCard card("Card B", ICON_MD_B);
|
||||
if (card.Begin()) {
|
||||
DrawMetadata(); // ...AND here! Duplicate!
|
||||
DrawCanvas();
|
||||
}
|
||||
card.End();
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Each card has its own content
|
||||
void DrawCardA() {
|
||||
gui::EditorCard card("Card A", ICON_MD_A);
|
||||
if (card.Begin()) {
|
||||
DrawCanvas();
|
||||
// Card A specific content only
|
||||
}
|
||||
card.End();
|
||||
}
|
||||
|
||||
void DrawCardB() {
|
||||
gui::EditorCard card("Card B", ICON_MD_B);
|
||||
if (card.Begin()) {
|
||||
DrawMetadata(); // Card B specific content only
|
||||
}
|
||||
card.End();
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Nested Function Calls
|
||||
|
||||
Watch out for functions that call other functions with overlapping content:
|
||||
|
||||
```cpp
|
||||
// ❌ WRONG: DrawSpriteCanvas calls DrawMetadata, then DrawCustomSprites
|
||||
// also calls DrawMetadata AND DrawSpriteCanvas
|
||||
void DrawSpriteCanvas() {
|
||||
// ... canvas code ...
|
||||
DrawAnimationFrames();
|
||||
DrawCustomSpritesMetadata(); // BUG: This shouldn't be here!
|
||||
}
|
||||
|
||||
void DrawCustomSprites() {
|
||||
if (BeginTable(...)) {
|
||||
TableNextColumn();
|
||||
DrawCustomSpritesMetadata(); // First call
|
||||
TableNextColumn();
|
||||
DrawSpriteCanvas(); // Calls DrawCustomSpritesMetadata again!
|
||||
EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Each function has clear, non-overlapping responsibilities
|
||||
void DrawSpriteCanvas() {
|
||||
// ... canvas code ...
|
||||
DrawAnimationFrames();
|
||||
// NO DrawCustomSpritesMetadata here!
|
||||
}
|
||||
|
||||
void DrawCustomSprites() {
|
||||
if (BeginTable(...)) {
|
||||
TableNextColumn();
|
||||
DrawCustomSpritesMetadata(); // Only place it's called
|
||||
TableNextColumn();
|
||||
DrawSpriteCanvas(); // Just draws the canvas
|
||||
EndTable();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Expensive Per-Frame Operations
|
||||
|
||||
Don't call expensive rendering operations every frame unless necessary:
|
||||
|
||||
```cpp
|
||||
// ❌ WRONG: RenderRoomGraphics called every frame when card is visible
|
||||
void DrawRoomGraphicsCard() {
|
||||
if (graphics_card.Begin()) {
|
||||
auto& room = rooms_[current_room_id_];
|
||||
room.RenderRoomGraphics(); // Expensive! Called every frame!
|
||||
DrawRoomGfxCanvas();
|
||||
}
|
||||
graphics_card.End();
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Only render when room data changes
|
||||
void DrawRoomGraphicsCard() {
|
||||
if (graphics_card.Begin()) {
|
||||
auto& room = rooms_[current_room_id_];
|
||||
// RenderRoomGraphics is called in DrawRoomTab when room loads
|
||||
// or when data changes - NOT every frame here
|
||||
DrawRoomGfxCanvas(); // Just displays already-rendered data
|
||||
}
|
||||
graphics_card.End();
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Visibility Flag Checks
|
||||
|
||||
Always check visibility before drawing:
|
||||
|
||||
```cpp
|
||||
// ❌ WRONG: Card drawn without visibility check
|
||||
void Update() {
|
||||
DrawMyCard(); // Always called!
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Check visibility first
|
||||
void Update() {
|
||||
if (show_my_card_) {
|
||||
DrawMyCard();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Debugging Duplicate Rendering
|
||||
|
||||
1. **Add logging to draw functions:**
|
||||
```cpp
|
||||
void DrawMyContent() {
|
||||
LOG_DEBUG("UI", "DrawMyContent called"); // Count calls per frame
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
2. **Check for multiple card instances:**
|
||||
```cpp
|
||||
// Search for multiple cards with similar names
|
||||
grep -n "EditorCard.*MyCard" src/app/editor/
|
||||
```
|
||||
|
||||
3. **Trace call hierarchy:**
|
||||
- Use a debugger or add call stack logging
|
||||
- Look for functions that call each other unexpectedly
|
||||
|
||||
## 9. Currently Integrated Editors
|
||||
|
||||
The card system is integrated across 11 of 13 editors:
|
||||
|
||||
@@ -604,7 +797,7 @@ The card system is integrated across 11 of 13 editors:
|
||||
- **SettingsEditor** - Monolithic settings window, low usage frequency
|
||||
- **AgentEditor** - Complex AI agent UI, under active development
|
||||
|
||||
## 9. Layout Helpers
|
||||
## 10. Layout Helpers
|
||||
|
||||
### Overview
|
||||
|
||||
@@ -691,7 +884,7 @@ if (ImGui::BeginTable("##Grid", 2, ImGuiTableFlags_SizingStretchSame)) {
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Workspace Management
|
||||
## 11. Workspace Management
|
||||
|
||||
The workspace manager provides comprehensive window and layout operations:
|
||||
|
||||
@@ -712,7 +905,7 @@ workspace_manager_.ExecuteWorkspaceCommand(command_id);
|
||||
// Supports: w.s (show all), w.h (hide all), l.s (save layout), etc.
|
||||
```
|
||||
|
||||
## 11. Future Editor Improvements
|
||||
## 12. Future Editor Improvements
|
||||
|
||||
This section outlines remaining improvements for editors not yet fully integrated.
|
||||
|
||||
@@ -734,7 +927,7 @@ This section outlines remaining improvements for editors not yet fully integrate
|
||||
2. Integrate with EditorCardManager
|
||||
3. Add keyboard shortcuts for common operations
|
||||
|
||||
## 12. Migration Checklist
|
||||
## 13. Migration Checklist
|
||||
|
||||
Use this checklist when converting an editor to the card-based architecture:
|
||||
|
||||
@@ -811,7 +1004,7 @@ Use this checklist when converting an editor to the card-based architecture:
|
||||
- [ ] Add example to this guide if pattern is novel
|
||||
- [ ] Update CLAUDE.md if editor behavior changed significantly
|
||||
|
||||
## 13. Code Examples
|
||||
## 14. Code Examples
|
||||
|
||||
### Complete Editor Implementation
|
||||
|
||||
@@ -1093,7 +1286,7 @@ void MyEditor::DrawPropertiesCard() {
|
||||
} // namespace yaze
|
||||
```
|
||||
|
||||
## 14. Common Pitfalls
|
||||
## 15. Common Pitfalls
|
||||
|
||||
### 1. Forgetting Bidirectional Visibility Sync
|
||||
|
||||
@@ -1197,6 +1390,113 @@ if (card.Begin()) {
|
||||
card.End(); // ALWAYS called
|
||||
```
|
||||
|
||||
### 5a. BeginChild/EndChild Mismatch (Most Common Bug!)
|
||||
|
||||
**Problem:** `EndTable() call should only be done while in BeginTable() scope` assertion, or other strange ImGui crashes.
|
||||
|
||||
**Cause:** `EndChild()` placed inside the if block instead of outside.
|
||||
|
||||
**Why it's confusing:** Unlike `BeginTable()`, the `BeginChild()` function requires `EndChild()` to be called regardless of the return value. Many developers assume all Begin/End pairs work the same way.
|
||||
|
||||
**Solution:**
|
||||
```cpp
|
||||
// ❌ WRONG - EndChild inside if block
|
||||
void DrawList() {
|
||||
if (ImGui::BeginChild("##List", ImVec2(0, 0), true)) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ImGui::Selectable(items[i].c_str());
|
||||
}
|
||||
ImGui::EndChild(); // BUG! Not called when BeginChild returns false!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECT - EndChild outside if block
|
||||
void DrawList() {
|
||||
if (ImGui::BeginChild("##List", ImVec2(0, 0), true)) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ImGui::Selectable(items[i].c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndChild(); // ALWAYS called!
|
||||
}
|
||||
```
|
||||
|
||||
**Files where this bug was found and fixed:**
|
||||
- `sprite_editor.cc` - `DrawSpriteCanvas()`, `DrawSpritesList()`
|
||||
- `dungeon_editor_v2.cc` - `DrawRoomsListCard()`, `DrawEntrancesListCard()`
|
||||
- `assembly_editor.cc` - `DrawCurrentFolder()`
|
||||
- `object_editor_card.cc` - `DrawTemplatesTab()`
|
||||
|
||||
### 5b. Duplicate Rendering in Shared Functions
|
||||
|
||||
**Problem:** UI elements appear twice, performance degradation, visual glitches.
|
||||
|
||||
**Cause:** A draw function is called from multiple places, or a function calls another function that draws the same content.
|
||||
|
||||
**Example of the bug:**
|
||||
```cpp
|
||||
// DrawSpriteCanvas was calling DrawCustomSpritesMetadata
|
||||
// DrawCustomSprites was also calling DrawCustomSpritesMetadata AND DrawSpriteCanvas
|
||||
// Result: DrawCustomSpritesMetadata rendered twice!
|
||||
|
||||
void DrawSpriteCanvas() {
|
||||
// ... canvas drawing ...
|
||||
DrawAnimationFrames();
|
||||
DrawCustomSpritesMetadata(); // ❌ BUG: Also called by DrawCustomSprites!
|
||||
}
|
||||
|
||||
void DrawCustomSprites() {
|
||||
TableNextColumn();
|
||||
DrawCustomSpritesMetadata(); // First call
|
||||
TableNextColumn();
|
||||
DrawSpriteCanvas(); // ❌ Calls DrawCustomSpritesMetadata AGAIN!
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:** Each function should have clear, non-overlapping responsibilities:
|
||||
```cpp
|
||||
void DrawSpriteCanvas() {
|
||||
// ... canvas drawing ...
|
||||
DrawAnimationFrames();
|
||||
// NO DrawCustomSpritesMetadata here - it belongs in DrawCustomSprites only
|
||||
}
|
||||
|
||||
void DrawCustomSprites() {
|
||||
TableNextColumn();
|
||||
DrawCustomSpritesMetadata(); // Only place it's called
|
||||
TableNextColumn();
|
||||
DrawSpriteCanvas(); // Just draws canvas + animations
|
||||
}
|
||||
```
|
||||
|
||||
### 5c. Expensive Operations Called Every Frame
|
||||
|
||||
**Problem:** Low FPS, high CPU usage when certain cards are visible.
|
||||
|
||||
**Cause:** Expensive operations like `RenderRoomGraphics()` called unconditionally every frame.
|
||||
|
||||
**Solution:**
|
||||
```cpp
|
||||
// ❌ WRONG - Renders every frame
|
||||
void DrawRoomGraphicsCard() {
|
||||
if (graphics_card.Begin()) {
|
||||
room.RenderRoomGraphics(); // Expensive! Called 60x per second!
|
||||
DrawCanvas();
|
||||
}
|
||||
graphics_card.End();
|
||||
}
|
||||
|
||||
// ✅ CORRECT - Only render when needed
|
||||
void DrawRoomGraphicsCard() {
|
||||
if (graphics_card.Begin()) {
|
||||
// RenderRoomGraphics is called in DrawRoomTab when room loads,
|
||||
// or when room data changes - NOT every frame
|
||||
DrawCanvas(); // Just displays already-rendered data
|
||||
}
|
||||
graphics_card.End();
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Not Testing Minimize-to-Icon
|
||||
|
||||
**Problem:** Control panel can't be reopened after minimizing.
|
||||
@@ -1329,4 +1629,4 @@ For questions or suggestions about GUI consistency, please open an issue on GitH
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: October 13, 2025
|
||||
**Last Updated**: November 26, 2025
|
||||
|
||||
@@ -52,14 +52,85 @@ struct PaletteGroupMap {
|
||||
#### Structure
|
||||
- **20 dungeon palettes** in the `dungeon_main` group
|
||||
- **90 colors per palette** (full SNES palette for BG layers)
|
||||
- **ROM Location**: `kDungeonMainPalettes` (check `snes_palette.cc` for exact address)
|
||||
- **180 bytes per palette** (90 colors × 2 bytes per color)
|
||||
- **ROM Location**: `kDungeonMainPalettes = 0xDD734`
|
||||
|
||||
#### Usage
|
||||
#### Palette Lookup System (CRITICAL)
|
||||
|
||||
**IMPORTANT**: Room headers store a "palette set ID" (0-71), NOT a direct palette index!
|
||||
|
||||
The game uses a **two-level lookup system** to convert room palette properties to actual
|
||||
dungeon palette indices:
|
||||
|
||||
1. **Palette Set Table** (`paletteset_ids` at ROM `0x75460`)
|
||||
- 72 entries, each 4 bytes: `[bg_palette_offset, aux1, aux2, aux3]`
|
||||
- The first byte is a **byte offset** into the palette pointer table
|
||||
|
||||
2. **Palette Pointer Table** (ROM `0xDEC4B`)
|
||||
- Contains 16-bit words that, when divided by 180, give the palette index
|
||||
- Each word = ROM offset into dungeon palette data
|
||||
|
||||
**Correct Lookup Algorithm**:
|
||||
```cpp
|
||||
// Loading a dungeon palette
|
||||
constexpr uint32_t kPalettesetIds = 0x75460;
|
||||
constexpr uint32_t kDungeonPalettePointerTable = 0xDEC4B;
|
||||
|
||||
// room.palette is 0-71 (palette set ID, NOT palette index!)
|
||||
uint8_t byte_offset = paletteset_ids[room.palette][0]; // Step 1
|
||||
uint16_t word = rom.ReadWord(kDungeonPalettePointerTable + byte_offset); // Step 2
|
||||
int palette_id = word / 180; // Step 3: convert ROM offset to palette index
|
||||
```
|
||||
|
||||
**Example Lookup**:
|
||||
```
|
||||
Room palette property = 16
|
||||
→ paletteset_ids[16][0] = 0x10 (byte offset 16)
|
||||
→ Word at 0xDEC4B + 16 = 0x05A0 (1440)
|
||||
→ Palette ID = 1440 / 180 = 8
|
||||
→ Use dungeon_main[8], NOT dungeon_main[16]!
|
||||
```
|
||||
|
||||
**The Pointer Table (0xDEC4B)**:
|
||||
| Offset | Word | Palette ID |
|
||||
|--------|--------|------------|
|
||||
| 0 | 0x0000 | 0 |
|
||||
| 2 | 0x00B4 | 1 |
|
||||
| 4 | 0x0168 | 2 |
|
||||
| 6 | 0x021C | 3 |
|
||||
| ... | ... | ... |
|
||||
| 38 | 0x0D5C | 19 |
|
||||
|
||||
#### Common Pitfall: Direct Palette ID Usage
|
||||
|
||||
**WRONG** (causes purple/wrong colors for palette sets 16+):
|
||||
```cpp
|
||||
// BUG: Uses byte offset directly as palette ID!
|
||||
palette_id = paletteset_ids[room.palette][0];
|
||||
```
|
||||
|
||||
**CORRECT**:
|
||||
```cpp
|
||||
auto offset = paletteset_ids[room.palette][0];
|
||||
auto word = rom->ReadWord(0xDEC4B + offset);
|
||||
palette_id = word.value() / 180;
|
||||
```
|
||||
|
||||
#### Standard Usage
|
||||
```cpp
|
||||
// Loading a dungeon palette (with proper lookup)
|
||||
auto& dungeon_pal_group = rom->palette_group().dungeon_main;
|
||||
int num_palettes = dungeon_pal_group.size(); // Should be 20
|
||||
int palette_id = room.palette; // Room's palette ID (0-19)
|
||||
|
||||
// Perform the two-level lookup
|
||||
constexpr uint32_t kDungeonPalettePointerTable = 0xDEC4B;
|
||||
int palette_id = room.palette; // Default fallback
|
||||
if (room.palette < paletteset_ids.size()) {
|
||||
auto offset = paletteset_ids[room.palette][0];
|
||||
auto word = rom->ReadWord(kDungeonPalettePointerTable + offset);
|
||||
if (word.ok()) {
|
||||
palette_id = word.value() / 180;
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: Use operator[] not palette() method!
|
||||
auto palette = dungeon_pal_group[palette_id]; // Returns reference
|
||||
@@ -302,6 +373,10 @@ constexpr uint32_t kDungeonMainPalettes = 0xDD734;
|
||||
constexpr uint32_t kHardcodedGrassLW = 0x5FEA9;
|
||||
constexpr uint32_t kTriforcePalette = 0xF4CD0;
|
||||
constexpr uint32_t kOverworldMiniMapPalettes = 0x55B27;
|
||||
|
||||
// Dungeon palette lookup tables (critical for room rendering!)
|
||||
constexpr uint32_t kPalettesetIds = 0x75460; // 72 entries × 4 bytes
|
||||
constexpr uint32_t kDungeonPalettePointerTable = 0xDEC4B; // Palette ROM offsets
|
||||
```
|
||||
|
||||
## Graphics Sheet Palette Application
|
||||
@@ -351,3 +426,103 @@ bitmap.mutable_data() = new_data;
|
||||
// CORRECT - Updates both vector and surface
|
||||
bitmap.set_data(new_data);
|
||||
```
|
||||
|
||||
## Bitmap Dual Palette System
|
||||
|
||||
### Understanding the Two Palette Storage Mechanisms
|
||||
|
||||
The `Bitmap` class has **two separate palette storage locations**, which can cause confusion:
|
||||
|
||||
| Storage | Location | Populated By | Used For |
|
||||
|---------|----------|--------------|----------|
|
||||
| Internal SnesPalette | `bitmap.palette_` | `SetPalette(SnesPalette)` | Serialization, palette editing |
|
||||
| SDL Surface Palette | `surface_->format->palette` | Both `SetPalette` overloads | Actual rendering to textures |
|
||||
|
||||
### The Problem: Empty palette() Returns
|
||||
|
||||
When dungeon rooms apply palettes to their layer buffers, they use `SetPalette(vector<SDL_Color>)`:
|
||||
|
||||
```cpp
|
||||
// In room.cc - CreateAllGraphicsLayers()
|
||||
auto set_dungeon_palette = [](gfx::Bitmap& bmp, const gfx::SnesPalette& pal) {
|
||||
std::vector<SDL_Color> colors(256);
|
||||
for (size_t i = 0; i < pal.size() && i < 256; ++i) {
|
||||
ImVec4 rgb = pal[i].rgb();
|
||||
colors[i] = { static_cast<Uint8>(rgb.x), static_cast<Uint8>(rgb.y),
|
||||
static_cast<Uint8>(rgb.z), 255 };
|
||||
}
|
||||
colors[255] = {0, 0, 0, 0}; // Transparent
|
||||
bmp.SetPalette(colors); // Uses SDL_Color overload!
|
||||
};
|
||||
```
|
||||
|
||||
This means `bitmap.palette().size()` returns **0** even though the bitmap renders correctly!
|
||||
|
||||
### Solution: Extract Palette from SDL Surface
|
||||
|
||||
When you need to copy a palette between bitmaps (e.g., for layer compositing), extract it from the SDL surface:
|
||||
|
||||
```cpp
|
||||
void CopyPaletteBetweenBitmaps(const gfx::Bitmap& src, gfx::Bitmap& dst) {
|
||||
SDL_Surface* src_surface = src.surface();
|
||||
if (!src_surface || !src_surface->format) return;
|
||||
|
||||
SDL_Palette* src_pal = src_surface->format->palette;
|
||||
if (!src_pal || src_pal->ncolors == 0) return;
|
||||
|
||||
// Extract palette colors into a vector
|
||||
std::vector<SDL_Color> colors(256);
|
||||
int colors_to_copy = std::min(src_pal->ncolors, 256);
|
||||
for (int i = 0; i < colors_to_copy; ++i) {
|
||||
colors[i] = src_pal->colors[i];
|
||||
}
|
||||
|
||||
// Apply to destination bitmap
|
||||
dst.SetPalette(colors);
|
||||
}
|
||||
```
|
||||
|
||||
### Layer Compositing with Correct Palettes
|
||||
|
||||
When merging multiple layers into a single composite bitmap (as done in `RoomLayerManager::CompositeToOutput()`), the correct approach is:
|
||||
|
||||
1. Create/clear the output bitmap
|
||||
2. For each visible layer:
|
||||
- Extract the SDL palette from the first layer with a valid surface
|
||||
- Apply it to the output bitmap using `SetPalette(vector<SDL_Color>)`
|
||||
- Composite the pixel data (skip transparent indices 0 and 255)
|
||||
3. Sync pixel data to surface with `UpdateSurfacePixels()`
|
||||
4. Mark as modified for texture update
|
||||
|
||||
**Example from RoomLayerManager**:
|
||||
```cpp
|
||||
void RoomLayerManager::CompositeToOutput(Room& room, gfx::Bitmap& output) const {
|
||||
// Create output bitmap
|
||||
output.Create(512, 512, 8, std::vector<uint8_t>(512*512, 255));
|
||||
|
||||
bool palette_copied = false;
|
||||
for (auto layer_type : GetDrawOrder()) {
|
||||
auto& buffer = GetLayerBuffer(room, layer_type);
|
||||
const auto& src_bitmap = buffer.bitmap();
|
||||
|
||||
// Copy palette from first visible layer
|
||||
if (!palette_copied && src_bitmap.surface()) {
|
||||
ApplySDLPaletteToBitmap(src_bitmap.surface(), output);
|
||||
palette_copied = true;
|
||||
}
|
||||
|
||||
// Composite pixels...
|
||||
}
|
||||
|
||||
output.UpdateSurfacePixels();
|
||||
output.set_modified(true);
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices for Palette Handling
|
||||
|
||||
1. **Don't assume palette() has data**: Always check `palette().size() > 0` before using it
|
||||
2. **Use SDL surface as authoritative source**: For rendering-related palette operations
|
||||
3. **Use SetPalette(SnesPalette) for persistence**: When the palette needs to be saved or edited
|
||||
4. **Use SetPalette(vector<SDL_Color>) for performance**: When you already have SDL colors
|
||||
5. **Always call UpdateSurfacePixels()**: After modifying pixel data and before rendering
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# A1 - Testing Guide
|
||||
# Testing Guide
|
||||
|
||||
This guide provides a comprehensive overview of the testing framework for the yaze project, including the test organization, execution methods, and the end-to-end GUI automation system.
|
||||
This guide covers the testing framework for YAZE, including test organization, execution, and the GUI automation system.
|
||||
|
||||
## 1. Test Organization
|
||||
---
|
||||
|
||||
The test suite is organized into a clear directory structure that separates tests by their purpose and dependencies. This is the primary way to understand the nature of a test.
|
||||
## Test Organization
|
||||
|
||||
Tests are organized by purpose and dependencies:
|
||||
|
||||
```
|
||||
test/
|
||||
├── unit/ # Unit tests for individual components
|
||||
├── unit/ # Isolated component tests
|
||||
│ ├── core/ # Core functionality (asar, hex utils)
|
||||
│ ├── cli/ # Command-line interface tests
|
||||
│ ├── emu/ # Emulator component tests
|
||||
@@ -16,138 +18,132 @@ test/
|
||||
│ ├── gui/ # GUI widget tests
|
||||
│ ├── rom/ # ROM data structure tests
|
||||
│ └── zelda3/ # Game-specific logic tests
|
||||
├── integration/ # Tests for interactions between components
|
||||
│ ├── ai/ # AI agent and vision tests
|
||||
├── integration/ # Component interaction tests
|
||||
│ ├── ai/ # AI agent tests
|
||||
│ ├── editor/ # Editor integration tests
|
||||
│ └── zelda3/ # Game-specific integration tests (ROM-dependent)
|
||||
├── e2e/ # End-to-end user workflow tests (GUI-driven)
|
||||
│ └── zelda3/ # ROM-dependent integration tests
|
||||
├── e2e/ # End-to-end GUI workflow tests
|
||||
│ ├── rom_dependent/ # E2E tests requiring a ROM
|
||||
│ └── zscustomoverworld/ # ZSCustomOverworld upgrade E2E tests
|
||||
│ └── zscustomoverworld/ # ZSCustomOverworld upgrade tests
|
||||
├── benchmarks/ # Performance benchmarks
|
||||
├── mocks/ # Mock objects for isolating tests
|
||||
└── assets/ # Test assets (patches, data)
|
||||
├── mocks/ # Mock objects
|
||||
└── assets/ # Test data and patches
|
||||
```
|
||||
|
||||
## 2. Test Categories
|
||||
---
|
||||
|
||||
Based on the directory structure, tests fall into the following categories:
|
||||
## Test Categories
|
||||
|
||||
### Unit Tests (`unit/`)
|
||||
- **Purpose**: To test individual classes or functions in isolation.
|
||||
- **Characteristics**:
|
||||
- Fast, self-contained, and reliable.
|
||||
- No external dependencies (e.g., ROM files, running GUI).
|
||||
- Form the core of the CI/CD validation pipeline.
|
||||
| Category | Purpose | Dependencies | Speed |
|
||||
|----------|---------|--------------|-------|
|
||||
| **Unit** | Test individual classes/functions | None | Fast |
|
||||
| **Integration** | Test component interactions | May require ROM | Medium |
|
||||
| **E2E** | Simulate user workflows | GUI + ROM | Slow |
|
||||
| **Benchmarks** | Measure performance | None | Variable |
|
||||
|
||||
### Integration Tests (`integration/`)
|
||||
- **Purpose**: To verify that different components of the application work together correctly.
|
||||
- **Characteristics**:
|
||||
- May require a real ROM file (especially those in `integration/zelda3/`). These are considered "ROM-dependent".
|
||||
- Test interactions between modules, such as the `asar` wrapper and the `Rom` class, or AI services with the GUI controller.
|
||||
- Slower than unit tests but crucial for catching bugs at module boundaries.
|
||||
### Unit Tests
|
||||
|
||||
### End-to-End (E2E) Tests (`e2e/`)
|
||||
- **Purpose**: To simulate a full user workflow from start to finish.
|
||||
- **Characteristics**:
|
||||
- Driven by the **ImGui Test Engine**.
|
||||
- Almost always require a running GUI and often a real ROM.
|
||||
- The slowest but most comprehensive tests, validating the user experience.
|
||||
- Includes smoke tests, canvas interactions, and complex workflows like ZSCustomOverworld upgrades.
|
||||
Fast, isolated tests with no external dependencies. Run in CI on every commit.
|
||||
|
||||
### Benchmarks (`benchmarks/`)
|
||||
- **Purpose**: To measure and track the performance of critical code paths, particularly in the graphics system.
|
||||
- **Characteristics**:
|
||||
- Not focused on correctness but on speed and efficiency.
|
||||
- Run manually or in specialized CI jobs to prevent performance regressions.
|
||||
### Integration Tests
|
||||
|
||||
## 3. Running Tests
|
||||
Test module interactions (e.g., `asar` wrapper with `Rom` class). Some require a ROM file.
|
||||
|
||||
> 💡 Need a refresher on presets/commands? See the [Build & Test Quick Reference](../build/quick-reference.md)
|
||||
> for the canonical `cmake`, `ctest`, and helper script usage before running the commands below.
|
||||
### E2E Tests
|
||||
|
||||
### Using the Enhanced Test Runner (`yaze_test`)
|
||||
GUI-driven tests using ImGui Test Engine. Validate complete user workflows.
|
||||
|
||||
The most flexible way to run tests is by using the `yaze_test` executable directly. It provides flags to filter tests by category, which is ideal for development and AI agent workflows.
|
||||
### Benchmarks
|
||||
|
||||
Performance measurement for critical paths. Run manually or in specialized CI jobs.
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
|
||||
> See the [Build and Test Quick Reference](../build/quick-reference.md) for full command reference.
|
||||
|
||||
### Using yaze_test Executable
|
||||
|
||||
```bash
|
||||
# First, build the test executable
|
||||
cmake --build build_ai --target yaze_test
|
||||
# Build
|
||||
cmake --build build --target yaze_test
|
||||
|
||||
# Run all tests
|
||||
./build_ai/bin/yaze_test
|
||||
./build/bin/yaze_test
|
||||
|
||||
# Run only unit tests
|
||||
./build_ai/bin/yaze_test --unit
|
||||
# Run by category
|
||||
./build/bin/yaze_test --unit
|
||||
./build/bin/yaze_test --integration
|
||||
./build/bin/yaze_test --e2e --show-gui
|
||||
|
||||
# Run only integration tests
|
||||
./build_ai/bin/yaze_test --integration
|
||||
# Run ROM-dependent tests
|
||||
./build/bin/yaze_test --rom-dependent --rom-path /path/to/zelda3.sfc
|
||||
|
||||
# Run E2E tests (requires a GUI)
|
||||
./build_ai/bin/yaze_test --e2e --show-gui
|
||||
# Run by pattern
|
||||
./build/bin/yaze_test "*Asar*"
|
||||
|
||||
# Run ROM-dependent tests with a specific ROM
|
||||
./build_ai/bin/yaze_test --rom-dependent --rom-path /path/to/zelda3.sfc
|
||||
|
||||
# Run tests matching a specific pattern (e.g., all Asar tests)
|
||||
./build_ai/bin/yaze_test "*Asar*"
|
||||
|
||||
# Get a full list of options
|
||||
./build_ai/bin/yaze_test --help
|
||||
# Show all options
|
||||
./build/bin/yaze_test --help
|
||||
```
|
||||
|
||||
### Using CTest and CMake Presets
|
||||
|
||||
For CI/CD or a more traditional workflow, you can use `ctest` with CMake presets.
|
||||
### Using CTest
|
||||
|
||||
```bash
|
||||
# Configure a development build (enables ROM-dependent tests)
|
||||
cmake --preset mac-dev -DYAZE_TEST_ROM_PATH=/path/to/your/zelda3.sfc
|
||||
# Configure with ROM tests
|
||||
cmake --preset mac-dev -DYAZE_TEST_ROM_PATH=/path/to/zelda3.sfc
|
||||
|
||||
# Build the tests
|
||||
# Build
|
||||
cmake --build --preset mac-dev --target yaze_test
|
||||
|
||||
# Run stable tests (fast, primarily unit tests)
|
||||
ctest --preset dev
|
||||
|
||||
# Run all tests, including ROM-dependent and E2E
|
||||
ctest --preset all
|
||||
# Run tests
|
||||
ctest --preset dev # Stable tests
|
||||
ctest --preset all # All tests
|
||||
```
|
||||
|
||||
## 4. Writing Tests
|
||||
---
|
||||
|
||||
When adding new tests, place them in the appropriate directory based on their purpose and dependencies.
|
||||
## Writing Tests
|
||||
|
||||
- **New class `MyClass`?** Add `test/unit/my_class_test.cc`.
|
||||
- **Testing `MyClass` with a real ROM?** Add `test/integration/my_class_rom_test.cc`.
|
||||
- **Testing a full UI workflow involving `MyClass`?** Add `test/e2e/my_class_workflow_test.cc`.
|
||||
Place tests based on their purpose:
|
||||
|
||||
## 5. E2E GUI Testing Framework
|
||||
| Test Type | File Location |
|
||||
|-----------|---------------|
|
||||
| Unit test for `MyClass` | `test/unit/my_class_test.cc` |
|
||||
| Integration with ROM | `test/integration/my_class_rom_test.cc` |
|
||||
| UI workflow test | `test/e2e/my_class_workflow_test.cc` |
|
||||
|
||||
The E2E framework uses `ImGuiTestEngine` to automate UI interactions.
|
||||
---
|
||||
|
||||
### Architecture
|
||||
## E2E GUI Testing
|
||||
|
||||
- **`test/yaze_test.cc`**: The main test runner that can initialize a GUI for E2E tests.
|
||||
- **`test/e2e/`**: Contains all E2E test files, such as:
|
||||
- `framework_smoke_test.cc`: Basic infrastructure verification.
|
||||
- `canvas_selection_test.cc`: Canvas interaction tests.
|
||||
- `dungeon_editor_tests.cc`: UI tests for the dungeon editor.
|
||||
- **`test/test_utils.h`**: Provides high-level helper functions for common actions like loading a ROM (`LoadRomInTest`) or opening an editor (`OpenEditorInTest`).
|
||||
The E2E framework uses ImGui Test Engine for UI automation.
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `test/yaze_test.cc` | Main test runner with GUI initialization |
|
||||
| `test/e2e/framework_smoke_test.cc` | Infrastructure verification |
|
||||
| `test/e2e/canvas_selection_test.cc` | Canvas interaction tests |
|
||||
| `test/e2e/dungeon_editor_tests.cc` | Dungeon editor UI tests |
|
||||
| `test/test_utils.h` | Helper functions (LoadRomInTest, OpenEditorInTest) |
|
||||
|
||||
### Running GUI Tests
|
||||
|
||||
To run E2E tests and see the GUI interactions, use the `--show-gui` flag.
|
||||
|
||||
```bash
|
||||
# Run all E2E tests with the GUI visible
|
||||
./build_ai/bin/yaze_test --e2e --show-gui
|
||||
# Run all E2E tests with visible GUI
|
||||
./build/bin/yaze_test --e2e --show-gui
|
||||
|
||||
# Run a specific E2E test by name
|
||||
./build_ai/bin/yaze_test --show-gui --gtest_filter="*DungeonEditorSmokeTest"
|
||||
# Run specific test
|
||||
./build/bin/yaze_test --show-gui --gtest_filter="*DungeonEditorSmokeTest"
|
||||
```
|
||||
|
||||
### Widget Discovery and AI Integration
|
||||
### AI Integration
|
||||
|
||||
The GUI testing framework is designed for AI agent automation. All major UI elements are registered with stable IDs, allowing an agent to "discover" and interact with them programmatically via the `z3ed` CLI.
|
||||
UI elements are registered with stable IDs for programmatic access via `z3ed`:
|
||||
- `z3ed gui discover` - List available widgets
|
||||
- `z3ed gui click` - Interact with widgets
|
||||
- `z3ed agent test replay` - Replay recorded tests
|
||||
|
||||
Refer to the `z3ed` agent guide for details on using commands like `z3ed gui discover`, `z3ed gui click`, and `z3ed agent test replay`.
|
||||
See the [z3ed CLI Guide](../usage/z3ed-cli.md) for more details.
|
||||
|
||||
@@ -300,7 +300,7 @@ After pushing, CI will run tests on all platforms (Linux, macOS, Windows):
|
||||
scripts/agents/run-gh-workflow.sh ci.yml -f enable_http_api_tests=true
|
||||
```
|
||||
|
||||
See [GH Actions Remote Guide](../../internal/agents/gh-actions-remote.md) for setup.
|
||||
See [GH Actions Remote Guide](../../internal/agents/archive/utility-tools/gh-actions-remote.md) for setup.
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@ jobs:
|
||||
|
||||
- name: Build z3ed
|
||||
run: |
|
||||
cmake -B build_test
|
||||
cmake --build build_test --parallel
|
||||
cmake -B build
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Run Agent Tests (Mock ROM)
|
||||
run: |
|
||||
|
||||
@@ -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
|
||||
874
docs/public/guides/z3ed-workflows.md
Normal file
874
docs/public/guides/z3ed-workflows.md
Normal file
@@ -0,0 +1,874 @@
|
||||
# Z3ED Workflow Examples
|
||||
|
||||
This guide demonstrates practical workflows using z3ed CLI for common ROM hacking tasks, test automation, and AI-assisted development.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Basic ROM Editing Workflow](#basic-rom-editing-workflow)
|
||||
2. [Automated Testing Pipeline](#automated-testing-pipeline)
|
||||
3. [AI-Assisted Development](#ai-assisted-development)
|
||||
4. [Multi-Agent Collaboration](#multi-agent-collaboration)
|
||||
5. [CI/CD Integration](#cicd-integration)
|
||||
6. [Advanced Automation Scripts](#advanced-automation-scripts)
|
||||
|
||||
## Basic ROM Editing Workflow
|
||||
|
||||
### Scenario: Adding a New Dungeon Room
|
||||
|
||||
This workflow demonstrates how to create and populate a new dungeon room using z3ed commands.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# new_dungeon_room.sh - Create and populate a new dungeon room
|
||||
|
||||
# Load ROM and create snapshot for safety
|
||||
z3ed rom snapshot --name "before_new_room" --compress
|
||||
|
||||
# Open dungeon editor for room 50
|
||||
z3ed editor dungeon set-property --room 50 --property "layout" --value "2x2"
|
||||
z3ed editor dungeon set-property --room 50 --property "floor1_graphics" --value 0x0A
|
||||
|
||||
# Place entrance and exit doors
|
||||
z3ed editor dungeon place-object --room 50 --type 0x00 --x 7 --y 0 # North door
|
||||
z3ed editor dungeon place-object --room 50 --type 0x00 --x 7 --y 15 # South door
|
||||
|
||||
# Add enemies
|
||||
z3ed editor dungeon place-object --room 50 --type 0x08 --x 4 --y 5 # Soldier
|
||||
z3ed editor dungeon place-object --room 50 --type 0x08 --x 10 --y 5 # Soldier
|
||||
z3ed editor dungeon place-object --room 50 --type 0x0C --x 7 --y 8 # Knight
|
||||
|
||||
# Add treasure chest with key
|
||||
z3ed editor dungeon place-object --room 50 --type 0x22 --x 7 --y 12
|
||||
z3ed editor dungeon set-property --room 50 --property "chest_contents" --value "small_key"
|
||||
|
||||
# Validate the room
|
||||
z3ed editor dungeon validate-room --room 50 --fix-issues
|
||||
|
||||
# Test in emulator
|
||||
z3ed emulator run --warp-to-room 50
|
||||
```
|
||||
|
||||
### Scenario: Batch Tile Replacement
|
||||
|
||||
Replace all instances of a tile across multiple overworld maps.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# batch_tile_replace.sh - Replace tiles across overworld maps
|
||||
|
||||
# Define old and new tile IDs
|
||||
OLD_TILE=0x142 # Old grass tile
|
||||
NEW_TILE=0x143 # New grass variant
|
||||
|
||||
# Create snapshot
|
||||
z3ed rom snapshot --name "before_tile_replacement"
|
||||
|
||||
# Create batch operation file
|
||||
cat > tile_replacement.json << EOF
|
||||
{
|
||||
"operations": [
|
||||
EOF
|
||||
|
||||
# Generate operations for all Light World maps (0x00-0x3F)
|
||||
for map in {0..63}; do
|
||||
# Find all occurrences of the old tile
|
||||
positions=$(z3ed query find-tiles --map $map --tile $OLD_TILE --format json)
|
||||
|
||||
# Parse positions and add replacement operations
|
||||
echo "$positions" | jq -r '.positions[] |
|
||||
" {\"editor\": \"overworld\", \"action\": \"set-tile\", \"params\": {\"map\": '$map', \"x\": .x, \"y\": .y, \"tile\": '$NEW_TILE'}},"' >> tile_replacement.json
|
||||
done
|
||||
|
||||
# Close JSON and execute
|
||||
echo ' ]
|
||||
}' >> tile_replacement.json
|
||||
|
||||
# Execute batch operation
|
||||
z3ed editor batch --script tile_replacement.json --dry-run
|
||||
read -p "Proceed with replacement? (y/n) " -n 1 -r
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
z3ed editor batch --script tile_replacement.json
|
||||
fi
|
||||
```
|
||||
|
||||
## Automated Testing Pipeline
|
||||
|
||||
### Scenario: Test-Driven Dungeon Development
|
||||
|
||||
Create tests before implementing dungeon features.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# test_driven_dungeon.sh - TDD approach for dungeon creation
|
||||
|
||||
# Start test recording
|
||||
z3ed test record --name "dungeon_puzzle_test" --start
|
||||
|
||||
# Define expected behavior
|
||||
cat > expected_behavior.json << EOF
|
||||
{
|
||||
"room": 75,
|
||||
"requirements": [
|
||||
"Player must push block to activate switch",
|
||||
"Door opens when switch is activated",
|
||||
"Chest appears after door opens",
|
||||
"Room must be completable in under 60 seconds"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Record the intended solution path
|
||||
z3ed editor dungeon place-object --room 75 --type "push_block" --x 5 --y 5
|
||||
z3ed editor dungeon place-object --room 75 --type "floor_switch" --x 10 --y 10
|
||||
z3ed editor dungeon place-object --room 75 --type "locked_door" --x 7 --y 0
|
||||
z3ed editor dungeon set-property --room 75 --property "switch_target" --value "locked_door"
|
||||
|
||||
# Stop recording and generate test
|
||||
z3ed test record --stop --save-as dungeon_puzzle_recording.json
|
||||
z3ed test generate --from-recording dungeon_puzzle_recording.json \
|
||||
--requirements expected_behavior.json \
|
||||
--output test_dungeon_puzzle.cc
|
||||
|
||||
# Compile and run the test
|
||||
z3ed test run --file test_dungeon_puzzle.cc
|
||||
|
||||
# Run continuously during development
|
||||
watch -n 5 'z3ed test run --file test_dungeon_puzzle.cc --quiet'
|
||||
```
|
||||
|
||||
### Scenario: Regression Test Suite
|
||||
|
||||
Automated regression testing for ROM modifications.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# regression_test_suite.sh - Comprehensive regression testing
|
||||
|
||||
# Create baseline from stable version
|
||||
z3ed test baseline --create --name "stable_v1.0"
|
||||
|
||||
# Define test suite
|
||||
cat > regression_tests.yaml << EOF
|
||||
tests:
|
||||
- name: "Overworld Collision"
|
||||
commands:
|
||||
- "editor overworld validate-collision --map ALL"
|
||||
expected: "no_errors"
|
||||
|
||||
- name: "Dungeon Room Connectivity"
|
||||
commands:
|
||||
- "query dungeon-graph --check-connectivity"
|
||||
expected: "all_rooms_reachable"
|
||||
|
||||
- name: "Sprite Limits"
|
||||
commands:
|
||||
- "query sprite-count --per-screen"
|
||||
expected: "max_sprites <= 16"
|
||||
|
||||
- name: "Memory Usage"
|
||||
commands:
|
||||
- "query memory-usage --runtime"
|
||||
expected: "usage < 95%"
|
||||
|
||||
- name: "Save/Load Integrity"
|
||||
commands:
|
||||
- "test save-load --iterations 100"
|
||||
expected: "no_corruption"
|
||||
EOF
|
||||
|
||||
# Run regression suite
|
||||
z3ed test run --suite regression_tests.yaml --parallel
|
||||
|
||||
# Compare against baseline
|
||||
z3ed test baseline --compare --name "stable_v1.0" --threshold 98
|
||||
|
||||
# Generate report
|
||||
z3ed test coverage --report html --output coverage_report.html
|
||||
```
|
||||
|
||||
## AI-Assisted Development
|
||||
|
||||
### Scenario: AI-Powered Bug Fix
|
||||
|
||||
Use AI to identify and fix a bug in dungeon logic.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ai_bug_fix.sh - AI-assisted debugging
|
||||
|
||||
# Describe the bug to AI
|
||||
BUG_DESCRIPTION="Player gets stuck when entering room 42 from the south"
|
||||
|
||||
# Ask AI to analyze
|
||||
z3ed ai analyze --type bug \
|
||||
--context "room=42" \
|
||||
--description "$BUG_DESCRIPTION" \
|
||||
--output bug_analysis.json
|
||||
|
||||
# Get AI suggestions for fix
|
||||
z3ed ai suggest --task "fix dungeon room entry bug" \
|
||||
--context bug_analysis.json \
|
||||
--output suggested_fix.json
|
||||
|
||||
# Review suggestions
|
||||
cat suggested_fix.json | jq '.suggestions[]'
|
||||
|
||||
# Apply AI-suggested fix (after review)
|
||||
z3ed ai apply --fix suggested_fix.json --dry-run
|
||||
|
||||
read -p "Apply AI-suggested fix? (y/n) " -n 1 -r
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
z3ed ai apply --fix suggested_fix.json
|
||||
|
||||
# Validate the fix
|
||||
z3ed editor dungeon validate-room --room 42
|
||||
z3ed test run --filter "*Room42*"
|
||||
fi
|
||||
|
||||
# Generate regression test for the bug
|
||||
z3ed test generate --type regression \
|
||||
--bug "$BUG_DESCRIPTION" \
|
||||
--fix suggested_fix.json \
|
||||
--output test_room42_entry.cc
|
||||
```
|
||||
|
||||
### Scenario: AI Test Generation
|
||||
|
||||
Generate comprehensive tests using AI.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# ai_test_generation.sh - AI-powered test creation
|
||||
|
||||
# Select component to test
|
||||
COMPONENT="OverworldEditor"
|
||||
|
||||
# Analyze code and generate test specification
|
||||
z3ed ai analyze --code src/app/editor/overworld_editor.cc \
|
||||
--task "identify test cases" \
|
||||
--output test_spec.json
|
||||
|
||||
# Generate comprehensive test suite
|
||||
z3ed test generate --target $COMPONENT \
|
||||
--spec test_spec.json \
|
||||
--include-edge-cases \
|
||||
--include-mocks \
|
||||
--framework gtest \
|
||||
--output ${COMPONENT}_test.cc
|
||||
|
||||
# AI review of generated tests
|
||||
z3ed ai review --file ${COMPONENT}_test.cc \
|
||||
--criteria "coverage,correctness,performance" \
|
||||
--output test_review.json
|
||||
|
||||
# Apply AI improvements
|
||||
z3ed ai improve --file ${COMPONENT}_test.cc \
|
||||
--feedback test_review.json \
|
||||
--output ${COMPONENT}_test_improved.cc
|
||||
|
||||
# Run and validate tests
|
||||
z3ed test run --file ${COMPONENT}_test_improved.cc --coverage
|
||||
```
|
||||
|
||||
## Multi-Agent Collaboration
|
||||
|
||||
### Scenario: Parallel ROM Development
|
||||
|
||||
Multiple AI agents working on different aspects simultaneously.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# multi_agent_development.py - Coordinate multiple AI agents
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from z3ed_client import Agent, Coordinator
|
||||
|
||||
async def main():
|
||||
# Initialize coordinator
|
||||
coordinator = Coordinator("localhost:8080")
|
||||
|
||||
# Define agents with specializations
|
||||
agents = [
|
||||
Agent("overworld_specialist", capabilities=["overworld", "sprites"]),
|
||||
Agent("dungeon_specialist", capabilities=["dungeon", "objects"]),
|
||||
Agent("graphics_specialist", capabilities=["graphics", "palettes"]),
|
||||
Agent("testing_specialist", capabilities=["testing", "validation"])
|
||||
]
|
||||
|
||||
# Connect all agents
|
||||
for agent in agents:
|
||||
await agent.connect()
|
||||
|
||||
# Define parallel tasks
|
||||
tasks = [
|
||||
{
|
||||
"id": "task_1",
|
||||
"type": "overworld",
|
||||
"description": "Optimize Light World map connections",
|
||||
"assigned_to": "overworld_specialist"
|
||||
},
|
||||
{
|
||||
"id": "task_2",
|
||||
"type": "dungeon",
|
||||
"description": "Balance enemy placement in dungeons 1-3",
|
||||
"assigned_to": "dungeon_specialist"
|
||||
},
|
||||
{
|
||||
"id": "task_3",
|
||||
"type": "graphics",
|
||||
"description": "Create new palette variations for seasons",
|
||||
"assigned_to": "graphics_specialist"
|
||||
},
|
||||
{
|
||||
"id": "task_4",
|
||||
"type": "testing",
|
||||
"description": "Generate tests for all recent changes",
|
||||
"assigned_to": "testing_specialist"
|
||||
}
|
||||
]
|
||||
|
||||
# Queue tasks
|
||||
for task in tasks:
|
||||
coordinator.queue_task(task)
|
||||
|
||||
# Monitor progress
|
||||
while not coordinator.all_tasks_complete():
|
||||
status = coordinator.get_status()
|
||||
print(f"Progress: {status['completed']}/{status['total']} tasks")
|
||||
|
||||
# Handle conflicts if they arise
|
||||
if status['conflicts']:
|
||||
for conflict in status['conflicts']:
|
||||
resolution = coordinator.resolve_conflict(
|
||||
conflict,
|
||||
strategy="merge" # or "last_write_wins", "manual"
|
||||
)
|
||||
print(f"Resolved conflict: {resolution}")
|
||||
|
||||
await asyncio.sleep(5)
|
||||
|
||||
# Collect results
|
||||
results = coordinator.get_all_results()
|
||||
|
||||
# Generate combined report
|
||||
report = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"agents": [agent.name for agent in agents],
|
||||
"tasks_completed": len(results),
|
||||
"results": results
|
||||
}
|
||||
|
||||
with open("multi_agent_report.json", "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print("Multi-agent development complete!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Scenario: Agent Coordination Script
|
||||
|
||||
Bash script for coordinating multiple z3ed instances.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# agent_coordination.sh - Coordinate multiple z3ed agents
|
||||
|
||||
# Start coordination server
|
||||
z3ed server start --port 8080 --config server.yaml &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Function to run agent task
|
||||
run_agent() {
|
||||
local agent_name=$1
|
||||
local task=$2
|
||||
local log_file="${agent_name}.log"
|
||||
|
||||
echo "Starting $agent_name for task: $task"
|
||||
z3ed agent run --name "$agent_name" \
|
||||
--task "$task" \
|
||||
--server localhost:8080 \
|
||||
--log "$log_file" &
|
||||
}
|
||||
|
||||
# Start multiple agents
|
||||
run_agent "agent_overworld" "optimize overworld maps 0x00-0x3F"
|
||||
run_agent "agent_dungeon" "validate and fix all dungeon rooms"
|
||||
run_agent "agent_graphics" "compress unused graphics data"
|
||||
run_agent "agent_testing" "generate missing unit tests"
|
||||
|
||||
# Monitor agent progress
|
||||
while true; do
|
||||
clear
|
||||
echo "=== Agent Status ==="
|
||||
z3ed agent status --server localhost:8080 --format table
|
||||
|
||||
# Check for completion
|
||||
if z3ed agent status --server localhost:8080 --check-complete; then
|
||||
echo "All agents completed!"
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
|
||||
# Collect and merge results
|
||||
z3ed agent collect-results --server localhost:8080 --output results/
|
||||
|
||||
# Stop server
|
||||
kill $SERVER_PID
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### Scenario: GitHub Actions Integration
|
||||
|
||||
`.github/workflows/z3ed-testing.yml`:
|
||||
|
||||
```yaml
|
||||
name: Z3ED Automated Testing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
rom-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup z3ed
|
||||
run: |
|
||||
./scripts/install-z3ed.sh
|
||||
z3ed --version
|
||||
|
||||
- name: Load ROM
|
||||
run: |
|
||||
z3ed rom load --file ${{ secrets.ROM_PATH }}
|
||||
z3ed rom validate
|
||||
|
||||
- name: Run validation suite
|
||||
run: |
|
||||
z3ed test run --suite validation.yaml
|
||||
z3ed query stats --type all --output stats.json
|
||||
|
||||
- name: Check for regressions
|
||||
run: |
|
||||
z3ed test baseline --compare --name stable --threshold 95
|
||||
|
||||
- name: Generate report
|
||||
run: |
|
||||
z3ed test coverage --report markdown --output REPORT.md
|
||||
|
||||
- name: Comment on PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const report = fs.readFileSync('REPORT.md', 'utf8');
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: report
|
||||
});
|
||||
|
||||
ai-review:
|
||||
runs-on: ubuntu-latest
|
||||
needs: rom-validation
|
||||
steps:
|
||||
- name: AI Code Review
|
||||
run: |
|
||||
z3ed ai review --changes ${{ github.sha }} \
|
||||
--model gemini-pro \
|
||||
--output review.json
|
||||
|
||||
- name: Apply AI Suggestions
|
||||
run: |
|
||||
z3ed ai apply --suggestions review.json --auto-approve safe
|
||||
```
|
||||
|
||||
### Scenario: Local CI Pipeline
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# local_ci.sh - Local CI/CD pipeline
|
||||
|
||||
# Configuration
|
||||
ROM_FILE="zelda3.sfc"
|
||||
BUILD_DIR="build"
|
||||
TEST_RESULTS="test_results"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
local status=$1
|
||||
local message=$2
|
||||
case $status in
|
||||
"SUCCESS") echo -e "${GREEN}✓${NC} $message" ;;
|
||||
"FAILURE") echo -e "${RED}✗${NC} $message" ;;
|
||||
"WARNING") echo -e "${YELLOW}⚠${NC} $message" ;;
|
||||
*) echo "$message" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Step 1: Clean and build
|
||||
print_status "INFO" "Starting CI pipeline..."
|
||||
z3ed build clean --all
|
||||
z3ed build --preset lin-dbg --parallel 8
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_status "SUCCESS" "Build completed"
|
||||
else
|
||||
print_status "FAILURE" "Build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Run tests
|
||||
mkdir -p $TEST_RESULTS
|
||||
z3ed test run --category unit --output $TEST_RESULTS/unit.xml
|
||||
UNIT_RESULT=$?
|
||||
|
||||
z3ed test run --category integration --rom $ROM_FILE --output $TEST_RESULTS/integration.xml
|
||||
INTEGRATION_RESULT=$?
|
||||
|
||||
if [ $UNIT_RESULT -eq 0 ] && [ $INTEGRATION_RESULT -eq 0 ]; then
|
||||
print_status "SUCCESS" "All tests passed"
|
||||
else
|
||||
print_status "FAILURE" "Some tests failed"
|
||||
z3ed test report --dir $TEST_RESULTS --format console
|
||||
fi
|
||||
|
||||
# Step 3: ROM validation
|
||||
z3ed rom load --file $ROM_FILE
|
||||
z3ed rom validate --checksums --headers --regions
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_status "SUCCESS" "ROM validation passed"
|
||||
else
|
||||
print_status "WARNING" "ROM validation warnings"
|
||||
fi
|
||||
|
||||
# Step 4: Performance benchmarks
|
||||
z3ed test benchmark --suite performance.yaml --output benchmarks.json
|
||||
z3ed test benchmark --compare-baseline --threshold 110
|
||||
|
||||
# Step 5: Generate reports
|
||||
z3ed test coverage --report html --output coverage/
|
||||
z3ed test report --comprehensive --output CI_REPORT.md
|
||||
|
||||
print_status "SUCCESS" "CI pipeline completed!"
|
||||
```
|
||||
|
||||
## Advanced Automation Scripts
|
||||
|
||||
### Scenario: Intelligent Room Generator
|
||||
|
||||
Generate dungeon rooms using AI and templates.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# intelligent_room_generator.py - AI-powered room generation
|
||||
|
||||
import json
|
||||
import random
|
||||
from z3ed_client import Z3edClient
|
||||
|
||||
class IntelligentRoomGenerator:
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.templates = self.load_templates()
|
||||
|
||||
def load_templates(self):
|
||||
"""Load room templates from file"""
|
||||
with open("room_templates.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def generate_room(self, room_id, difficulty="medium", theme="castle"):
|
||||
"""Generate a room based on parameters"""
|
||||
|
||||
# Select appropriate template
|
||||
template = self.select_template(difficulty, theme)
|
||||
|
||||
# Get AI suggestions for room layout
|
||||
suggestions = self.client.ai_suggest(
|
||||
task="generate dungeon room layout",
|
||||
constraints={
|
||||
"room_id": room_id,
|
||||
"difficulty": difficulty,
|
||||
"theme": theme,
|
||||
"template": template["name"]
|
||||
}
|
||||
)
|
||||
|
||||
# Apply base template
|
||||
self.apply_template(room_id, template)
|
||||
|
||||
# Apply AI suggestions
|
||||
for suggestion in suggestions["modifications"]:
|
||||
self.apply_modification(room_id, suggestion)
|
||||
|
||||
# Validate room
|
||||
validation = self.client.validate_room(room_id)
|
||||
|
||||
if not validation["valid"]:
|
||||
# Ask AI to fix issues
|
||||
fixes = self.client.ai_suggest(
|
||||
task="fix dungeon room issues",
|
||||
context={
|
||||
"room_id": room_id,
|
||||
"issues": validation["issues"]
|
||||
}
|
||||
)
|
||||
|
||||
for fix in fixes["fixes"]:
|
||||
self.apply_modification(room_id, fix)
|
||||
|
||||
return self.get_room_data(room_id)
|
||||
|
||||
def select_template(self, difficulty, theme):
|
||||
"""Select best matching template"""
|
||||
matching = [
|
||||
t for t in self.templates
|
||||
if t["difficulty"] == difficulty and t["theme"] == theme
|
||||
]
|
||||
return random.choice(matching) if matching else self.templates[0]
|
||||
|
||||
def apply_template(self, room_id, template):
|
||||
"""Apply template to room"""
|
||||
# Set room properties
|
||||
for prop, value in template["properties"].items():
|
||||
self.client.set_room_property(room_id, prop, value)
|
||||
|
||||
# Place template objects
|
||||
for obj in template["objects"]:
|
||||
self.client.place_object(
|
||||
room_id,
|
||||
obj["type"],
|
||||
obj["x"],
|
||||
obj["y"]
|
||||
)
|
||||
|
||||
def apply_modification(self, room_id, modification):
|
||||
"""Apply a single modification to room"""
|
||||
action = modification["action"]
|
||||
params = modification["params"]
|
||||
|
||||
if action == "place_object":
|
||||
self.client.place_object(room_id, **params)
|
||||
elif action == "set_property":
|
||||
self.client.set_room_property(room_id, **params)
|
||||
elif action == "remove_object":
|
||||
self.client.remove_object(room_id, **params)
|
||||
|
||||
def generate_dungeon(self, start_room, num_rooms, difficulty_curve):
|
||||
"""Generate entire dungeon"""
|
||||
rooms = []
|
||||
|
||||
for i in range(num_rooms):
|
||||
room_id = start_room + i
|
||||
|
||||
# Adjust difficulty based on curve
|
||||
difficulty = self.calculate_difficulty(i, num_rooms, difficulty_curve)
|
||||
|
||||
# Generate room
|
||||
room_data = self.generate_room(room_id, difficulty)
|
||||
rooms.append(room_data)
|
||||
|
||||
# Connect to previous room
|
||||
if i > 0:
|
||||
self.connect_rooms(room_id - 1, room_id)
|
||||
|
||||
# Final validation
|
||||
self.validate_dungeon(start_room, num_rooms)
|
||||
|
||||
return rooms
|
||||
|
||||
def calculate_difficulty(self, index, total, curve):
|
||||
"""Calculate difficulty based on position and curve"""
|
||||
if curve == "linear":
|
||||
return ["easy", "medium", "hard"][min(index // (total // 3), 2)]
|
||||
elif curve == "exponential":
|
||||
return ["easy", "medium", "hard"][min(int((index / total) ** 2 * 3), 2)]
|
||||
else:
|
||||
return "medium"
|
||||
|
||||
# Main execution
|
||||
if __name__ == "__main__":
|
||||
client = Z3edClient("localhost:8080")
|
||||
generator = IntelligentRoomGenerator(client)
|
||||
|
||||
# Generate a 10-room dungeon
|
||||
dungeon = generator.generate_dungeon(
|
||||
start_room=100,
|
||||
num_rooms=10,
|
||||
difficulty_curve="exponential"
|
||||
)
|
||||
|
||||
# Save dungeon data
|
||||
with open("generated_dungeon.json", "w") as f:
|
||||
json.dump(dungeon, f, indent=2)
|
||||
|
||||
print(f"Generated {len(dungeon)} rooms successfully!")
|
||||
```
|
||||
|
||||
### Scenario: Automated ROM Optimizer
|
||||
|
||||
Optimize ROM for size and performance.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# rom_optimizer.sh - Automated ROM optimization
|
||||
|
||||
# Create backup
|
||||
z3ed rom snapshot --name "pre-optimization" --compress
|
||||
|
||||
# Step 1: Identify unused space
|
||||
echo "Analyzing ROM for optimization opportunities..."
|
||||
z3ed query find-unused-space --min-size 256 --output unused_space.json
|
||||
|
||||
# Step 2: Compress graphics
|
||||
echo "Compressing graphics data..."
|
||||
for sheet in {0..223}; do
|
||||
z3ed editor graphics compress-sheet --sheet $sheet --algorithm lz77
|
||||
done
|
||||
|
||||
# Step 3: Optimize sprite data
|
||||
echo "Optimizing sprite data..."
|
||||
z3ed ai analyze --type optimization \
|
||||
--target sprites \
|
||||
--output sprite_optimization.json
|
||||
|
||||
z3ed ai apply --optimizations sprite_optimization.json
|
||||
|
||||
# Step 4: Remove duplicate data
|
||||
echo "Removing duplicate data..."
|
||||
z3ed query find-duplicates --min-size 16 --output duplicates.json
|
||||
z3ed optimize remove-duplicates --input duplicates.json --safe-mode
|
||||
|
||||
# Step 5: Optimize room data
|
||||
echo "Optimizing dungeon room data..."
|
||||
for room in {0..295}; do
|
||||
z3ed editor dungeon optimize-room --room $room \
|
||||
--remove-unreachable \
|
||||
--compress-objects
|
||||
done
|
||||
|
||||
# Step 6: Pack data efficiently
|
||||
echo "Repacking ROM data..."
|
||||
z3ed optimize repack --strategy best-fit
|
||||
|
||||
# Step 7: Validate optimization
|
||||
echo "Validating optimized ROM..."
|
||||
z3ed rom validate --comprehensive
|
||||
z3ed test run --suite optimization_validation.yaml
|
||||
|
||||
# Step 8: Generate report
|
||||
original_size=$(z3ed rom info --snapshot "pre-optimization" | jq '.size')
|
||||
optimized_size=$(z3ed rom info | jq '.size')
|
||||
saved=$((original_size - optimized_size))
|
||||
percent=$((saved * 100 / original_size))
|
||||
|
||||
cat > optimization_report.md << EOF
|
||||
# ROM Optimization Report
|
||||
|
||||
## Summary
|
||||
- Original Size: $original_size bytes
|
||||
- Optimized Size: $optimized_size bytes
|
||||
- Space Saved: $saved bytes ($percent%)
|
||||
|
||||
## Optimizations Applied
|
||||
$(z3ed optimize list-applied --format markdown)
|
||||
|
||||
## Validation Results
|
||||
$(z3ed test results --suite optimization_validation.yaml --format markdown)
|
||||
EOF
|
||||
|
||||
echo "Optimization complete! Report saved to optimization_report.md"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Create Snapshots
|
||||
Before any major operation:
|
||||
```bash
|
||||
z3ed rom snapshot --name "descriptive_name" --compress
|
||||
```
|
||||
|
||||
### 2. Use Dry-Run for Dangerous Operations
|
||||
```bash
|
||||
z3ed editor batch --script changes.json --dry-run
|
||||
```
|
||||
|
||||
### 3. Validate After Changes
|
||||
```bash
|
||||
z3ed rom validate
|
||||
z3ed test run --quick
|
||||
```
|
||||
|
||||
### 4. Document Your Workflows
|
||||
```bash
|
||||
# Generate documentation from your scripts
|
||||
z3ed docs generate --from-script my_workflow.sh --output workflow_docs.md
|
||||
```
|
||||
|
||||
### 5. Use AI for Review
|
||||
```bash
|
||||
z3ed ai review --changes . --criteria "correctness,performance,style"
|
||||
```
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Issue: Command Not Found
|
||||
```bash
|
||||
# Verify z3ed is in PATH
|
||||
which z3ed
|
||||
|
||||
# Or use full path
|
||||
/usr/local/bin/z3ed --version
|
||||
```
|
||||
|
||||
### Issue: ROM Won't Load
|
||||
```bash
|
||||
# Check ROM validity
|
||||
z3ed rom validate --file suspicious.sfc --verbose
|
||||
|
||||
# Try with different region
|
||||
z3ed rom load --file rom.sfc --region USA
|
||||
```
|
||||
|
||||
### Issue: Test Failures
|
||||
```bash
|
||||
# Run with verbose output
|
||||
z3ed test run --verbose --filter failing_test
|
||||
|
||||
# Generate detailed report
|
||||
z3ed test debug --test failing_test --output debug_report.json
|
||||
```
|
||||
|
||||
### Issue: Network Connection Failed
|
||||
```bash
|
||||
# Test connection
|
||||
z3ed network ping --host localhost --port 8080
|
||||
|
||||
# Use fallback mode
|
||||
z3ed --offline --cache-only
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Explore the [Z3ED Command Reference](z3ed-command-reference.md)
|
||||
- Read the [API Documentation](../reference/api/)
|
||||
- Join the [YAZE Discord](https://discord.gg/yaze) for support
|
||||
- Contribute your workflows to the [Examples Repository](https://github.com/yaze/z3ed-examples)
|
||||
@@ -5,50 +5,52 @@
|
||||
|
||||
# YAZE Documentation
|
||||
|
||||
YAZE documentation now focuses on concise, Doxygen-friendly sections. Use the categories
|
||||
below for human-readable guides and reference material. Internal planning, AI agent playbooks,
|
||||
and research notes were moved to `docs/internal/` so the public docs stay focused.
|
||||
|
||||
## Overview
|
||||
- [Getting Started](overview/getting-started.md)
|
||||
|
||||
## Build & Tooling
|
||||
- [Build Quick Reference](build/quick-reference.md)
|
||||
- [Build From Source](build/build-from-source.md)
|
||||
- [Platform Compatibility](build/platform-compatibility.md)
|
||||
- [CMake Presets](build/presets.md)
|
||||
- [Build Troubleshooting](build/troubleshooting.md)
|
||||
|
||||
## Usage Guides
|
||||
- [Dungeon Editor](usage/dungeon-editor.md)
|
||||
- [Overworld Loading](usage/overworld-loading.md)
|
||||
- [z3ed CLI](usage/z3ed-cli.md)
|
||||
- [Examples & Recipes](examples/)
|
||||
|
||||
## Developer Guides
|
||||
- [Architecture Overview](developer/architecture.md)
|
||||
- [Dependency Architecture](developer/dependency-architecture.md)
|
||||
- [Git Workflow](developer/git-workflow.md)
|
||||
- [Networking Overview](developer/networking.md)
|
||||
- [Testing Guide](developer/testing-guide.md)
|
||||
- [Testing Without ROMs](developer/testing-without-roms.md)
|
||||
- [Debugging Guide](developer/debugging-guide.md)
|
||||
- [Debug Flags](developer/debug-flags.md)
|
||||
- [Assembler Style Guide](developer/asm-style-guide.md)
|
||||
- [API Reference](developer/api-reference.md)
|
||||
- [Emulator Development Guide](developer/emulator-development-guide.md)
|
||||
- [Canvas System](developer/canvas-system.md)
|
||||
- [Palette System Overview](developer/palette-system-overview.md)
|
||||
- [Tile16 Palette System](developer/tile16-palette-system.md)
|
||||
- [Overworld Entity System](developer/overworld-entity-system.md)
|
||||
- [GUI Consistency Guide](developer/gui-consistency-guide.md)
|
||||
- [AI-Assisted Development](developer/ai-assisted-development.md)
|
||||
|
||||
## Reference
|
||||
- [ROM Reference](reference/rom-reference.md)
|
||||
- [Changelog](reference/changelog.md)
|
||||
Welcome to the YAZE documentation. This site covers installation, usage, and development of the YAZE ROM editor for The Legend of Zelda: A Link to the Past.
|
||||
|
||||
---
|
||||
|
||||
Need editor playbooks, refactors, or AI workflows? Head over to [`docs/internal/`](../internal/README.md)
|
||||
for internal documentation that stays out of the public Doxygen site.
|
||||
## Quick Links
|
||||
|
||||
| Goal | Start Here |
|
||||
|------|------------|
|
||||
| **Get started** | [Getting Started](overview/getting-started.md) |
|
||||
| **Build from source** | [Build Quick Reference](build/quick-reference.md) |
|
||||
| **Understand the code** | [Architecture Overview](developer/architecture.md) |
|
||||
| **Fix build issues** | [Build Troubleshooting](build/troubleshooting.md) |
|
||||
| **Use the CLI** | [z3ed CLI Guide](usage/z3ed-cli.md) |
|
||||
|
||||
---
|
||||
|
||||
## Documentation Sections
|
||||
|
||||
### Getting Started
|
||||
- [Getting Started](overview/getting-started.md)
|
||||
|
||||
### Build and Tooling
|
||||
- [Build Quick Reference](build/quick-reference.md) - Essential build commands
|
||||
- [Build from Source](build/build-from-source.md) - Full build guide
|
||||
- [CMake Presets](build/presets.md) - Preset reference
|
||||
- [Platform Compatibility](build/platform-compatibility.md) - OS-specific notes
|
||||
- [Build Troubleshooting](build/troubleshooting.md) - Common issues
|
||||
|
||||
### Usage Guides
|
||||
- [Dungeon Editor](usage/dungeon-editor.md) - Room editing guide
|
||||
- [z3ed CLI](usage/z3ed-cli.md) - Command-line interface
|
||||
- [Web App](usage/web-app.md) - Browser-based editor (preview)
|
||||
- [Overworld Loading](usage/overworld-loading.md) - Technical reference
|
||||
|
||||
### Developer Guides
|
||||
- [Architecture Overview](developer/architecture.md) - System design
|
||||
- [Testing Guide](developer/testing-guide.md) - Test organization and execution
|
||||
- [Debugging Guide](developer/debugging-guide.md) - Debugging workflows
|
||||
- [Canvas System](developer/canvas-system.md) - UI canvas reference
|
||||
- [Palette System](developer/palette-system-overview.md) - Color handling
|
||||
|
||||
### Reference
|
||||
- [ROM Reference](reference/rom-reference.md) - ROM data structures
|
||||
- [z3ed Command Reference](reference/z3ed-command-reference.md) - CLI commands
|
||||
- [Changelog](reference/changelog.md) - Version history
|
||||
|
||||
---
|
||||
|
||||
**Internal Documentation:** Development plans, agent workflows, and architecture deep-dives are in [docs/internal/](../internal/README.md).
|
||||
|
||||
@@ -1,68 +1,77 @@
|
||||
# Getting Started
|
||||
# Getting Started with YAZE
|
||||
|
||||
This software allows you to modify "The Legend of Zelda: A Link to the Past" (US or JP) ROMs. It is built for compatibility with ZScream projects and designed to be cross-platform.
|
||||
YAZE is a ROM editor for "The Legend of Zelda: A Link to the Past" (US and JP versions). It provides a full-featured GUI editor, integrated SNES emulator, and AI-powered command-line tools.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Download** the latest release for your platform from the [releases page](https://github.com/scawful/yaze/releases).
|
||||
2. **Load ROM** via `File > Open ROM`.
|
||||
3. **Select an Editor** from the main toolbar (e.g., Overworld, Dungeon, Graphics).
|
||||
4. **Make Changes** and save your project.
|
||||
1. **Download** the latest release for your platform from the [GitHub Releases page](https://github.com/scawful/yaze/releases)
|
||||
2. **Launch** the application and load your ROM via `File > Open ROM`
|
||||
3. **Choose an Editor** from the toolbar (Overworld, Dungeon, Graphics, etc.)
|
||||
4. **Edit** your ROM and save your changes
|
||||
|
||||
> Building from source or enabling AI tooling? Use the
|
||||
> [Build & Test Quick Reference](../build/quick-reference.md) for the canonical commands and presets.
|
||||
> **Building from source?** See the [Build and Test Quick Reference](../build/quick-reference.md).
|
||||
|
||||
## General Tips
|
||||
---
|
||||
|
||||
- **Experiment Flags**: Enable or disable new features in `File > Options > Experiment Flags`.
|
||||
- **Backup Files**: Enabled by default. Each save creates a timestamped backup of your ROM.
|
||||
- **Extensions**: Load custom tools via the `Extensions` menu (C library and Python module support is planned).
|
||||
## Tips
|
||||
|
||||
## Feature Status
|
||||
- **Backups**: Automatic backups are enabled by default. Each save creates a timestamped backup.
|
||||
- **Experiment Flags**: Try new features via `File > Options > Experiment Flags`.
|
||||
- **Extensions**: Load custom tools from the `Extensions` menu (plugin system under development).
|
||||
|
||||
| Feature | State | Notes |
|
||||
|---|---|---|
|
||||
| Overworld Editor | Stable | Supports vanilla and ZSCustomOverworld v2/v3 projects. |
|
||||
| Dungeon Editor | Experimental | Requires extensive manual testing before production use. |
|
||||
| Tile16 Editor | Experimental | Palette and tile workflows are still being tuned. |
|
||||
| Palette Editor | Stable | Reference implementation for palette utilities. |
|
||||
| Graphics Editor | Experimental | Rendering pipeline under active refactor. |
|
||||
| Sprite Editor | Experimental | Card/UI patterns mid-migration. |
|
||||
| Message Editor | Stable | Re-test after recent palette fixes. |
|
||||
| Hex Editor | Stable | Direct ROM editing utility. |
|
||||
| Asar Patching | Stable | Wraps the bundled Asar assembler. |
|
||||
---
|
||||
|
||||
## Command-Line Interface (`z3ed`)
|
||||
## Editor Status
|
||||
|
||||
`z3ed` provides scripted access to the same ROM editors.
|
||||
| Editor | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| Overworld | Stable | Full support for vanilla and ZSCustomOverworld v2/v3 |
|
||||
| Dungeon | Stable | Room editing, objects, sprites, palettes |
|
||||
| Palette | Stable | Reference implementation for palette utilities |
|
||||
| Message | Stable | Text and dialogue editing |
|
||||
| Hex | Stable | Direct ROM byte editing |
|
||||
| Asar Patching | Stable | Integrated Asar assembler |
|
||||
| Graphics | Stable | Tile and sprite graphics editing |
|
||||
| Sprite | Stable | Vanilla and custom sprite editing |
|
||||
| Music | Experimental | Tracker and instrument editing |
|
||||
|
||||
### AI Agent Chat
|
||||
Chat with an AI to perform edits using natural language.
|
||||
---
|
||||
|
||||
## Command-Line Interface (z3ed)
|
||||
|
||||
The `z3ed` CLI provides scriptable access to ROM editing capabilities.
|
||||
|
||||
### AI Chat
|
||||
|
||||
```bash
|
||||
# Start an interactive chat session with the AI agent
|
||||
z3ed agent chat --rom zelda3.sfc
|
||||
```
|
||||
> **Prompt:** "What sprites are in dungeon 2?"
|
||||
Example prompt: "What sprites are in dungeon 2?"
|
||||
|
||||
### Resource Inspection
|
||||
Directly query ROM data.
|
||||
### ROM Inspection
|
||||
|
||||
```bash
|
||||
# List all sprites in the Eastern Palace (dungeon 2)
|
||||
# List sprites in Eastern Palace
|
||||
z3ed dungeon list-sprites --rom zelda3.sfc --dungeon 2
|
||||
|
||||
# Get information about a specific overworld map area
|
||||
# Describe overworld map
|
||||
z3ed overworld describe-map --rom zelda3.sfc --map 80
|
||||
```
|
||||
|
||||
### Patching
|
||||
Apply assembly patches using the integrated Asar assembler.
|
||||
|
||||
```bash
|
||||
# Apply an assembly patch to the ROM
|
||||
z3ed asar patch.asm --rom zelda3.sfc
|
||||
```
|
||||
|
||||
## Extending Functionality
|
||||
For more details, see the [z3ed CLI Guide](../usage/z3ed-cli.md).
|
||||
|
||||
YAZE exports a C API that is still evolving. Treat it as experimental and expect breaking changes while the plugin system is built out.
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Dungeon Editor Guide](../usage/dungeon-editor.md)** - Learn dungeon room editing
|
||||
- **[z3ed CLI Guide](../usage/z3ed-cli.md)** - Master the command-line interface
|
||||
- **[Architecture Overview](../developer/architecture.md)** - Understand the codebase
|
||||
|
||||
50
docs/public/reference/SAVE_STATE_FORMAT.md
Normal file
50
docs/public/reference/SAVE_STATE_FORMAT.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Save State Format (v2)
|
||||
|
||||
This documents the chunked save-state format introduced in `Snes::saveState/loadState` (state file version 2).
|
||||
|
||||
## Goals
|
||||
- Forward/backward compatible: per-file header + per-chunk versioning.
|
||||
- Corruption-resilient: CRC on each chunk; size caps to avoid runaway allocations.
|
||||
- Explicit layout: avoid raw struct dumps to sidestep padding/endianness issues.
|
||||
|
||||
## File Structure
|
||||
```
|
||||
uint32 magic = 'YAZE' (0x59415A45)
|
||||
uint32 version = 2
|
||||
repeat until EOF:
|
||||
Chunk {
|
||||
uint32 tag // ASCII packed: 'SNES', 'CPU ', 'PPU ', 'APU '
|
||||
uint32 version // per-chunk version; currently 1 for all
|
||||
uint32 size // payload size in bytes (capped at 16 MiB)
|
||||
uint32 crc32 // CRC-32 of payload
|
||||
uint8 payload[size]
|
||||
}
|
||||
```
|
||||
|
||||
## Chunk Payloads (v1)
|
||||
- `SNES`: Core machine state (WRAM, timers, IRQ/NMI flags, latched ports, timers, etc.).
|
||||
- `CPU `: CPU registers/flags + breakpoint list (capped to 1024 entries).
|
||||
- `PPU `: PPU registers, VRAM/CGRAM/OAM, layer/window/bg structs written field-by-field.
|
||||
- `APU `: APU registers, ports, timers, and 64K ARAM (capped) plus DSP/SPC700 state.
|
||||
|
||||
## Compatibility
|
||||
- Legacy v1 flat saves (no magic) are still loadable: the loader falls back if the magic/version header is missing. They do not carry CRCs and remain best-effort only.
|
||||
- Host endianness: serialization assumes little-endian hosts; load/save will fail fast otherwise.
|
||||
|
||||
## Validation & Errors
|
||||
- Size guard: any chunk `size > 16 MiB` is rejected.
|
||||
- CRC guard: mismatched CRC rejects the load to avoid partial/dirty state.
|
||||
- Missing required chunks (`SNES`, `CPU `, `PPU `, `APU `) rejects the load.
|
||||
- Streams are checked for `fail()` after every read/write; callers receive `absl::Status`.
|
||||
|
||||
## Extending
|
||||
- Add a new chunk tag and bump its per-chunk `version` only. Keep `file version` stable unless the top-level format changes.
|
||||
- Keep payloads explicit (no raw struct dumps). Write scalars/arrays with defined width and order.
|
||||
- If you add new fields to an existing chunk, prefer:
|
||||
1. Extending the payload and bumping that chunk’s version.
|
||||
2. Keeping old fields first so older loaders can short-circuit safely.
|
||||
|
||||
## Conventions
|
||||
- Tags use little-endian ASCII packing: `'SNES'` -> `0x53454E53`.
|
||||
- CRC uses `render::CalculateCRC32`.
|
||||
- Max buffer cap mirrors the largest expected subsystem payload (WRAM/ARAM).
|
||||
140
docs/public/reference/SNES_COMPRESSION.md
Normal file
140
docs/public/reference/SNES_COMPRESSION.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# SNES Compression Format (ALttP)
|
||||
|
||||
Decompression algorithm used in A Link to the Past, documented from ZSpriteMaker.
|
||||
|
||||
**Source:** `~/Documents/Zelda/Editors/ZSpriteMaker-1/ZSpriteMaker/Utils.cs`
|
||||
|
||||
## Command Format
|
||||
|
||||
Each compressed block starts with a command byte:
|
||||
|
||||
```
|
||||
Normal Command (when upper 3 bits != 0b111):
|
||||
┌─────────────────────────────────────┐
|
||||
│ 7 6 5 │ 4 3 2 1 0 │
|
||||
│ CMD │ LENGTH (0-31) │
|
||||
└─────────────────────────────────────┘
|
||||
Length = (byte & 0x1F) + 1
|
||||
|
||||
Expanded Command (when upper 3 bits == 0b111):
|
||||
┌─────────────────────────────────────┬──────────────┐
|
||||
│ 7 6 5 │ 4 3 2 │ 1 0 │ Byte 2 │
|
||||
│ 0b111 │ CMD │ LENGTH_HI │ LENGTH_LO │
|
||||
└─────────────────────────────────────┴──────────────┘
|
||||
Length = ((byte & 0x03) << 8 | nextByte) + 1
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| CMD | Name | Description |
|
||||
|-----|------|-------------|
|
||||
| 0 | Direct Copy | Copy `length` bytes directly from ROM to output |
|
||||
| 1 | Byte Fill | Repeat single byte `length` times |
|
||||
| 2 | Word Fill | Repeat 2-byte word `length/2` times |
|
||||
| 3 | Increasing Fill | Write byte, increment, repeat `length` times |
|
||||
| 4 | Repeat | Copy `length` bytes from earlier in output buffer |
|
||||
|
||||
## Terminator
|
||||
|
||||
`0xFF` byte terminates decompression.
|
||||
|
||||
## C++ Implementation
|
||||
|
||||
```cpp
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
std::vector<uint8_t> decompress_alttp(const uint8_t* rom, int pos) {
|
||||
std::vector<uint8_t> buffer(0x1000, 0);
|
||||
int bufferPos = 0;
|
||||
|
||||
while (true) {
|
||||
uint8_t databyte = rom[pos];
|
||||
if (databyte == 0xFF) break; // End marker
|
||||
|
||||
uint8_t cmd;
|
||||
int length;
|
||||
|
||||
if ((databyte & 0xE0) == 0xE0) {
|
||||
// Expanded command
|
||||
cmd = (databyte >> 2) & 0x07;
|
||||
length = ((databyte & 0x03) << 8) | rom[pos + 1];
|
||||
pos += 2;
|
||||
} else {
|
||||
// Normal command
|
||||
cmd = (databyte >> 5) & 0x07;
|
||||
length = databyte & 0x1F;
|
||||
pos += 1;
|
||||
}
|
||||
length += 1; // Minimum length is 1
|
||||
|
||||
switch (cmd) {
|
||||
case 0: // Direct Copy
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[bufferPos++] = rom[pos++];
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // Byte Fill
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[bufferPos++] = rom[pos];
|
||||
}
|
||||
pos += 1;
|
||||
break;
|
||||
|
||||
case 2: // Word Fill
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
buffer[bufferPos++] = rom[pos];
|
||||
buffer[bufferPos++] = rom[pos + 1];
|
||||
}
|
||||
pos += 2;
|
||||
break;
|
||||
|
||||
case 3: // Increasing Fill
|
||||
{
|
||||
uint8_t val = rom[pos];
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[bufferPos++] = val++;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // Repeat from buffer
|
||||
{
|
||||
// Little-endian address
|
||||
int addr = rom[pos] | (rom[pos + 1] << 8);
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[bufferPos++] = buffer[addr++];
|
||||
}
|
||||
pos += 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(bufferPos);
|
||||
return buffer;
|
||||
}
|
||||
```
|
||||
|
||||
## Address Conversion
|
||||
|
||||
```cpp
|
||||
// SNES LoROM to PC file offset
|
||||
inline int snes_to_pc(int addr) {
|
||||
return (addr & 0x7FFF) | ((addr & 0x7F0000) >> 1);
|
||||
}
|
||||
|
||||
// PC file offset to SNES LoROM
|
||||
inline int pc_to_snes(int addr) {
|
||||
return (addr & 0x7FFF) | 0x8000 | ((addr & 0x7F8000) << 1);
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Buffer size is 0x1000 (4KB) - typical for tile/map data
|
||||
- Command 4 (Repeat) uses little-endian address within output buffer
|
||||
- Expanded commands allow lengths up to 1024 bytes
|
||||
- Used for: tile graphics, tilemaps, some sprite data
|
||||
189
docs/public/reference/SNES_GRAPHICS.md
Normal file
189
docs/public/reference/SNES_GRAPHICS.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# SNES Graphics Conversion
|
||||
|
||||
Tile format conversion routines for ALttP graphics, documented from ZSpriteMaker.
|
||||
|
||||
**Source:** `~/Documents/Zelda/Editors/ZSpriteMaker-1/ZSpriteMaker/Utils.cs`
|
||||
|
||||
## SNES Tile Formats
|
||||
|
||||
### 3BPP (3 bits per pixel, 8 colors)
|
||||
- Used for: Sprites, some backgrounds
|
||||
- 24 bytes per 8x8 tile
|
||||
- Planar format: 2 bytes per row (planes 0-1) + 1 byte per row (plane 2)
|
||||
|
||||
### 4BPP (4 bits per pixel, 16 colors)
|
||||
- Used for: Most backgrounds, UI
|
||||
- 32 bytes per 8x8 tile
|
||||
- Planar format: 2 interleaved bitplanes per 16 bytes
|
||||
|
||||
## 3BPP Tile Layout
|
||||
|
||||
```
|
||||
Bytes 0-15: Planes 0 and 1 (interleaved, 2 bytes per row)
|
||||
Bytes 16-23: Plane 2 (1 byte per row)
|
||||
|
||||
Row 0: [Plane0_Row0][Plane1_Row0]
|
||||
Row 1: [Plane0_Row1][Plane1_Row1]
|
||||
...
|
||||
Row 7: [Plane0_Row7][Plane1_Row7]
|
||||
[Plane2_Row0][Plane2_Row1]...[Plane2_Row7]
|
||||
```
|
||||
|
||||
## C++ Implementation: 3BPP to 8BPP
|
||||
|
||||
Converts a sheet of 64 tiles (16x4 arrangement, 128x32 pixels) from 3BPP to indexed 8BPP:
|
||||
|
||||
```cpp
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
constexpr std::array<uint8_t, 8> bitmask = {
|
||||
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
|
||||
};
|
||||
|
||||
// Input: 24 * 64 = 1536 bytes (64 tiles in 3BPP)
|
||||
// Output: 128 * 32 = 4096 bytes (8BPP indexed)
|
||||
std::array<uint8_t, 0x1000> snes_3bpp_to_8bpp(const uint8_t* data) {
|
||||
std::array<uint8_t, 0x1000> sheet{};
|
||||
int index = 0;
|
||||
|
||||
for (int tileRow = 0; tileRow < 4; tileRow++) { // 4 rows of tiles
|
||||
for (int tileCol = 0; tileCol < 16; tileCol++) { // 16 tiles per row
|
||||
int tileOffset = (tileCol + tileRow * 16) * 24; // 24 bytes per tile
|
||||
|
||||
for (int y = 0; y < 8; y++) { // 8 pixel rows
|
||||
uint8_t plane0 = data[tileOffset + y * 2];
|
||||
uint8_t plane1 = data[tileOffset + y * 2 + 1];
|
||||
uint8_t plane2 = data[tileOffset + 16 + y];
|
||||
|
||||
for (int x = 0; x < 8; x++) { // 8 pixels per row
|
||||
uint8_t mask = bitmask[x];
|
||||
uint8_t pixel = 0;
|
||||
|
||||
if (plane0 & mask) pixel |= 1;
|
||||
if (plane1 & mask) pixel |= 2;
|
||||
if (plane2 & mask) pixel |= 4;
|
||||
|
||||
// Calculate output position in 128-wide sheet
|
||||
int outX = tileCol * 8 + x;
|
||||
int outY = tileRow * 8 + y;
|
||||
sheet[outY * 128 + outX] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
```
|
||||
|
||||
## Alternative: Direct Index Calculation
|
||||
|
||||
```cpp
|
||||
std::array<uint8_t, 0x1000> snes_3bpp_to_8bpp_v2(const uint8_t* data) {
|
||||
std::array<uint8_t, 0x1000> sheet{};
|
||||
int index = 0;
|
||||
|
||||
for (int j = 0; j < 4; j++) { // Tile row
|
||||
for (int i = 0; i < 16; i++) { // Tile column
|
||||
for (int y = 0; y < 8; y++) { // Pixel row
|
||||
int base = y * 2 + i * 24 + j * 384;
|
||||
|
||||
uint8_t line0 = data[base];
|
||||
uint8_t line1 = data[base + 1];
|
||||
uint8_t line2 = data[base - y * 2 + y + 16];
|
||||
|
||||
for (uint8_t mask = 0x80; mask > 0; mask >>= 1) {
|
||||
uint8_t pixel = 0;
|
||||
if (line0 & mask) pixel |= 1;
|
||||
if (line1 & mask) pixel |= 2;
|
||||
if (line2 & mask) pixel |= 4;
|
||||
sheet[index++] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sheet;
|
||||
}
|
||||
```
|
||||
|
||||
## Palette Reading
|
||||
|
||||
SNES uses 15-bit BGR color (5 bits per channel):
|
||||
|
||||
```cpp
|
||||
#include <cstdint>
|
||||
|
||||
struct Color {
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
Color read_snes_color(const uint8_t* data, int offset) {
|
||||
uint16_t color = data[offset] | (data[offset + 1] << 8);
|
||||
|
||||
return {
|
||||
static_cast<uint8_t>((color & 0x001F) << 3), // R: bits 0-4
|
||||
static_cast<uint8_t>(((color >> 5) & 0x1F) << 3), // G: bits 5-9
|
||||
static_cast<uint8_t>(((color >> 10) & 0x1F) << 3), // B: bits 10-14
|
||||
255 // A: opaque
|
||||
};
|
||||
}
|
||||
|
||||
// Read full 8-color palette (3BPP)
|
||||
std::array<Color, 8> read_3bpp_palette(const uint8_t* data, int offset) {
|
||||
std::array<Color, 8> palette;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
palette[i] = read_snes_color(data, offset + i * 2);
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
|
||||
// Read full 16-color palette (4BPP)
|
||||
std::array<Color, 16> read_4bpp_palette(const uint8_t* data, int offset) {
|
||||
std::array<Color, 16> palette;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
palette[i] = read_snes_color(data, offset + i * 2);
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
```
|
||||
|
||||
## OAM Tile Positioning
|
||||
|
||||
From ZSpriteMaker's OamTile class - convert tile ID to sheet coordinates:
|
||||
|
||||
```cpp
|
||||
// Tile ID to sprite sheet pixel position
|
||||
// Assumes 16 tiles per row (128 pixels wide sheet)
|
||||
inline int tile_to_sheet_x(uint16_t id) { return (id % 16) * 8; }
|
||||
inline int tile_to_sheet_y(uint16_t id) { return (id / 16) * 8; }
|
||||
|
||||
// Packed OAM format (32-bit)
|
||||
inline uint32_t pack_oam_tile(uint16_t id, uint8_t x, uint8_t y,
|
||||
uint8_t palette, uint8_t priority,
|
||||
bool mirrorX, bool mirrorY) {
|
||||
return (id << 16) |
|
||||
((mirrorY ? 0 : 1) << 31) |
|
||||
((mirrorX ? 0 : 1) << 30) |
|
||||
(priority << 28) |
|
||||
(palette << 25) |
|
||||
(x << 8) |
|
||||
y;
|
||||
}
|
||||
```
|
||||
|
||||
## Sheet Dimensions
|
||||
|
||||
| Format | Tiles | Sheet Size | Bytes/Tile | Total Bytes |
|
||||
|--------|-------|------------|------------|-------------|
|
||||
| 3BPP 64-tile | 16x4 | 128x32 px | 24 | 1,536 |
|
||||
| 4BPP 64-tile | 16x4 | 128x32 px | 32 | 2,048 |
|
||||
| 3BPP 128-tile | 16x8 | 128x64 px | 24 | 3,072 |
|
||||
|
||||
## Integration Notes
|
||||
|
||||
- Color index 0 is typically transparent
|
||||
- SNES sprites use 3BPP (8 colors per palette row)
|
||||
- Background tiles often use 4BPP (16 colors)
|
||||
- ALttP Link sprites: 3BPP, multiple sheets for different states
|
||||
209
docs/public/reference/SYMBOL_FORMAT.md
Normal file
209
docs/public/reference/SYMBOL_FORMAT.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# ALttP Symbol File Format
|
||||
|
||||
Documentation for importing disassembly symbol files into YAZE.
|
||||
|
||||
**Source:** `~/Code/alttp-gigaleak/DISASM/jpdasm/symbols_*.asm`
|
||||
|
||||
## Available Symbol Files
|
||||
|
||||
| File | Contents | Address Range |
|
||||
|------|----------|---------------|
|
||||
| `symbols_wram.asm` | Work RAM labels | $7E0000-$7FFFFF |
|
||||
| `symbols_sram.asm` | Save RAM labels | $700000-$70FFFF |
|
||||
| `symbols_apu.asm` | Audio processor | APU addresses |
|
||||
| `registers.asm` | Hardware registers | $2100-$21FF, $4200-$43FF |
|
||||
|
||||
## File Format
|
||||
|
||||
Simple assembly-style symbol definitions:
|
||||
|
||||
```asm
|
||||
; Comment lines start with semicolon
|
||||
SYMBOL_NAME = $AABBCC ; Optional inline comment
|
||||
|
||||
; Multi-line comments for documentation
|
||||
; LENGTH: 0x10
|
||||
BLOCK_START = $7E0000
|
||||
```
|
||||
|
||||
## Symbol Naming Conventions
|
||||
|
||||
### Suffixes
|
||||
| Suffix | Meaning |
|
||||
|--------|---------|
|
||||
| `L` | Low byte of 16-bit value |
|
||||
| `H` | High byte of 16-bit value |
|
||||
| `U` | Unused high byte |
|
||||
| `Q` | Queue (for NMI updates) |
|
||||
|
||||
### Prefixes
|
||||
| Prefix | Category |
|
||||
|--------|----------|
|
||||
| `LINK_` | Player state |
|
||||
| `SPR_` | Sprite/enemy |
|
||||
| `NMI_` | NMI handler |
|
||||
| `OAM_` | OAM buffer |
|
||||
| `UNUSED_` | Free RAM |
|
||||
|
||||
### Bitfield Documentation
|
||||
```asm
|
||||
; a - found to the north west
|
||||
; b - found to the north east
|
||||
; c - found to the south west
|
||||
; d - found to the south east
|
||||
; Bitfield: abcd....
|
||||
TILE_DIRECTION = $7E000A
|
||||
```
|
||||
|
||||
## Key Symbols (Quick Reference)
|
||||
|
||||
### Game State ($7E0010-$7E001F)
|
||||
```asm
|
||||
MODE = $7E0010 ; Main game mode
|
||||
SUBMODE = $7E0011 ; Sub-mode within mode
|
||||
LAG = $7E0012 ; NMI sync flag
|
||||
INIDISPQ = $7E0013 ; Display brightness queue
|
||||
NMISTRIPES = $7E0014 ; Tilemap update flag
|
||||
NMICGRAM = $7E0015 ; Palette update flag
|
||||
```
|
||||
|
||||
### Player Position (typical)
|
||||
```asm
|
||||
LINK_X_LO = $7E0022 ; Link X position (low)
|
||||
LINK_X_HI = $7E0023 ; Link X position (high)
|
||||
LINK_Y_LO = $7E0020 ; Link Y position (low)
|
||||
LINK_Y_HI = $7E0021 ; Link Y position (high)
|
||||
LINK_LAYER = $7E00EE ; Current layer (0=BG1, 1=BG2)
|
||||
```
|
||||
|
||||
### Room/Dungeon
|
||||
```asm
|
||||
ROOM_ID = $7E00A0 ; Current room number
|
||||
DUNGEON_ID = $7E040C ; Current dungeon
|
||||
```
|
||||
|
||||
## C++ Parser
|
||||
|
||||
```cpp
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
struct Symbol {
|
||||
std::string name;
|
||||
uint32_t address;
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, Symbol> parse_symbols(const std::string& path) {
|
||||
std::unordered_map<uint32_t, Symbol> symbols;
|
||||
std::ifstream file(path);
|
||||
std::string line;
|
||||
|
||||
// Pattern: NAME = $ADDRESS ; comment
|
||||
std::regex pattern(R"(^(\w+)\s*=\s*\$([0-9A-Fa-f]+)\s*(?:;\s*(.*))?$)");
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
// Skip pure comment lines
|
||||
if (line.empty() || line[0] == ';') continue;
|
||||
|
||||
std::smatch match;
|
||||
if (std::regex_search(line, match, pattern)) {
|
||||
Symbol sym;
|
||||
sym.name = match[1].str();
|
||||
sym.address = std::stoul(match[2].str(), nullptr, 16);
|
||||
sym.comment = match[3].str();
|
||||
|
||||
symbols[sym.address] = sym;
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// Get symbol name for address (returns empty if not found)
|
||||
std::string lookup_symbol(const std::unordered_map<uint32_t, Symbol>& syms,
|
||||
uint32_t addr) {
|
||||
auto it = syms.find(addr);
|
||||
return (it != syms.end()) ? it->second.name : "";
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Ideas
|
||||
|
||||
### Hex Editor Enhancement
|
||||
```cpp
|
||||
// Display symbol alongside address
|
||||
void draw_hex_line(uint32_t addr, const uint8_t* data) {
|
||||
std::string sym = lookup_symbol(symbols, addr);
|
||||
if (!sym.empty()) {
|
||||
printf("%06X %-20s ", addr, sym.c_str());
|
||||
} else {
|
||||
printf("%06X %-20s ", addr, "");
|
||||
}
|
||||
// ... draw hex bytes
|
||||
}
|
||||
```
|
||||
|
||||
### Disassembly View
|
||||
```cpp
|
||||
// Replace addresses with symbols in ASM output
|
||||
std::string format_operand(uint32_t addr) {
|
||||
std::string sym = lookup_symbol(symbols, addr);
|
||||
if (!sym.empty()) {
|
||||
return sym;
|
||||
}
|
||||
return "$" + to_hex(addr);
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Watcher
|
||||
```cpp
|
||||
struct Watch {
|
||||
std::string label;
|
||||
uint32_t address;
|
||||
uint8_t size; // 1, 2, or 3 bytes
|
||||
};
|
||||
|
||||
// Auto-populate watches from symbol file
|
||||
std::vector<Watch> create_watches_from_symbols() {
|
||||
std::vector<Watch> watches;
|
||||
|
||||
// Key game state
|
||||
watches.push_back({"Mode", 0x7E0010, 1});
|
||||
watches.push_back({"Submode", 0x7E0011, 1});
|
||||
watches.push_back({"Link X", 0x7E0022, 2});
|
||||
watches.push_back({"Link Y", 0x7E0020, 2});
|
||||
watches.push_back({"Room", 0x7E00A0, 2});
|
||||
|
||||
return watches;
|
||||
}
|
||||
```
|
||||
|
||||
## Free RAM Discovery
|
||||
|
||||
Symbol files mark unused RAM:
|
||||
```asm
|
||||
; FREE RAM: 0x20
|
||||
UNUSED_7E0500 = $7E0500
|
||||
UNUSED_7E0501 = $7E0501
|
||||
; ...
|
||||
|
||||
; BIG FREE RAM
|
||||
UNUSED_7E1000 = $7E1000 ; Large block available
|
||||
```
|
||||
|
||||
Parse for `FREE RAM` comments to find available space for ROM hacks.
|
||||
|
||||
## File Locations
|
||||
|
||||
```
|
||||
~/Code/alttp-gigaleak/DISASM/jpdasm/
|
||||
├── symbols_wram.asm # Work RAM ($7E)
|
||||
├── symbols_sram.asm # Save RAM ($70)
|
||||
├── symbols_apu.asm # Audio
|
||||
├── registers.asm # Hardware registers
|
||||
└── registers_spc.asm # SPC700 registers
|
||||
```
|
||||
213
docs/public/reference/ZSM_FORMAT.md
Normal file
213
docs/public/reference/ZSM_FORMAT.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# ZSM File Format Specification
|
||||
|
||||
ZSpriteMaker Project File format (`.zsm`) - used by ZSpriteMaker for ALttP custom sprites.
|
||||
|
||||
**Source:** `~/Documents/Zelda/Editors/ZSpriteMaker-1/`
|
||||
|
||||
## Format Overview
|
||||
|
||||
Binary file format using .NET BinaryWriter/BinaryReader conventions:
|
||||
- Strings: Length-prefixed (7-bit encoded length + UTF-8 bytes)
|
||||
- Integers: Little-endian 32-bit
|
||||
- Booleans: Single byte (0x00 = false, 0x01 = true)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ANIMATIONS SECTION │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ int32 animationCount │
|
||||
│ for each animation: │
|
||||
│ string name (length-prefixed) │
|
||||
│ byte frameStart │
|
||||
│ byte frameEnd │
|
||||
│ byte frameSpeed │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ FRAMES SECTION │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ int32 frameCount │
|
||||
│ for each frame: │
|
||||
│ int32 tileCount │
|
||||
│ for each tile: │
|
||||
│ uint16 id (tile index in sheet) │
|
||||
│ byte palette (0-7) │
|
||||
│ bool mirrorX │
|
||||
│ bool mirrorY │
|
||||
│ byte priority (0-3, default 3) │
|
||||
│ bool size (false=8x8, true=16x16) │
|
||||
│ byte x (0-251) │
|
||||
│ byte y (0-219) │
|
||||
│ byte z (depth/layer) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ SPRITE PROPERTIES (20 booleans) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ bool blockable │
|
||||
│ bool canFall │
|
||||
│ bool collisionLayer │
|
||||
│ bool customDeath │
|
||||
│ bool damageSound │
|
||||
│ bool deflectArrows │
|
||||
│ bool deflectProjectiles │
|
||||
│ bool fast │
|
||||
│ bool harmless │
|
||||
│ bool impervious │
|
||||
│ bool imperviousArrow │
|
||||
│ bool imperviousMelee │
|
||||
│ bool interaction │
|
||||
│ bool isBoss │
|
||||
│ bool persist │
|
||||
│ bool shadow │
|
||||
│ bool smallShadow │
|
||||
│ statis (stasis) │
|
||||
│ bool statue │
|
||||
│ bool waterSprite │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ SPRITE STATS (6 bytes) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ byte prize (drop item ID) │
|
||||
│ byte palette (sprite palette) │
|
||||
│ byte oamNbr (OAM slot count) │
|
||||
│ byte hitbox (collision box ID) │
|
||||
│ byte health │
|
||||
│ byte damage │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ USER ROUTINES SECTION (optional) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ string spriteName (length-prefixed) │
|
||||
│ int32 routineCount │
|
||||
│ for each routine: │
|
||||
│ string name (e.g., "Long Main") │
|
||||
│ string code (ASM code) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ SPRITE ID (optional) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ string spriteId (hex string) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Types
|
||||
|
||||
### OamTile
|
||||
```cpp
|
||||
struct OamTile {
|
||||
uint16_t id; // Tile index in sprite sheet (0-511)
|
||||
uint8_t palette; // Palette index (0-7)
|
||||
bool mirrorX; // Horizontal flip
|
||||
bool mirrorY; // Vertical flip
|
||||
uint8_t priority; // BG priority (0-3)
|
||||
bool size; // false=8x8, true=16x16
|
||||
uint8_t x; // X position (0-251)
|
||||
uint8_t y; // Y position (0-219)
|
||||
uint8_t z; // Z depth for sorting
|
||||
};
|
||||
|
||||
// Tile sheet position from ID:
|
||||
// sheet_x = (id % 16) * 8
|
||||
// sheet_y = (id / 16) * 8
|
||||
```
|
||||
|
||||
### AnimationGroup
|
||||
```cpp
|
||||
struct AnimationGroup {
|
||||
std::string name; // Animation name
|
||||
uint8_t frameStart; // First frame index
|
||||
uint8_t frameEnd; // Last frame index
|
||||
uint8_t frameSpeed; // Frames per tick
|
||||
};
|
||||
```
|
||||
|
||||
### Frame
|
||||
```cpp
|
||||
struct Frame {
|
||||
std::vector<OamTile> tiles;
|
||||
};
|
||||
```
|
||||
|
||||
## Sprite Properties Bitfield (Alternative)
|
||||
|
||||
The 20 boolean properties could be packed as bitfield:
|
||||
```cpp
|
||||
enum SpriteFlags : uint32_t {
|
||||
BLOCKABLE = 1 << 0,
|
||||
CAN_FALL = 1 << 1,
|
||||
COLLISION_LAYER = 1 << 2,
|
||||
CUSTOM_DEATH = 1 << 3,
|
||||
DAMAGE_SOUND = 1 << 4,
|
||||
DEFLECT_ARROWS = 1 << 5,
|
||||
DEFLECT_PROJECTILES = 1 << 6,
|
||||
FAST = 1 << 7,
|
||||
HARMLESS = 1 << 8,
|
||||
IMPERVIOUS = 1 << 9,
|
||||
IMPERVIOUS_ARROW = 1 << 10,
|
||||
IMPERVIOUS_MELEE = 1 << 11,
|
||||
INTERACTION = 1 << 12,
|
||||
IS_BOSS = 1 << 13,
|
||||
PERSIST = 1 << 14,
|
||||
SHADOW = 1 << 15,
|
||||
SMALL_SHADOW = 1 << 16,
|
||||
STASIS = 1 << 17,
|
||||
STATUE = 1 << 18,
|
||||
WATER_SPRITE = 1 << 19,
|
||||
};
|
||||
```
|
||||
|
||||
## Default User Routines
|
||||
|
||||
New projects include three template routines:
|
||||
1. **Long Main** - Main sprite loop (`TemplateLongMain.asm`)
|
||||
2. **Sprite Prep** - Initialization (`TemplatePrep.asm`)
|
||||
3. **Sprite Draw** - Rendering (`TemplateDraw.asm`)
|
||||
|
||||
## Related Formats
|
||||
|
||||
### ZSPR (ALttP Randomizer Format)
|
||||
Different format used by ALttP Randomizer for Link sprite replacements.
|
||||
- Magic: `ZSPR` (4 bytes)
|
||||
- Contains: sprite sheet, palette, glove colors, metadata
|
||||
- Spec: https://github.com/Zarby89/ZScreamRandomizer
|
||||
|
||||
### ZSM vs ZSPR
|
||||
| Feature | ZSM | ZSPR |
|
||||
|---------|-----|------|
|
||||
| Purpose | Custom enemy/NPC sprites | Link sprite replacement |
|
||||
| Contains ASM | Yes (routines) | No |
|
||||
| Animation data | Yes | No (uses ROM animations) |
|
||||
| Properties | Sprite behavior flags | Metadata only |
|
||||
| Editor | ZSpriteMaker | SpriteSomething, others |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Reading ZSM in C++
|
||||
```cpp
|
||||
// .NET BinaryReader string format:
|
||||
std::string read_dotnet_string(std::istream& is) {
|
||||
uint32_t length = 0;
|
||||
uint8_t byte;
|
||||
int shift = 0;
|
||||
do {
|
||||
is.read(reinterpret_cast<char*>(&byte), 1);
|
||||
length |= (byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
|
||||
std::string result(length, '\0');
|
||||
is.read(&result[0], length);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Validation
|
||||
- Frame count typically 0-255 (byte range in UI)
|
||||
- Tile positions clamped: x < 252, y < 220
|
||||
- Palette 0-7
|
||||
- Priority 0-3
|
||||
|
||||
## Source Files
|
||||
|
||||
From `~/Documents/Zelda/Editors/ZSpriteMaker-1/ZSpriteMaker/`:
|
||||
- `MainWindow.xaml.cs:323-419` - Save_Command (write format)
|
||||
- `MainWindow.xaml.cs:209-319` - Open_Command (read format)
|
||||
- `OamTile.cs` - Tile data structure
|
||||
- `Frame.cs` - Frame container
|
||||
- `AnimationGroup.cs` - Animation definition
|
||||
@@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.1 (December 2025)
|
||||
|
||||
### Overworld Editor Fixes
|
||||
|
||||
**Vanilla ROM Corruption Fix**:
|
||||
- Fixed critical bug where save functions wrote to custom ASM address space (0x140000+) for ALL ROMs without version checking
|
||||
- `SaveAreaSpecificBGColors()`, `SaveCustomOverworldASM()`, and `SaveDiggableTiles()` now properly check ROM version before writing
|
||||
- Vanilla and v1 ROMs are no longer corrupted by writes to ZSCustomOverworld address space
|
||||
- Added `OverworldVersionHelper` with `SupportsCustomBGColors()` and `SupportsAreaEnum()` methods
|
||||
|
||||
**Toolbar UI Improvements**:
|
||||
- Increased button widths from 30px to 40px for comfortable touch targets
|
||||
- Added version badge showing "Vanilla", "v2", or "v3" ROM version with color coding
|
||||
- Added "Upgrade" button for applying ZSCustomOverworld ASM patch to vanilla ROMs
|
||||
- Improved panel toggle button spacing and column layout
|
||||
|
||||
### Testing Infrastructure
|
||||
|
||||
**ROM Auto-Discovery**:
|
||||
- Tests now automatically discover ROMs in common locations (roms/, ../roms/, etc.)
|
||||
- Searches for common filenames: zelda3.sfc, alttp_vanilla.sfc, vanilla.sfc
|
||||
- Environment variable `YAZE_TEST_ROM_PATH` still takes precedence if set
|
||||
|
||||
**Overworld Regression Tests**:
|
||||
- Added 9 new regression tests for save function version checks
|
||||
- Tests verify vanilla/v1/v2/v3 ROM handling for all version-gated save functions
|
||||
- Version feature matrix validation tests added
|
||||
|
||||
### Logging & Diagnostics
|
||||
- Added CLI controls for log level/categories and console mirroring (`--log_level`, `--log_categories`, `--log_to_console`); `--debug` now force-enables console logging at debug level.
|
||||
- Startup logging now reports the resolved level, categories, and log file destination for easier reproducibility.
|
||||
|
||||
### Editor & Panel Launch Controls
|
||||
- `--open_panels` matching is case-insensitive and accepts both display names and stable panel IDs (e.g., `dungeon.room_list`, `Room 105`, `welcome`, `dashboard`).
|
||||
- New startup visibility overrides (`--startup_welcome`, `--startup_dashboard`, `--startup_sidebar`) let you force panels to show/hide on launch for automation or demos.
|
||||
- Welcome and dashboard behavior is coordinated through the UI layer so CLI overrides and in-app toggles stay in sync.
|
||||
|
||||
### Documentation & Testing
|
||||
- Debugging guides refreshed with the new logging filters and startup panel controls.
|
||||
- Startup flag reference and dungeon editor guide now use panel terminology and up-to-date CLI examples for automation setups.
|
||||
|
||||
---
|
||||
|
||||
## 0.3.9 (November 2025)
|
||||
|
||||
### AI Agent Infrastructure
|
||||
|
||||
@@ -257,5 +257,17 @@ Tile Data:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: October 13, 2025
|
||||
## Additional Format Documentation
|
||||
|
||||
For more detailed format specifications, see:
|
||||
|
||||
- [SNES Graphics Format](SNES_GRAPHICS.md) - Tile and sprite format specifications
|
||||
- [SNES Compression](SNES_COMPRESSION.md) - Detailed LC-LZ2 compression algorithm
|
||||
- [Symbol Format](SYMBOL_FORMAT.md) - Assembler symbol file format
|
||||
- [ZSM Format](ZSM_FORMAT.md) - Music and sound effect format
|
||||
- [Save State Format](SAVE_STATE_FORMAT.md) - Emulator save state specifications
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: November 27, 2025
|
||||
|
||||
|
||||
659
docs/public/reference/z3ed-command-reference.md
Normal file
659
docs/public/reference/z3ed-command-reference.md
Normal file
@@ -0,0 +1,659 @@
|
||||
# Z3ED Command Reference
|
||||
|
||||
Complete command reference for the z3ed CLI tool, including all automation and AI-powered features.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [ROM Operations](#rom-operations)
|
||||
2. [Editor Automation](#editor-automation)
|
||||
3. [Testing Commands](#testing-commands)
|
||||
4. [Build & CI/CD](#build--cicd)
|
||||
5. [Query & Discovery](#query--discovery)
|
||||
6. [Interactive REPL](#interactive-repl)
|
||||
7. [Network & Collaboration](#network--collaboration)
|
||||
8. [AI Integration](#ai-integration)
|
||||
|
||||
## ROM Operations
|
||||
|
||||
### `z3ed rom read`
|
||||
Read bytes from ROM at specified address.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed rom read --address <hex> [--length <bytes>] [--format hex|ascii|binary]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Read 16 bytes from address 0x1000
|
||||
z3ed rom read --address 0x1000 --length 16
|
||||
|
||||
# Read 256 bytes and display as ASCII
|
||||
z3ed rom read --address 0x20000 --length 256 --format ascii
|
||||
|
||||
# Read single byte
|
||||
z3ed rom read --address 0xFFFF
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"address": "0x001000",
|
||||
"data": "A9 00 85 2C A9 01 85 2D A9 02 85 2E A9 03 85 2F",
|
||||
"ascii": ".........,......",
|
||||
"length": 16
|
||||
}
|
||||
```
|
||||
|
||||
### `z3ed rom write`
|
||||
Write bytes to ROM at specified address.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed rom write --address <hex> --data <hex_string> [--verify]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Write 4 bytes
|
||||
z3ed rom write --address 0x1000 --data "A9 00 85 2C"
|
||||
|
||||
# Write with verification
|
||||
z3ed rom write --address 0x2000 --data "FF FE FD FC" --verify
|
||||
|
||||
# Write ASCII string (converted to hex)
|
||||
z3ed rom write --address 0x3000 --data "YAZE" --format ascii
|
||||
```
|
||||
|
||||
### `z3ed rom validate`
|
||||
Validate ROM integrity and structure.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed rom validate [--checksums] [--headers] [--regions] [--fix]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Full validation
|
||||
z3ed rom validate
|
||||
|
||||
# Validate checksums only
|
||||
z3ed rom validate --checksums
|
||||
|
||||
# Validate and attempt fixes
|
||||
z3ed rom validate --fix
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"valid": true,
|
||||
"checksums": {
|
||||
"header": "OK",
|
||||
"complement": "OK"
|
||||
},
|
||||
"headers": {
|
||||
"title": "THE LEGEND OF ZELDA",
|
||||
"version": "1.0",
|
||||
"region": "USA"
|
||||
},
|
||||
"issues": [],
|
||||
"warnings": ["Expanded ROM detected"]
|
||||
}
|
||||
```
|
||||
|
||||
### `z3ed rom snapshot`
|
||||
Create a named snapshot of current ROM state.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed rom snapshot --name <name> [--compress] [--metadata <json>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Create snapshot
|
||||
z3ed rom snapshot --name "before_dungeon_edit"
|
||||
|
||||
# Compressed snapshot with metadata
|
||||
z3ed rom snapshot --name "v1.0_release" --compress --metadata '{"version": "1.0", "author": "user"}'
|
||||
|
||||
# List snapshots
|
||||
z3ed rom list-snapshots
|
||||
```
|
||||
|
||||
### `z3ed rom restore`
|
||||
Restore ROM from a previous snapshot.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed rom restore --snapshot <name> [--verify]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Restore snapshot
|
||||
z3ed rom restore --snapshot "before_dungeon_edit"
|
||||
|
||||
# Restore with verification
|
||||
z3ed rom restore --snapshot "last_stable" --verify
|
||||
```
|
||||
|
||||
## Editor Automation
|
||||
|
||||
### `z3ed editor dungeon`
|
||||
Automate dungeon editor operations.
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
#### `place-object`
|
||||
Place an object in a dungeon room.
|
||||
|
||||
```bash
|
||||
z3ed editor dungeon place-object --room <id> --type <object_id> --x <x> --y <y> [--layer <0|1|2>]
|
||||
|
||||
# Example: Place a chest at position (10, 15) in room 0
|
||||
z3ed editor dungeon place-object --room 0 --type 0x22 --x 10 --y 15
|
||||
```
|
||||
|
||||
#### `set-property`
|
||||
Modify room properties.
|
||||
|
||||
```bash
|
||||
z3ed editor dungeon set-property --room <id> --property <name> --value <value>
|
||||
|
||||
# Example: Set room darkness
|
||||
z3ed editor dungeon set-property --room 5 --property "dark" --value true
|
||||
```
|
||||
|
||||
#### `list-objects`
|
||||
List all objects in a room.
|
||||
|
||||
```bash
|
||||
z3ed editor dungeon list-objects --room <id> [--filter-type <type>]
|
||||
|
||||
# Example: List all chests in room 10
|
||||
z3ed editor dungeon list-objects --room 10 --filter-type 0x22
|
||||
```
|
||||
|
||||
#### `validate-room`
|
||||
Check room for issues.
|
||||
|
||||
```bash
|
||||
z3ed editor dungeon validate-room --room <id> [--fix-issues]
|
||||
|
||||
# Example: Validate and fix room 0
|
||||
z3ed editor dungeon validate-room --room 0 --fix-issues
|
||||
```
|
||||
|
||||
### `z3ed editor overworld`
|
||||
Automate overworld editor operations.
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
#### `set-tile`
|
||||
Modify a tile on the overworld map.
|
||||
|
||||
```bash
|
||||
z3ed editor overworld set-tile --map <id> --x <x> --y <y> --tile <tile_id>
|
||||
|
||||
# Example: Set grass tile at position (100, 50) on Light World
|
||||
z3ed editor overworld set-tile --map 0x00 --x 100 --y 50 --tile 0x002
|
||||
```
|
||||
|
||||
#### `place-entrance`
|
||||
Add an entrance to the overworld.
|
||||
|
||||
```bash
|
||||
z3ed editor overworld place-entrance --map <id> --x <x> --y <y> --target <room_id> [--type <type>]
|
||||
|
||||
# Example: Place dungeon entrance
|
||||
z3ed editor overworld place-entrance --map 0x00 --x 200 --y 150 --target 0x10 --type "dungeon"
|
||||
```
|
||||
|
||||
#### `modify-sprite`
|
||||
Modify sprite properties.
|
||||
|
||||
```bash
|
||||
z3ed editor overworld modify-sprite --map <id> --sprite-index <idx> --property <prop> --value <val>
|
||||
|
||||
# Example: Change enemy sprite type
|
||||
z3ed editor overworld modify-sprite --map 0x00 --sprite-index 0 --property "type" --value 0x08
|
||||
```
|
||||
|
||||
### `z3ed editor batch`
|
||||
Execute multiple editor operations from a script file.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed editor batch --script <file> [--dry-run] [--parallel] [--continue-on-error]
|
||||
```
|
||||
|
||||
**Script Format (JSON):**
|
||||
```json
|
||||
{
|
||||
"operations": [
|
||||
{
|
||||
"editor": "dungeon",
|
||||
"action": "place-object",
|
||||
"params": {
|
||||
"room": 1,
|
||||
"type": 34,
|
||||
"x": 10,
|
||||
"y": 15
|
||||
}
|
||||
},
|
||||
{
|
||||
"editor": "overworld",
|
||||
"action": "set-tile",
|
||||
"params": {
|
||||
"map": 0,
|
||||
"x": 20,
|
||||
"y": 30,
|
||||
"tile": 322
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"stop_on_error": false,
|
||||
"validate_after": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Commands
|
||||
|
||||
### `z3ed test run`
|
||||
Execute test suites.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed test run [--category <unit|integration|e2e>] [--filter <pattern>] [--parallel]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Run all tests
|
||||
z3ed test run
|
||||
|
||||
# Run unit tests only
|
||||
z3ed test run --category unit
|
||||
|
||||
# Run specific test pattern
|
||||
z3ed test run --filter "*Overworld*"
|
||||
|
||||
# Parallel execution
|
||||
z3ed test run --parallel
|
||||
```
|
||||
|
||||
### `z3ed test generate`
|
||||
Generate test code for a class or component.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed test generate --target <class|file> --output <file> [--framework <gtest|catch2>] [--include-mocks]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Generate tests for a class
|
||||
z3ed test generate --target OverworldEditor --output overworld_test.cc
|
||||
|
||||
# Generate with mocks
|
||||
z3ed test generate --target DungeonRoom --output dungeon_test.cc --include-mocks
|
||||
|
||||
# Generate integration tests
|
||||
z3ed test generate --target src/app/rom.cc --output rom_integration_test.cc --framework catch2
|
||||
```
|
||||
|
||||
### `z3ed test record`
|
||||
Record interactions for test generation.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed test record --name <test_name> --start
|
||||
z3ed test record --stop [--save-as <file>]
|
||||
z3ed test record --pause
|
||||
z3ed test record --resume
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Start recording
|
||||
z3ed test record --name "dungeon_edit_test" --start
|
||||
|
||||
# Perform actions...
|
||||
z3ed editor dungeon place-object --room 0 --type 0x22 --x 10 --y 10
|
||||
|
||||
# Stop and save
|
||||
z3ed test record --stop --save-as dungeon_test.json
|
||||
|
||||
# Generate test from recording
|
||||
z3ed test generate --from-recording dungeon_test.json --output dungeon_test.cc
|
||||
```
|
||||
|
||||
### `z3ed test baseline`
|
||||
Manage test baselines for regression testing.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed test baseline --create --name <baseline>
|
||||
z3ed test baseline --compare --name <baseline> [--threshold <percent>]
|
||||
z3ed test baseline --update --name <baseline>
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Create baseline
|
||||
z3ed test baseline --create --name "v1.0_stable"
|
||||
|
||||
# Compare against baseline
|
||||
z3ed test baseline --compare --name "v1.0_stable" --threshold 95
|
||||
|
||||
# Update baseline
|
||||
z3ed test baseline --update --name "v1.0_stable"
|
||||
```
|
||||
|
||||
## Build & CI/CD
|
||||
|
||||
### `z3ed build`
|
||||
Build the project with specified configuration.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed build --preset <preset> [--verbose] [--parallel <jobs>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Debug build
|
||||
z3ed build --preset lin-dbg
|
||||
|
||||
# Release build with 8 parallel jobs
|
||||
z3ed build --preset lin-rel --parallel 8
|
||||
|
||||
# Verbose output
|
||||
z3ed build --preset mac-dbg --verbose
|
||||
```
|
||||
|
||||
### `z3ed ci status`
|
||||
Check CI/CD pipeline status.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed ci status [--workflow <name>] [--branch <branch>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Check all workflows
|
||||
z3ed ci status
|
||||
|
||||
# Check specific workflow
|
||||
z3ed ci status --workflow "Build and Test"
|
||||
|
||||
# Check branch status
|
||||
z3ed ci status --branch develop
|
||||
```
|
||||
|
||||
## Query & Discovery
|
||||
|
||||
### `z3ed query rom-info`
|
||||
Get comprehensive ROM information.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed query rom-info [--detailed] [--format <json|yaml|text>]
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"title": "THE LEGEND OF ZELDA",
|
||||
"size": 2097152,
|
||||
"expanded": true,
|
||||
"version": "1.0",
|
||||
"region": "USA",
|
||||
"checksum": "0xABCD",
|
||||
"header": {
|
||||
"mapper": "LoROM",
|
||||
"rom_speed": "FastROM",
|
||||
"rom_type": "ROM+SRAM+Battery"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `z3ed query available-commands`
|
||||
Discover available commands and their usage.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed query available-commands [--category <category>] [--format tree|list|json]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# List all commands as tree
|
||||
z3ed query available-commands --format tree
|
||||
|
||||
# List editor commands
|
||||
z3ed query available-commands --category editor
|
||||
|
||||
# JSON output for programmatic use
|
||||
z3ed query available-commands --format json
|
||||
```
|
||||
|
||||
### `z3ed query find-tiles`
|
||||
Search for tile patterns in ROM.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed query find-tiles --pattern <hex> [--context <bytes>] [--limit <count>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Find specific tile pattern
|
||||
z3ed query find-tiles --pattern "FF 00 FF 00"
|
||||
|
||||
# Find with context
|
||||
z3ed query find-tiles --pattern "A9 00" --context 8
|
||||
|
||||
# Limit results
|
||||
z3ed query find-tiles --pattern "00 00" --limit 10
|
||||
```
|
||||
|
||||
## Interactive REPL
|
||||
|
||||
### Starting REPL Mode
|
||||
|
||||
```bash
|
||||
# Start REPL with ROM
|
||||
z3ed repl --rom zelda3.sfc
|
||||
|
||||
# Start with history file
|
||||
z3ed repl --rom zelda3.sfc --history ~/.z3ed_history
|
||||
```
|
||||
|
||||
### REPL Features
|
||||
|
||||
#### Variable Assignment
|
||||
```
|
||||
z3ed> $info = rom info
|
||||
z3ed> echo $info.title
|
||||
THE LEGEND OF ZELDA
|
||||
```
|
||||
|
||||
#### Command Pipelines
|
||||
```
|
||||
z3ed> rom read --address 0x1000 --length 100 | find --pattern "A9"
|
||||
z3ed> query find-tiles --pattern "FF" | head -10
|
||||
```
|
||||
|
||||
#### Session Management
|
||||
```
|
||||
z3ed> session save my_session
|
||||
z3ed> session load my_session
|
||||
z3ed> history show
|
||||
z3ed> history replay 5-10
|
||||
```
|
||||
|
||||
## Network & Collaboration
|
||||
|
||||
### `z3ed network connect`
|
||||
Connect to collaborative editing session.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed network connect --host <host> --port <port> [--username <name>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Connect to session
|
||||
z3ed network connect --host localhost --port 8080 --username "agent1"
|
||||
|
||||
# Join specific room
|
||||
z3ed network join --room "dungeon_editing" --password "secret"
|
||||
```
|
||||
|
||||
### `z3ed network sync`
|
||||
Synchronize changes with other collaborators.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed network sync [--push] [--pull] [--merge-strategy <strategy>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Push local changes
|
||||
z3ed network sync --push
|
||||
|
||||
# Pull remote changes
|
||||
z3ed network sync --pull
|
||||
|
||||
# Bidirectional sync with merge
|
||||
z3ed network sync --merge-strategy last-write-wins
|
||||
```
|
||||
|
||||
## AI Integration
|
||||
|
||||
### `z3ed ai chat`
|
||||
Interactive AI assistant for ROM hacking.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed ai chat [--model <model>] [--context <file>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Start chat with default model
|
||||
z3ed ai chat
|
||||
|
||||
# Use specific model
|
||||
z3ed ai chat --model gemini-pro
|
||||
|
||||
# Provide context file
|
||||
z3ed ai chat --context room_layout.json
|
||||
```
|
||||
|
||||
### `z3ed ai suggest`
|
||||
Get AI suggestions for ROM modifications.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed ai suggest --task <task> [--model <model>] [--constraints <json>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Suggest dungeon layout improvements
|
||||
z3ed ai suggest --task "improve dungeon flow" --constraints '{"rooms": [1,2,3]}'
|
||||
|
||||
# Suggest sprite placements
|
||||
z3ed ai suggest --task "balance enemy placement" --model ollama:llama2
|
||||
```
|
||||
|
||||
### `z3ed ai analyze`
|
||||
Analyze ROM for patterns and issues.
|
||||
|
||||
**Syntax:**
|
||||
```bash
|
||||
z3ed ai analyze --type <pattern|bug|optimization> [--report <file>]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Analyze for bugs
|
||||
z3ed ai analyze --type bug --report bugs.json
|
||||
|
||||
# Find optimization opportunities
|
||||
z3ed ai analyze --type optimization
|
||||
|
||||
# Pattern analysis
|
||||
z3ed ai analyze --type pattern --report patterns.md
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
These options work with all commands:
|
||||
|
||||
- `--rom <file>` - Specify ROM file to use
|
||||
- `--verbose` - Enable verbose output
|
||||
- `--quiet` - Suppress non-error output
|
||||
- `--format <json|yaml|text>` - Output format
|
||||
- `--output <file>` - Write output to file
|
||||
- `--no-color` - Disable colored output
|
||||
- `--config <file>` - Use configuration file
|
||||
- `--log-level <level>` - Set logging level
|
||||
- `--help` - Show help for command
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- `0` - Success
|
||||
- `1` - General error
|
||||
- `2` - Invalid arguments
|
||||
- `3` - ROM not loaded
|
||||
- `4` - Operation failed
|
||||
- `5` - Network error
|
||||
- `6` - Build error
|
||||
- `7` - Test failure
|
||||
- `127` - Command not found
|
||||
|
||||
## Configuration
|
||||
|
||||
Z3ed can be configured via `~/.z3edrc` or `z3ed.config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"default_rom": "~/roms/zelda3.sfc",
|
||||
"ai_model": "gemini-pro",
|
||||
"output_format": "json",
|
||||
"log_level": "info",
|
||||
"network": {
|
||||
"default_host": "localhost",
|
||||
"default_port": 8080
|
||||
},
|
||||
"build": {
|
||||
"default_preset": "lin-dbg",
|
||||
"parallel_jobs": 8
|
||||
},
|
||||
"test": {
|
||||
"framework": "gtest",
|
||||
"coverage": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `Z3ED_ROM_PATH` - Default ROM file path
|
||||
- `Z3ED_CONFIG` - Configuration file location
|
||||
- `Z3ED_AI_MODEL` - Default AI model
|
||||
- `Z3ED_LOG_LEVEL` - Logging verbosity
|
||||
- `Z3ED_HISTORY_FILE` - REPL history file location
|
||||
- `Z3ED_CACHE_DIR` - Cache directory for snapshots
|
||||
- `Z3ED_API_KEY` - API key for AI services
|
||||
33
docs/public/release-notes.md
Normal file
33
docs/public/release-notes.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Release Notes
|
||||
|
||||
## v0.5.0-alpha (In Development)
|
||||
|
||||
**Type:** Major Feature & Architecture Update
|
||||
**Date:** TBD
|
||||
|
||||
### 🎵 New Music Editor
|
||||
The highly anticipated SPC Music Editor is now available!
|
||||
- **Tracker View:** Edit music patterns with a familiar tracker interface.
|
||||
- **Piano Roll:** Visualize and edit notes with velocity and duration control.
|
||||
- **Preview:** Real-time N-SPC audio preview with authentic ADSR envelopes.
|
||||
- **Instruments:** Manage instruments and samples directly.
|
||||
|
||||
### 🕸️ Web App Improvements (WASM)
|
||||
- **Experimental Web Port:** Run YAZE directly in your browser.
|
||||
- **UI Refresh:** New panelized layout with VSCode-style terminal and problems view.
|
||||
- **Usability:** Improved error handling for missing features (Emulator).
|
||||
- **Security:** Hardened `SharedArrayBuffer` support with `coi-serviceworker`.
|
||||
|
||||
### 🏗️ Architecture & CI
|
||||
- **Windows CI:** Fixed environment setup for `clang-cl` builds (MSVC dev cmd).
|
||||
- **Code Quality:** Consolidated CI workflows.
|
||||
- **SDL3 Prep:** Groundwork for the upcoming migration to SDL3.
|
||||
|
||||
---
|
||||
|
||||
## v0.4.0 - Music Editor & UI Polish
|
||||
**Released:** November 2025
|
||||
|
||||
- Complete SPC music editing infrastructure.
|
||||
- EditorManager refactoring for better multi-window support.
|
||||
- AI Agent integration (experimental).
|
||||
@@ -1,116 +1,117 @@
|
||||
# F2: Dungeon Editor v2 Guide
|
||||
# Dungeon Editor Guide
|
||||
|
||||
**Scope**: DungeonEditorV2 (card-based UI), DungeonEditorSystem, dungeon canvases
|
||||
**Related**: [Architecture Overview](../developer/architecture.md), [Canvas System](../developer/canvas-system.md)
|
||||
The Dungeon Editor provides a multi-panel workspace for editing Zelda 3 dungeon rooms. Each room has isolated graphics, objects, and palette data, allowing you to work on multiple rooms simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The Dungeon Editor ships with the multi-card workspace introduced in the 0.3.x releases.
|
||||
Self-contained room buffers keep graphics, objects, and palettes isolated so you can switch between
|
||||
rooms without invalidating the entire renderer.
|
||||
## Overview
|
||||
|
||||
### Key Features
|
||||
- 512×512 canvas per room with pan/zoom, grid, and collision overlays.
|
||||
- Layer-specific visualization (BG1/BG2 toggles, colored object outlines, slot labels).
|
||||
- Modular cards for rooms, objects, palettes, entrances, and toolsets.
|
||||
- Undo/Redo shared across cards via `DungeonEditorSystem`.
|
||||
- Tight overworld integration: double-click an entrance to open the linked dungeon room.
|
||||
|
||||
- **512x512 canvas** per room with pan, zoom, grid, and collision overlays
|
||||
- **Layer visualization** with BG1/BG2 toggles and colored object outlines
|
||||
- **Modular panels** for rooms, objects, palettes, and entrances
|
||||
- **Undo/Redo** shared across all panels
|
||||
- **Overworld integration** - double-click entrances to open linked rooms
|
||||
|
||||
**Related Documentation:**
|
||||
- [Architecture Overview](../developer/architecture.md)
|
||||
- [Canvas System](../developer/canvas-system.md)
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture Snapshot
|
||||
## Architecture
|
||||
|
||||
```
|
||||
DungeonEditorV2 (UI)
|
||||
├─ Cards & docking
|
||||
├─ Canvas presenter
|
||||
└─ Menu + toolbar actions
|
||||
The editor uses a three-layer architecture:
|
||||
|
||||
DungeonEditorSystem (Backend)
|
||||
├─ Room/session state
|
||||
├─ Undo/Redo stack
|
||||
├─ Sprite/entrance/item helpers
|
||||
└─ Persistence + ROM writes
|
||||
| Layer | Components | Responsibility |
|
||||
|-------|------------|----------------|
|
||||
| **UI** | DungeonEditorV2 | Panels, canvas, menus, toolbar |
|
||||
| **Backend** | DungeonEditorSystem | State management, undo/redo, persistence |
|
||||
| **Data** | Room Model | Buffers, objects, palettes, blocksets |
|
||||
|
||||
Room Model (Data)
|
||||
├─ bg1_buffer_, bg2_buffer_
|
||||
├─ tile_objects_, door data, metadata
|
||||
└─ Palette + blockset caches
|
||||
```
|
||||
### Rendering Pipeline
|
||||
|
||||
### Room Rendering Pipeline
|
||||
1. **Load** – `DungeonRoomLoader` reads the room header, blockset pointers, and door/entrance
|
||||
metadata, producing a `Room` instance with immutable layout info.
|
||||
2. **Decode** – The requested blockset is converted into `current_gfx16_` bitmaps; objects are parsed
|
||||
into `tile_objects_` grouped by layer and palette slot.
|
||||
3. **Draw** – `DungeonCanvasViewer` builds BG1/BG2 bitmaps, then overlays each object layer via
|
||||
`ObjectDrawer`. Palette state comes from the room’s 90-color dungeon palette.
|
||||
4. **Queue** – The finished bitmaps are pushed into the graphics `Arena`, which uploads a bounded
|
||||
number of textures per frame so UI latency stays flat.
|
||||
5. **Present** – When textures become available, the canvas displays the layers, draws interaction
|
||||
widgets (selection rectangles, door gizmos, entity labels), and applies zoom/grid settings.
|
||||
1. **Load** - Read room header, blockset pointers, and door/entrance metadata
|
||||
2. **Decode** - Convert blockset into bitmaps; parse objects by layer
|
||||
3. **Draw** - Build BG1/BG2 bitmaps with object overlays
|
||||
4. **Queue** - Push bitmaps to texture queue for GPU upload
|
||||
5. **Present** - Display layers with selection widgets and grid
|
||||
|
||||
Changing tiles, palettes, or objects invalidates the affected room cache so steps 2–5 rerun only for
|
||||
that room.
|
||||
Changes to tiles, palettes, or objects invalidate only the affected room's cache.
|
||||
|
||||
---
|
||||
|
||||
## 3. Editing Workflow
|
||||
## Editing Workflow
|
||||
|
||||
### Opening Rooms
|
||||
1. Launch `yaze` with a ROM (`./build/bin/yaze --rom_file=zelda3.sfc`).
|
||||
2. Use the **Room Matrix** or **Rooms List** card to choose a room. The toolbar “+” button also opens
|
||||
the selector.
|
||||
3. Pin multiple rooms by opening them in separate cards; each card maintains its own canvas state.
|
||||
|
||||
### Working with Cards
|
||||
1. Launch YAZE with a ROM: `./build/bin/yaze --rom_file=zelda3.sfc`
|
||||
2. Select a room from **Room Matrix** or **Rooms List** panel
|
||||
3. Open multiple rooms in separate panels for comparison
|
||||
|
||||
| Card | Purpose |
|
||||
|------|---------|
|
||||
| **Room Graphics** | Primary canvas, BG toggles, collision/grid switches. |
|
||||
| **Object Editor** | Filter by type/layer, edit coordinates, duplicate/delete objects. |
|
||||
| **Palette Editor** | Adjust per-room palette slots and preview results immediately. |
|
||||
| **Entrances List** | Jump between overworld entrances and their mapped rooms. |
|
||||
| **Room Matrix** | Visual grid of all rooms grouped per dungeon for quick navigation. |
|
||||
### Available Panels
|
||||
|
||||
Cards can be docked, detached, or saved as workspace presets; use the sidebar to store favorite
|
||||
layouts (e.g., Room Graphics + Object Editor + Palette).
|
||||
| Panel | Purpose |
|
||||
|-------|---------|
|
||||
| **Room Graphics** | Main canvas with BG toggles and grid options |
|
||||
| **Object Editor** | Edit objects by type, layer, and coordinates |
|
||||
| **Palette Editor** | Adjust room palettes with live preview |
|
||||
| **Entrances List** | Navigate between overworld entrances and rooms |
|
||||
| **Room Matrix** | Visual dungeon room grid for quick navigation |
|
||||
|
||||
### Canvas Interactions
|
||||
- Left-click to select an object; Shift-click to add to the selection.
|
||||
- Drag handles to move objects or use the property grid for precise coordinates.
|
||||
- Right-click to open the context menu, which includes quick inserts for common objects and a “jump
|
||||
to entrance” helper.
|
||||
- Hold Space to pan, use mouse wheel (or trackpad pinch) to zoom. The status footer shows current
|
||||
zoom and cursor coordinates.
|
||||
- Enable **Object Labels** from the toolbar to show layer-colored labels (e.g., `L1 Chest 0x23`).
|
||||
Panels can be docked, detached, or saved as workspace presets.
|
||||
|
||||
### Saving & Undo
|
||||
- The editor queues every change through `DungeonEditorSystem`. Use `Cmd/Ctrl+Z` and `Cmd/Ctrl+Shift+Z`
|
||||
to undo/redo across cards.
|
||||
- Saving writes back the room buffers, door metadata, and palettes for the active session. Keep
|
||||
backups enabled (`File → Options → Experiment Flags`) for safety.
|
||||
### Canvas Controls
|
||||
|
||||
| Action | Control |
|
||||
|--------|---------|
|
||||
| Select object | Left-click |
|
||||
| Add to selection | Shift + Left-click |
|
||||
| Move object | Drag handles |
|
||||
| Pan canvas | Hold Space + drag |
|
||||
| Zoom | Mouse wheel or trackpad pinch |
|
||||
| Context menu | Right-click |
|
||||
|
||||
Enable **Object Labels** from the toolbar to display layer-colored labels.
|
||||
|
||||
### Saving
|
||||
|
||||
- **Undo/Redo**: `Cmd/Ctrl+Z` and `Cmd/Ctrl+Shift+Z`
|
||||
- Changes are tracked across all panels
|
||||
- Keep backups enabled in `File > Options > Experiment Flags`
|
||||
|
||||
---
|
||||
|
||||
## 4. Tips & Troubleshooting
|
||||
## Troubleshooting
|
||||
|
||||
- **Layer sanity**: If objects appear on the wrong layer, check the BG toggles in Room Graphics and
|
||||
the layer filter in Object Editor—they operate independently.
|
||||
- **Palette issues**: Palettes are per room. After editing, ensure `Palette Editor` writes the new
|
||||
values before switching rooms; the status footer confirms pending writes.
|
||||
- **Door alignment**: Use the entrance/door inspector popup (right-click a door marker) to verify
|
||||
leads-to IDs without leaving the canvas.
|
||||
- **Performance**: Large ROMs with many rooms can accumulate textures. If the editor feels sluggish,
|
||||
close unused room cards; each card releases its textures when closed.
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Objects on wrong layer | Check BG toggles in Room Graphics and layer filter in Object Editor |
|
||||
| Palette not saving | Ensure Palette Editor writes values before switching rooms |
|
||||
| Door alignment issues | Right-click door markers to verify leads-to IDs |
|
||||
| Sluggish performance | Close unused room panels to release textures |
|
||||
|
||||
---
|
||||
|
||||
## 5. Related Docs
|
||||
- [Developer Architecture Overview](../developer/architecture.md) – patterns shared across editors.
|
||||
- [Canvas System Guide](../developer/canvas-system.md) – detailed explanation of canvas usage,
|
||||
context menus, and popups.
|
||||
- [Debugging Guide](../developer/debugging-guide.md) – startup flags and logging tips (e.g.,
|
||||
`--editor=Dungeon --cards="Room 0"` for focused debugging).
|
||||
## Quick Launch Examples
|
||||
|
||||
```bash
|
||||
# Open specific room for testing
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0"
|
||||
|
||||
# Compare multiple rooms
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon --open_panels="Room 0,Room 1,Room 105"
|
||||
|
||||
# Full workspace with all tools
|
||||
./yaze --rom_file=zelda3.sfc --editor=Dungeon \
|
||||
--open_panels="Rooms List,Room Matrix,Object Editor,Palette Editor"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Architecture Overview](../developer/architecture.md) - Patterns shared across editors
|
||||
- [Canvas System](../developer/canvas-system.md) - Canvas controls and context menus
|
||||
- [Debugging Guide](../developer/debugging-guide.md) - Startup flags and logging
|
||||
|
||||
@@ -495,6 +495,49 @@ class OverworldMap {
|
||||
- Ensure proper sprite graphics table selection for v2 vs v3 ROMs
|
||||
- Verify that special area maps use the correct graphics from referenced LW/DW maps
|
||||
|
||||
## Save Operations and Version Safety
|
||||
|
||||
### Version Checking for Save Functions
|
||||
|
||||
**CRITICAL**: All save functions that write to custom ASM address space (0x140000+) must check the ROM version before writing. Failing to do so will corrupt vanilla ROMs by overwriting game data with uninitialized values.
|
||||
|
||||
```cpp
|
||||
// CORRECT: Check version before writing to custom address space
|
||||
absl::Status Overworld::SaveAreaSpecificBGColors() {
|
||||
auto version = OverworldVersionHelper::GetVersion(*rom_);
|
||||
if (!OverworldVersionHelper::SupportsCustomBGColors(version)) {
|
||||
return absl::OkStatus(); // Vanilla/v1 ROM - skip custom address writes
|
||||
}
|
||||
// Safe to write to 0x140000+ for v2+ ROMs
|
||||
}
|
||||
|
||||
// INCORRECT: Writing without version check
|
||||
absl::Status Overworld::SaveAreaSpecificBGColors() {
|
||||
// BUG: This writes to 0x140000 even for vanilla ROMs!
|
||||
for (int i = 0; i < 160; ++i) {
|
||||
rom_->Write(OverworldCustomAreaSpecificBGPalette + i * 2, color);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Version-Gated Save Functions
|
||||
|
||||
| Save Function | Required Version | Address Range |
|
||||
|---------------|------------------|---------------|
|
||||
| `SaveAreaSpecificBGColors()` | v2+ | 0x140000-0x140140 |
|
||||
| `SaveCustomOverworldASM()` (v2 features) | v2+ | 0x140140-0x140180 |
|
||||
| `SaveCustomOverworldASM()` (v3 features) | v3+ | 0x140200+ |
|
||||
| `SaveDiggableTiles()` | v3+ | 0x140980+ |
|
||||
| `SaveAreaSizes()` | v3+ | 0x1417F8+ |
|
||||
|
||||
### ROM Upgrade Path
|
||||
|
||||
To enable v2/v3 features on a vanilla ROM:
|
||||
1. Use the toolbar version badge to identify current ROM version
|
||||
2. Click "Upgrade" button to apply ZSCustomOverworld ASM patch
|
||||
3. Editor automatically reinitializes custom tables with sensible defaults
|
||||
4. New UI controls become visible after upgrade
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Version-Specific Code
|
||||
|
||||
278
docs/public/usage/web-app.md
Normal file
278
docs/public/usage/web-app.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Web App Guide (Preview)
|
||||
|
||||
YAZE is available as a **preview** web application that runs in your browser. The web port is under active development - expect bugs and incomplete features.
|
||||
|
||||
> **⚠️ Preview Status**: The web app is experimental. For production ROM hacking, use the [desktop build](../build/quick-reference.md).
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Try it Now
|
||||
|
||||
Visit your deployed instance to access the web version. The application is served via GitHub Pages or custom deployment.
|
||||
|
||||
### Loading a ROM
|
||||
|
||||
1. Click **"Open ROM"** or drag and drop your Zelda 3 ROM file onto the page
|
||||
2. The ROM is stored locally in your browser using IndexedDB - it never leaves your computer
|
||||
3. Start editing using a subset of the desktop interface
|
||||
|
||||
### Saving Your Work
|
||||
|
||||
- **Auto-save**: Changes are automatically saved to browser storage (when working)
|
||||
- **Download ROM**: Click the download button to save your modified ROM to disk
|
||||
- **Backup recommended**: Download frequently; browser storage can be cleared
|
||||
|
||||
## Feature Status
|
||||
|
||||
The web version is in preview with varying editor support:
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| ROM Loading | ✅ Working | Drag & drop, file picker |
|
||||
| Overworld Editor | ⚡ Preview | Basic tile editing, incomplete features |
|
||||
| Dungeon Editor | ⚡ Preview | Room viewing, editing incomplete |
|
||||
| Palette Editor | ⚡ Preview | Basic palette viewing/editing |
|
||||
| Graphics Editor | ⚡ Preview | Tile viewing, editing incomplete |
|
||||
| Sprite Editor | ⚡ Preview | Limited functionality |
|
||||
| Message Editor | ⚡ Preview | Text viewing, editing incomplete |
|
||||
| Hex Editor | ✅ Working | Direct ROM editing |
|
||||
| Asar Patching | ⚡ Preview | Basic assembly patching |
|
||||
| Emulator | ❌ Not Available | Use desktop build |
|
||||
| Real-time Collaboration | ⚡ Experimental | Requires server setup |
|
||||
| Audio Playback | ⚡ Experimental | Limited SPC700 support |
|
||||
|
||||
**Legend**: ✅ Working | ⚡ Preview/Incomplete | ❌ Not Available
|
||||
|
||||
## Browser Requirements
|
||||
|
||||
### Recommended Browsers
|
||||
|
||||
- **Chrome/Edge** 90+ (best performance)
|
||||
- **Firefox** 88+
|
||||
- **Safari** 15.4+ (macOS/iOS)
|
||||
|
||||
### Required Features
|
||||
|
||||
The web app requires modern browser features:
|
||||
- **WebAssembly**: Core application runtime
|
||||
- **SharedArrayBuffer**: Multi-threading support (requires HTTPS or localhost)
|
||||
- **IndexedDB**: Local ROM and project storage
|
||||
- **File System Access API**: Better file handling (Chrome/Edge)
|
||||
|
||||
### Mobile Support
|
||||
|
||||
✅ **Tablets**: Full support on iPad and Android tablets
|
||||
⚠️ **Phones**: Limited - interface designed for larger screens
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### For Best Performance
|
||||
|
||||
1. **Use Chrome or Edge** - Fastest WebAssembly implementation
|
||||
2. **Close other tabs** - Free up browser memory
|
||||
3. **Desktop/laptop recommended** - Better than mobile for complex editing
|
||||
4. **Stable internet** - Only needed for initial load; works offline after
|
||||
|
||||
### Troubleshooting Slow Performance
|
||||
|
||||
- Clear browser cache and reload
|
||||
- Disable browser extensions temporarily
|
||||
- Check browser console for errors (F12)
|
||||
- Use the debug build for better error messages (see Development section)
|
||||
|
||||
## Storage & Privacy
|
||||
|
||||
### What's Stored Locally
|
||||
|
||||
- ROM files (IndexedDB)
|
||||
- Project settings and preferences
|
||||
- Auto-save data
|
||||
- Recent file history
|
||||
|
||||
### Privacy Guarantee
|
||||
|
||||
- **All data stays in your browser** - ROMs and edits never uploaded to servers
|
||||
- **No tracking** - No analytics or user data collection
|
||||
- **No login required** - No accounts or personal information needed
|
||||
|
||||
### Clearing Data
|
||||
|
||||
```javascript
|
||||
// Open browser console (F12) and run:
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
// Then reload the page
|
||||
```
|
||||
|
||||
Or use your browser's "Clear browsing data" feature.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
The web app supports desktop keyboard shortcuts:
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| **Ctrl/Cmd + O** | Open ROM |
|
||||
| **Ctrl/Cmd + S** | Save/Download ROM |
|
||||
| **Ctrl/Cmd + Z** | Undo |
|
||||
| **Ctrl/Cmd + Y** | Redo |
|
||||
| **`** (backtick) | Toggle terminal |
|
||||
| **Esc** | Close panels/dialogs |
|
||||
| **Tab** | Cycle through editors |
|
||||
|
||||
**Note**: Some shortcuts may conflict with browser defaults. Use the menu bar as an alternative.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Terminal Access
|
||||
|
||||
Press **`** (backtick) to open the integrated terminal:
|
||||
|
||||
```bash
|
||||
# List all dungeons
|
||||
z3ed dungeon list --rom current
|
||||
|
||||
# Get overworld info
|
||||
z3ed overworld info --map 0
|
||||
|
||||
# Apply a patch
|
||||
z3ed asar my_patch.asm
|
||||
```
|
||||
|
||||
### Collaboration (Experimental)
|
||||
|
||||
When enabled, multiple users can edit the same ROM simultaneously:
|
||||
|
||||
1. Click **"Start Collaboration"** in the toolbar
|
||||
2. Share the session URL with collaborators
|
||||
3. See real-time cursors and changes from other users
|
||||
|
||||
**Note**: Requires a collaboration server to be deployed. See deployment docs for setup.
|
||||
|
||||
### Developer Tools
|
||||
|
||||
Open browser console (F12) for debugging:
|
||||
|
||||
```javascript
|
||||
// Check if WASM module is ready
|
||||
window.yazeDebug.isReady()
|
||||
|
||||
// Get ROM status
|
||||
window.yazeDebug.rom.getStatus()
|
||||
|
||||
// Get current editor state
|
||||
window.yaze.editor.getActiveEditor()
|
||||
|
||||
// Read ROM data
|
||||
window.yaze.data.getRoomTiles(roomId)
|
||||
```
|
||||
|
||||
See [`docs/internal/wasm-yazeDebug-api-reference.md`](../../internal/wasm-yazeDebug-api-reference.md) for full API documentation.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Not Yet Supported
|
||||
|
||||
- **Emulator**: No built-in emulator in web version (use desktop)
|
||||
- **Custom plugins**: Plugin system requires desktop build
|
||||
- **Large file export**: Browser limits on file size (typically 2GB+)
|
||||
|
||||
### Browser-Specific Issues
|
||||
|
||||
- **Safari**: Slower performance, some SharedArrayBuffer limitations
|
||||
- **Firefox**: Clipboard access may require permissions
|
||||
- **Mobile Chrome**: Touch controls under development
|
||||
|
||||
## Offline Usage
|
||||
|
||||
After first load, the web app works offline:
|
||||
|
||||
1. Visit the site once while online
|
||||
2. Service worker caches the application
|
||||
3. Disconnect from internet
|
||||
4. App continues to function normally
|
||||
|
||||
**Note**: Initial load requires internet; updates require reconnection.
|
||||
|
||||
## Building from Source
|
||||
|
||||
To build and deploy your own instance:
|
||||
|
||||
```bash
|
||||
# Build the web app
|
||||
./scripts/build-wasm.sh
|
||||
|
||||
# Serve locally for testing
|
||||
./scripts/serve-wasm.sh
|
||||
|
||||
# Deploy dist/ folder to your web server
|
||||
# Artifacts are in build-wasm/dist/
|
||||
```
|
||||
|
||||
See [`docs/internal/agents/wasm-development-guide.md`](../../internal/agents/wasm-development-guide.md) for detailed build instructions.
|
||||
|
||||
## Deployment
|
||||
|
||||
### GitHub Pages (Automated)
|
||||
|
||||
Pushes to `master` automatically build and deploy via GitHub Actions:
|
||||
|
||||
```yaml
|
||||
# See .github/workflows/web-build.yml
|
||||
```
|
||||
|
||||
### Custom Server
|
||||
|
||||
Requirements:
|
||||
- Static file server (nginx, Apache, etc.)
|
||||
- HTTPS enabled (required for SharedArrayBuffer)
|
||||
- Proper CORS headers
|
||||
|
||||
Minimal nginx config:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name yaze.yourdomain.com;
|
||||
|
||||
root /var/www/yaze;
|
||||
|
||||
# Required for SharedArrayBuffer
|
||||
add_header Cross-Origin-Opener-Policy same-origin;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
|
||||
# Cache WASM files
|
||||
location ~* \.(wasm|js)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Discord**: [Oracle of Secrets](https://discord.gg/MBFkMTPEmk)
|
||||
- **Issues**: [GitHub Issues](https://github.com/scawful/yaze/issues)
|
||||
- **Docs**: [`docs/public/index.md`](../index.md)
|
||||
|
||||
## Comparison: Web vs Desktop
|
||||
|
||||
| Feature | Web (Preview) | Desktop (Stable) |
|
||||
|---------|---------------|------------------|
|
||||
| **Installation** | None required | Download & install |
|
||||
| **Platforms** | Modern browsers | Windows, macOS, Linux |
|
||||
| **Performance** | Moderate | Excellent |
|
||||
| **Editor Completeness** | Preview/Incomplete | Full-featured |
|
||||
| **Emulator** | ❌ | ✅ |
|
||||
| **Plugins** | ❌ | ✅ |
|
||||
| **Stability** | Experimental | Production-ready |
|
||||
| **Updates** | Automatic | Manual download |
|
||||
| **Offline** | After first load | Always |
|
||||
| **Collaboration** | Experimental | Via server |
|
||||
| **Mobile** | Tablets (limited) | No |
|
||||
|
||||
**Recommendation**:
|
||||
- **For serious ROM hacking**: Use the desktop build
|
||||
- **For quick previews or demos**: Web app is suitable
|
||||
- **For learning/exploration**: Either works, but desktop is more complete
|
||||
|
||||
@@ -1,107 +1,116 @@
|
||||
# z3ed CLI Guide
|
||||
|
||||
_Last reviewed: November 2025. `z3ed` ships alongside the main editor in every `*-ai` preset and
|
||||
runs on Windows, macOS, and Linux._
|
||||
The `z3ed` command-line tool provides scriptable ROM editing, AI-assisted workflows, and resource inspection. It ships with all `*-ai` preset builds and runs on Windows, macOS, and Linux.
|
||||
|
||||
`z3ed` exposes the same ROM-editing capabilities as the GUI but in a scriptable form. Use it to
|
||||
apply patches, inspect resources, run batch conversions, or drive the AI-assisted workflows that
|
||||
feed the in-editor proposals.
|
||||
---
|
||||
|
||||
## 1. Building & Configuration
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Enable the agent/CLI toolchain
|
||||
# Build with AI features
|
||||
cmake --preset mac-ai
|
||||
cmake --build --preset mac-ai --target z3ed
|
||||
|
||||
# Run the text UI (FTXUI)
|
||||
# Run the text UI
|
||||
./build/bin/z3ed --tui
|
||||
```
|
||||
|
||||
The AI features require at least one provider:
|
||||
- **Ollama (local)** – install via `brew install ollama`, run `ollama serve`, then set
|
||||
`OLLAMA_MODEL=qwen2.5-coder:0.5b` (the lightweight default used in CI) or any other supported
|
||||
model. Pass `--ai_model "$OLLAMA_MODEL"` on the CLI to override per-run.
|
||||
- **Gemini (cloud)** – export `GEMINI_API_KEY` before launching `z3ed`.
|
||||
## AI Provider Configuration
|
||||
|
||||
If no provider is configured the CLI still works, but agent subcommands will fall back to manual
|
||||
plans.
|
||||
AI features require at least one provider:
|
||||
|
||||
## 2. Everyday Commands
|
||||
| Provider | Setup |
|
||||
|----------|-------|
|
||||
| **Ollama** (local) | `brew install ollama && ollama serve` |
|
||||
| **Gemini** (cloud) | `export GEMINI_API_KEY=your_key` |
|
||||
|
||||
| Task | Example |
|
||||
| --- | --- |
|
||||
| Apply an Asar patch | `z3ed asar patch.asm --rom zelda3.sfc` |
|
||||
| Export all sprites from a dungeon | `z3ed dungeon list-sprites --dungeon 2 --rom zelda3.sfc --format json` |
|
||||
| Inspect an overworld map | `z3ed overworld describe-map --map 80 --rom zelda3.sfc` |
|
||||
| Dump palette data | `z3ed palette export --rom zelda3.sfc --output palettes.json` |
|
||||
| Validate ROM headers | `z3ed rom info --rom zelda3.sfc` |
|
||||
Set the model with `--ai_model` or `OLLAMA_MODEL` environment variable.
|
||||
|
||||
Pass `--help` after any command to see its flags. Most resource commands follow the
|
||||
`<noun> <verb>` convention (`overworld set-tile`, `dungeon import-room`, etc.).
|
||||
> Without a provider, z3ed still works but agent commands use manual plans.
|
||||
|
||||
## 3. Agent & Proposal Workflow
|
||||
---
|
||||
|
||||
## Common Commands
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Apply assembly patch | `z3ed asar patch.asm --rom zelda3.sfc` |
|
||||
| List dungeon sprites | `z3ed dungeon list-sprites --dungeon 2 --rom zelda3.sfc` |
|
||||
| Describe overworld map | `z3ed overworld describe-map --map 80 --rom zelda3.sfc` |
|
||||
| Export palettes | `z3ed palette export --rom zelda3.sfc --output palettes.json` |
|
||||
| Validate ROM | `z3ed rom info --rom zelda3.sfc` |
|
||||
|
||||
Commands follow `<noun> <verb>` convention. Use `--help` for flag details:
|
||||
```bash
|
||||
z3ed dungeon --help
|
||||
z3ed dungeon list-sprites --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AI Agent Workflows
|
||||
|
||||
### Interactive Chat
|
||||
|
||||
### 3.1 Interactive Chat
|
||||
```bash
|
||||
z3ed agent chat --rom zelda3.sfc --theme overworld
|
||||
```
|
||||
- Maintains conversation history on disk so you can pause/resume.
|
||||
- Supports tool-calling: the agent invokes subcommands (e.g., `overworld describe-map`) and
|
||||
returns structured diffs.
|
||||
|
||||
### 3.2 Plans & Batches
|
||||
Chat sessions maintain conversation history and can invoke ROM commands automatically.
|
||||
|
||||
### Plan and Apply
|
||||
|
||||
```bash
|
||||
# Generate a proposal but do not apply it
|
||||
z3ed agent plan --prompt "Move the eastern palace entrance 3 tiles east" --rom zelda3.sfc
|
||||
# Create a plan without applying
|
||||
z3ed agent plan --prompt "Move eastern palace entrance 3 tiles east" --rom zelda3.sfc
|
||||
|
||||
# List pending plans
|
||||
z3ed agent list
|
||||
|
||||
# Apply a plan after review
|
||||
# Apply after review
|
||||
z3ed agent accept --proposal-id <id> --rom zelda3.sfc
|
||||
```
|
||||
Plans store the command transcript, diffs, and metadata inside
|
||||
`$XDG_DATA_HOME/yaze/proposals/` (or `%APPDATA%\yaze\proposals\`). Review them before applying to
|
||||
non-sandbox ROMs.
|
||||
|
||||
### 3.3 Non-interactive Scripts
|
||||
Plans are stored in `$XDG_DATA_HOME/yaze/proposals/` (or `%APPDATA%\yaze\proposals\` on Windows).
|
||||
|
||||
### Scripted Prompts
|
||||
|
||||
```bash
|
||||
# Run prompts from a file
|
||||
z3ed agent simple-chat --file scripts/queries.txt --rom zelda3.sfc --stdout
|
||||
# From file
|
||||
z3ed agent simple-chat --file queries.txt --rom zelda3.sfc --stdout
|
||||
|
||||
# Feed stdin (useful in CI)
|
||||
cat <<'PROMPTS' | z3ed agent simple-chat --rom zelda3.sfc --stdout
|
||||
Describe tile 0x3A in map 0x80.
|
||||
Suggest palette swaps for dungeon 2.
|
||||
PROMPTS
|
||||
# From stdin
|
||||
echo "Describe tile 0x3A in map 0x80" | z3ed agent simple-chat --rom zelda3.sfc --stdout
|
||||
```
|
||||
|
||||
## 4. Automation Tips
|
||||
---
|
||||
|
||||
1. **Sandbox first** – point the agent at a copy of your ROM (`--sandbox` flag) so you can review
|
||||
patches safely.
|
||||
2. **Log everything** – `--log-file agent.log` captures the provider transcript for auditing.
|
||||
3. **Structure output** – most list/describe commands support `--format json` or `--format yaml`
|
||||
for downstream tooling.
|
||||
4. **Combine with `yaze_test`** – run `./build_ai/bin/yaze_test --unit` after batch patches to
|
||||
confirm nothing regressed.
|
||||
5. **Use TUI filters** – in `--tui`, press `:` to open the command palette, type part of a command,
|
||||
hit Enter, and the tool auto-fills the available flags.
|
||||
## Best Practices
|
||||
|
||||
## 5. Troubleshooting
|
||||
| Tip | Description |
|
||||
|-----|-------------|
|
||||
| **Use sandbox mode** | `--sandbox` flag creates a copy for safe testing |
|
||||
| **Log sessions** | `--log-file agent.log` captures transcripts |
|
||||
| **Structured output** | `--format json` or `--format yaml` for scripting |
|
||||
| **Run tests after patches** | `./build/bin/yaze_test --unit` |
|
||||
| **TUI command palette** | Press `:` in TUI mode to search commands |
|
||||
|
||||
| Symptom | Fix |
|
||||
| --- | --- |
|
||||
| `agent chat` hangs after a prompt | Ensure `ollama serve` or the Gemini API key is configured. |
|
||||
| `libgrpc` or `absl` missing | Re-run the `*-ai` preset; plain debug presets do not pull the agent stack. |
|
||||
| CLI cannot find the ROM | Use absolute paths or set `YAZE_DEFAULT_ROM=/path/to/zelda3.sfc`. |
|
||||
| Tool reports "command not found" | Run `z3ed --help` to refresh the command index; stale binaries from older builds lack new verbs. |
|
||||
| Proposal diffs are empty | Provide `--rom` plus either `--sandbox` or `--workspace` so the agent knows where to stage files. |
|
||||
---
|
||||
|
||||
## 6. Related Documentation
|
||||
- `docs/public/developer/testing-without-roms.md` – ROM-less fixtures for CI.
|
||||
- `docs/public/developer/debugging-guide.md` – logging and instrumentation tips shared between the
|
||||
GUI and CLI.
|
||||
- `docs/internal/agents/` – deep dives into the agent architecture and refactor plans (internal
|
||||
audience only).
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| `agent chat` hangs | Verify `ollama serve` is running or `GEMINI_API_KEY` is set |
|
||||
| Missing `libgrpc` or `absl` | Rebuild with `*-ai` preset |
|
||||
| ROM not found | Use absolute paths or set `YAZE_DEFAULT_ROM` |
|
||||
| Command not found | Run `z3ed --help` to verify build is current |
|
||||
| Empty proposal diffs | Include `--rom` with `--sandbox` or `--workspace` |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Testing Without ROMs](../developer/testing-without-roms.md) - CI fixtures
|
||||
- [Debugging Guide](../developer/debugging-guide.md) - Logging and instrumentation
|
||||
- [CLI Reference](../cli/README.md) - Complete command documentation
|
||||
|
||||
Reference in New Issue
Block a user