backend-infra-engineer: Release v0.3.3 snapshot
This commit is contained in:
461
docs/internal/research/apu-timing-analysis.md
Normal file
461
docs/internal/research/apu-timing-analysis.md
Normal file
@@ -0,0 +1,461 @@
|
||||
# APU Timing Fix - Technical Analysis
|
||||
|
||||
**Branch:** `feature/apu-timing-fix`
|
||||
**Date:** October 10, 2025
|
||||
**Status:** Implemented - Core Timing Fixed (Minor Audio Glitches Remain)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Completed:**
|
||||
- Atomic `Step()` function for SPC700
|
||||
- Fixed-point cycle ratio (no floating-point drift)
|
||||
- Cycle budget model in APU
|
||||
- Removed `bstep` mechanism from instructions.cc
|
||||
- Cycle-accurate instruction implementations
|
||||
- Proper branch timing (+2 cycles when taken)
|
||||
- Dummy read/write cycles for MOV and RMW instructions
|
||||
|
||||
**Known Issues:**
|
||||
- Some audio glitches/distortion during playback
|
||||
- Minor timing inconsistencies under investigation
|
||||
- Can be improved in future iterations
|
||||
|
||||
**Note:** The APU now executes correctly and music plays, but audio quality can be further refined.
|
||||
|
||||
## Problem Summary
|
||||
|
||||
The APU fails to load and play music because the SPC700 gets stuck during the initial CPU-APU handshake. This handshake uploads the sound driver from ROM to APU RAM. The timing desynchronization causes infinite loops detected by the watchdog timer.
|
||||
|
||||
---
|
||||
|
||||
## Current Implementation Analysis
|
||||
|
||||
### 1. **Cycle Counting System** (`spc700.cc`)
|
||||
|
||||
**Current Approach:**
|
||||
```cpp
|
||||
// In spc700.h line 87:
|
||||
int last_opcode_cycles_ = 0;
|
||||
|
||||
// In RunOpcode() line 80:
|
||||
last_opcode_cycles_ = spc700_cycles[opcode]; // Static lookup
|
||||
```
|
||||
|
||||
**Problem:** The `spc700_cycles[]` array provides BASELINE cycle counts only. It does NOT account for:
|
||||
- Addressing mode variations
|
||||
- Page boundary crossings (+1 cycle)
|
||||
- Branch taken vs not taken (+2 cycles if taken)
|
||||
- Memory access penalties
|
||||
|
||||
### 2. **The `bstep` Mechanism** (`spc700.cc`)
|
||||
|
||||
**What is `bstep`?**
|
||||
|
||||
`bstep` is a "business step" counter used to spread complex multi-step instructions across multiple calls to `RunOpcode()`.
|
||||
|
||||
**Example from line 1108-1115 (opcode 0xCB - MOVSY dp):**
|
||||
```cpp
|
||||
case 0xcb: { // movsy dp
|
||||
if (bstep == 0) {
|
||||
adr = dp(); // Save address for bstep=1
|
||||
}
|
||||
if (adr == 0x00F4 && bstep == 1) {
|
||||
LOG_DEBUG("SPC", "MOVSY writing Y=$%02X to F4 at PC=$%04X", Y, PC);
|
||||
}
|
||||
MOVSY(adr); // Use saved address
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
The `MOVSY()` function internally increments `bstep` to track progress:
|
||||
- `bstep=0`: Call `dp()` to get address
|
||||
- `bstep=1`: Actually perform the write
|
||||
- `bstep=2`: Reset to 0, instruction complete
|
||||
|
||||
**Why this is fragile:**
|
||||
1. **Non-atomic execution**: An instruction takes 2-3 calls to `RunOpcode()` to complete
|
||||
2. **State leakage**: If `bstep` gets out of sync, all future instructions fail
|
||||
3. **Cycle accounting errors**: Cycles are consumed incrementally, not atomically
|
||||
4. **Debugging nightmare**: Hard to trace when an instruction "really" executes
|
||||
|
||||
### 3. **APU Main Loop** (`apu.cc:73-143`)
|
||||
|
||||
**Current implementation:**
|
||||
```cpp
|
||||
void Apu::RunCycles(uint64_t master_cycles) {
|
||||
const double ratio = memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster;
|
||||
uint64_t master_delta = master_cycles - g_last_master_cycles;
|
||||
g_last_master_cycles = master_cycles;
|
||||
|
||||
const uint64_t target_apu_cycles = cycles_ + static_cast<uint64_t>(master_delta * ratio);
|
||||
|
||||
while (cycles_ < target_apu_cycles) {
|
||||
spc700_.RunOpcode(); // Variable cycles
|
||||
int spc_cycles = spc700_.GetLastOpcodeCycles();
|
||||
|
||||
for (int i = 0; i < spc_cycles; ++i) {
|
||||
Cycle(); // Advance DSP/timers
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
1. **Floating-point `ratio`**: `apuCyclesPerMaster` is `double` (line 17), causing precision drift
|
||||
2. **Opcode-level granularity**: Advances by opcode, not by cycle
|
||||
3. **No sub-cycle accuracy**: Can't model instructions that span multiple cycles
|
||||
|
||||
### 4. **Floating-Point Precision** (`apu.cc:17`)
|
||||
|
||||
```cpp
|
||||
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
|
||||
```
|
||||
|
||||
**Calculation:**
|
||||
- Numerator: 32040 * 32 = 1,025,280
|
||||
- Denominator: 1364 * 262 * 60.0 = 21,437,280
|
||||
- Result: ~0.04783 (floating point)
|
||||
|
||||
**Problem:** Over thousands of cycles, tiny rounding errors accumulate, causing timing drift.
|
||||
|
||||
---
|
||||
|
||||
## Root Cause: Handshake Timing Failure
|
||||
|
||||
### The Handshake Protocol
|
||||
|
||||
1. **APU Ready**: SPC700 writes `$AA` to `$F4`, `$BB` to `$F5`
|
||||
2. **CPU Waits**: Main CPU polls for `$BBAA`
|
||||
3. **CPU Initiates**: Writes `$CC` to APU input port
|
||||
4. **APU Acknowledges**: SPC700 sees `$CC`, prepares to receive
|
||||
5. **Byte Transfer Loop**: CPU sends byte, waits for echo confirmation, sends next byte
|
||||
|
||||
### Where It Gets Stuck
|
||||
|
||||
The SPC700 enters an infinite loop because:
|
||||
- **SPC700 is waiting** for a byte from CPU (hasn't arrived yet)
|
||||
- **CPU is waiting** for acknowledgment from SPC700 (already sent, but missed)
|
||||
|
||||
This happens because cycle counts are off by 1-2 cycles per instruction, which accumulates over the ~500-1000 instructions in the handshake.
|
||||
|
||||
---
|
||||
|
||||
## LakeSnes Comparison Analysis
|
||||
|
||||
### What LakeSnes Does Right
|
||||
|
||||
**1. Atomic Instruction Execution (spc.c:73-93)**
|
||||
```c
|
||||
void spc_runOpcode(Spc* spc) {
|
||||
if(spc->resetWanted) { /* handle reset */ return; }
|
||||
if(spc->stopped) { spc_idleWait(spc); return; }
|
||||
|
||||
uint8_t opcode = spc_readOpcode(spc);
|
||||
spc_doOpcode(spc, opcode); // COMPLETE instruction in one call
|
||||
}
|
||||
```
|
||||
|
||||
**Key insight:** LakeSnes executes instructions **atomically** - no `bstep`, no `step`, no state leakage.
|
||||
|
||||
**2. Cycle Tracking via Callbacks (spc.c:406-409)**
|
||||
```c
|
||||
static void spc_movsy(Spc* spc, uint16_t adr) {
|
||||
spc_read(spc, adr); // Calls apu_cycle()
|
||||
spc_write(spc, adr, spc->y); // Calls apu_cycle()
|
||||
}
|
||||
```
|
||||
|
||||
Every `spc_read()`, `spc_write()`, and `spc_idle()` call triggers `apu_cycle()`, which:
|
||||
- Advances APU cycle counter
|
||||
- Ticks DSP every 32 cycles
|
||||
- Updates timers
|
||||
|
||||
**3. Simple Addressing Mode Functions (spc.c:189-275)**
|
||||
```c
|
||||
static uint16_t spc_adrDp(Spc* spc) {
|
||||
return spc_readOpcode(spc) | (spc->p << 8);
|
||||
}
|
||||
|
||||
static uint16_t spc_adrDpx(Spc* spc) {
|
||||
uint16_t res = ((spc_readOpcode(spc) + spc->x) & 0xff) | (spc->p << 8);
|
||||
spc_idle(spc); // Extra cycle for indexed addressing
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
Each memory access and idle call automatically advances cycles.
|
||||
|
||||
**4. APU Main Loop (apu.c:73-82)**
|
||||
```c
|
||||
int apu_runCycles(Apu* apu, int wantedCycles) {
|
||||
int runCycles = 0;
|
||||
uint32_t startCycles = apu->cycles;
|
||||
while(runCycles < wantedCycles) {
|
||||
spc_runOpcode(apu->spc);
|
||||
runCycles += (uint32_t) (apu->cycles - startCycles);
|
||||
startCycles = apu->cycles;
|
||||
}
|
||||
return runCycles;
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:** This approach tracks cycles by **delta**, which works because every memory access calls `apu_cycle()`.
|
||||
|
||||
### Where LakeSnes Falls Short (And How We Can Do Better)
|
||||
|
||||
**1. No Explicit Cycle Return**
|
||||
- LakeSnes relies on tracking `cycles` delta after each opcode
|
||||
- Doesn't return precise cycle count from `spc_runOpcode()`
|
||||
- Makes it hard to validate cycle accuracy per instruction
|
||||
|
||||
**Our improvement:** Return exact cycle count from `Step()`:
|
||||
```cpp
|
||||
int Spc700::Step() {
|
||||
uint8_t opcode = ReadOpcode();
|
||||
int cycles = CalculatePreciseCycles(opcode);
|
||||
ExecuteInstructionAtomic(opcode);
|
||||
return cycles; // EXPLICIT return
|
||||
}
|
||||
```
|
||||
|
||||
**2. Implicit Cycle Counting**
|
||||
- Cycles accumulated implicitly through callbacks
|
||||
- Hard to debug when cycles are wrong
|
||||
- No way to verify cycle accuracy per instruction
|
||||
|
||||
**Our improvement:** Explicit cycle budget model in `Apu::RunCycles()`:
|
||||
```cpp
|
||||
while (cycles_ < target_apu_cycles) {
|
||||
int spc_cycles = spc700_.Step(); // Explicit cycle count
|
||||
for (int i = 0; i < spc_cycles; ++i) {
|
||||
Cycle(); // Explicit cycle advancement
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. No Fixed-Point Ratio**
|
||||
- LakeSnes also uses floating-point (implicitly in SNES main loop)
|
||||
- Subject to same precision drift issues
|
||||
|
||||
**Our improvement:** Integer numerator/denominator for perfect precision.
|
||||
|
||||
### What We're Adopting from LakeSnes
|
||||
|
||||
**Atomic instruction execution** - No `bstep` mechanism
|
||||
**Simple addressing mode functions** - Return address, advance cycles via callbacks
|
||||
**Cycle advancement per memory access** - Every read/write/idle advances cycles
|
||||
|
||||
### What We're Improving Over LakeSnes
|
||||
|
||||
**Explicit cycle counting** - `Step()` returns exact cycles consumed
|
||||
**Cycle budget model** - Clear loop with explicit cycle advancement
|
||||
**Fixed-point ratio** - Integer arithmetic for perfect precision
|
||||
**Testability** - Easy to verify cycle counts per instruction
|
||||
|
||||
---
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Phase 1: Atomic Instruction Execution
|
||||
|
||||
**Goal:** Eliminate `bstep` mechanism entirely.
|
||||
|
||||
**New Design:**
|
||||
```cpp
|
||||
// New function signature
|
||||
int Spc700::Step() {
|
||||
if (reset_wanted_) { /* handle reset */ return 8; }
|
||||
if (stopped_) { /* handle stop */ return 2; }
|
||||
|
||||
// Fetch opcode
|
||||
uint8_t opcode = ReadOpcode();
|
||||
|
||||
// Calculate EXACT cycle cost upfront
|
||||
int cycles = CalculatePreciseCycles(opcode);
|
||||
|
||||
// Execute instruction COMPLETELY
|
||||
ExecuteInstructionAtomic(opcode);
|
||||
|
||||
return cycles; // Return exact cycles consumed
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- One call = one complete instruction
|
||||
- Cycles calculated before execution
|
||||
- No state leakage between calls
|
||||
- Easier debugging
|
||||
|
||||
### Phase 2: Precise Cycle Calculation
|
||||
|
||||
**New function:**
|
||||
```cpp
|
||||
int Spc700::CalculatePreciseCycles(uint8_t opcode) {
|
||||
int base_cycles = spc700_cycles[opcode];
|
||||
|
||||
// Account for addressing mode penalties
|
||||
switch (opcode) {
|
||||
case 0x10: case 0x30: /* ... branches ... */
|
||||
// Branches: +2 cycles if taken (handled in execution)
|
||||
break;
|
||||
case 0x15: case 0x16: /* ... abs+X, abs+Y ... */
|
||||
// Check if page boundary crossed (+1 cycle)
|
||||
if (will_cross_page_boundary(opcode)) {
|
||||
base_cycles += 1;
|
||||
}
|
||||
break;
|
||||
// ... more addressing mode checks ...
|
||||
}
|
||||
|
||||
return base_cycles;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Refactor `Apu::RunCycles` to Cycle Budget Model
|
||||
|
||||
**New implementation:**
|
||||
```cpp
|
||||
void Apu::RunCycles(uint64_t master_cycles) {
|
||||
// 1. Calculate target using FIXED-POINT ratio (Phase 4)
|
||||
uint64_t master_delta = master_cycles - g_last_master_cycles;
|
||||
g_last_master_cycles = master_cycles;
|
||||
|
||||
// 2. Fixed-point conversion (avoiding floating point)
|
||||
uint64_t target_apu_cycles = cycles_ + (master_delta * kApuCyclesNumerator) / kApuCyclesDenominator;
|
||||
|
||||
// 3. Run until budget exhausted
|
||||
while (cycles_ < target_apu_cycles) {
|
||||
// 4. Execute ONE instruction atomically
|
||||
int spc_cycles_consumed = spc700_.Step();
|
||||
|
||||
// 5. Advance DSP/timers for each cycle
|
||||
for (int i = 0; i < spc_cycles_consumed; ++i) {
|
||||
Cycle(); // Ticks DSP, timers, increments cycles_
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Fixed-Point Cycle Ratio
|
||||
|
||||
**Replace floating-point with integer ratio:**
|
||||
```cpp
|
||||
// Old (apu.cc:17)
|
||||
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
|
||||
|
||||
// New
|
||||
static constexpr uint64_t kApuCyclesNumerator = 32040 * 32; // 1,025,280
|
||||
static constexpr uint64_t kApuCyclesDenominator = 1364 * 262 * 60; // 21,437,280
|
||||
```
|
||||
|
||||
**Conversion:**
|
||||
```cpp
|
||||
apu_cycles = (master_cycles * kApuCyclesNumerator) / kApuCyclesDenominator;
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Perfect precision (no floating-point drift)
|
||||
- Integer arithmetic is faster
|
||||
- Deterministic across platforms
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: Add `Spc700::Step()` Function
|
||||
- Add new `Step()` method to `spc700.h`
|
||||
- Implement atomic instruction execution
|
||||
- Keep `RunOpcode()` temporarily for compatibility
|
||||
|
||||
### Step 2: Implement Precise Cycle Calculation
|
||||
- Create `CalculatePreciseCycles()` helper
|
||||
- Handle branch penalties
|
||||
- Handle page boundary crossings
|
||||
- Add tests to verify against known SPC700 timings
|
||||
|
||||
### Step 3: Eliminate `bstep` Mechanism
|
||||
- Refactor all multi-step instructions (0xCB, 0xD0, 0xD7, etc.)
|
||||
- Remove `bstep` variable
|
||||
- Remove `step` variable
|
||||
- Verify all 256 opcodes work atomically
|
||||
|
||||
### Step 4: Refactor `Apu::RunCycles`
|
||||
- Switch to cycle budget model
|
||||
- Use `Step()` instead of `RunOpcode()`
|
||||
- Add cycle budget logging for debugging
|
||||
|
||||
### Step 5: Convert to Fixed-Point Ratio
|
||||
- Replace `apuCyclesPerMaster` double
|
||||
- Use integer numerator/denominator
|
||||
- Add constants for PAL timing too
|
||||
|
||||
### Step 6: Testing
|
||||
- Test with vanilla Zelda3 ROM
|
||||
- Verify handshake completes
|
||||
- Verify music plays
|
||||
- Check for watchdog timeouts
|
||||
- Measure timing accuracy
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. **src/app/emu/audio/spc700.h**
|
||||
- Add `int Step()` method
|
||||
- Add `int CalculatePreciseCycles(uint8_t opcode)`
|
||||
- Remove `bstep` and `step` variables
|
||||
|
||||
2. **src/app/emu/audio/spc700.cc**
|
||||
- Implement `Step()`
|
||||
- Implement `CalculatePreciseCycles()`
|
||||
- Refactor `ExecuteInstructions()` to be atomic
|
||||
- Remove all `bstep` logic
|
||||
|
||||
3. **src/app/emu/audio/apu.h**
|
||||
- Update cycle ratio constants
|
||||
|
||||
4. **src/app/emu/audio/apu.cc**
|
||||
- Refactor `RunCycles()` to use `Step()`
|
||||
- Convert to fixed-point ratio
|
||||
- Remove floating-point arithmetic
|
||||
|
||||
5. **test/unit/spc700_timing_test.cc** (new)
|
||||
- Test cycle accuracy for all opcodes
|
||||
- Test handshake simulation
|
||||
- Verify no regressions
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] All SPC700 instructions execute atomically (one `Step()` call)
|
||||
- [x] Cycle counts accurate to ±1 cycle per instruction
|
||||
- [x] APU handshake completes without watchdog timeout
|
||||
- [x] Music loads and plays in vanilla Zelda3
|
||||
- [x] No floating-point drift over long emulation sessions
|
||||
- [ ] Unit tests pass for all 256 opcodes (future work)
|
||||
- [ ] Audio quality refined (minor glitches remain)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Completed
|
||||
|
||||
1. Create feature branch
|
||||
2. Analyze current implementation
|
||||
3. Implement `Spc700::Step()` function
|
||||
4. Add precise cycle calculation
|
||||
5. Refactor `Apu::RunCycles`
|
||||
6. Convert to fixed-point ratio
|
||||
7. Refactor instructions.cc to be atomic and cycle-accurate
|
||||
8. Test with Zelda3 ROM
|
||||
9. Write unit tests (future work)
|
||||
10. Fine-tune audio quality (future work)
|
||||
|
||||
---
|
||||
|
||||
**References:**
|
||||
- [SPC700 Opcode Reference](https://problemkaputt.de/fullsnes.htm#snesapucpu)
|
||||
- [APU Timing Documentation](https://wiki.superfamicom.org/spc700-reference)
|
||||
- docs/E6-emulator-improvements.md
|
||||
1939
docs/internal/research/emulator-debugging-vision.md
Normal file
1939
docs/internal/research/emulator-debugging-vision.md
Normal file
File diff suppressed because it is too large
Load Diff
234
docs/internal/research/emulator-improvements.md
Normal file
234
docs/internal/research/emulator-improvements.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Emulator Core Improvements Roadmap
|
||||
|
||||
**Last Updated:** October 10, 2025
|
||||
**Status:** Active Planning
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines improvements, refactors, and optimizations for the yaze emulator core. These changes aim to enhance accuracy, performance, and code maintainability.
|
||||
|
||||
Items are presented in order of descending priority, from critical accuracy fixes to quality-of-life improvements.
|
||||
|
||||
---
|
||||
|
||||
## Critical Priority: APU Timing Fix
|
||||
|
||||
### Problem Statement
|
||||
|
||||
The emulator's Audio Processing Unit (APU) currently fails to load and play music. Analysis shows that the SPC700 processor gets "stuck" during the initial handshake sequence with the main CPU. This handshake is responsible for uploading the sound driver from ROM to APU RAM. The failure of this timing-sensitive process prevents the sound driver from running.
|
||||
|
||||
### Root Cause: CPU-APU Handshake Timing
|
||||
|
||||
The process of starting the APU and loading a sound bank requires tightly synchronized communication between the main CPU (65816) and the APU's CPU (SPC700).
|
||||
|
||||
#### The Handshake Protocol
|
||||
|
||||
1. **APU Ready**: SPC700 boots, initializes, signals ready by writing `$AA` to port `$F4` and `$BB` to port `$F5`
|
||||
2. **CPU Waits**: Main CPU waits in tight loop, reading combined 16-bit value from I/O ports until it sees `$BBAA`
|
||||
3. **CPU Initiates**: CPU writes command code `$CC` to APU's input port
|
||||
4. **APU Acknowledges**: SPC700 sees `$CC` and prepares to receive data block
|
||||
5. **Synchronized Byte Transfer**: CPU and APU enter lock-step loop to transfer sound driver byte-by-byte:
|
||||
* CPU sends data
|
||||
* CPU waits for APU to read data and echo back confirmation
|
||||
* Only upon receiving confirmation does CPU send next byte
|
||||
|
||||
#### Point of Failure
|
||||
|
||||
The "stuck" behavior occurs because one side fails to meet the other's expectation. Due to timing desynchronization:
|
||||
* The SPC700 is waiting for a byte that the CPU has not yet sent (or sent too early), OR
|
||||
* The CPU is waiting for an acknowledgment that the SPC700 has already sent (or has not yet sent)
|
||||
|
||||
The result is an infinite loop on the SPC700, detected by the watchdog timer in `Apu::RunCycles`.
|
||||
|
||||
### Technical Analysis
|
||||
|
||||
The handshake's reliance on precise timing exposes inaccuracies in the current SPC700 emulation model.
|
||||
|
||||
#### Issue 1: Incomplete Opcode Timing
|
||||
|
||||
The emulator uses a static lookup table (`spc700_cycles.h`) for instruction cycle counts. This provides a *base* value but fails to account for:
|
||||
* **Addressing Modes**: Different addressing modes have different cycle costs
|
||||
* **Page Boundaries**: Memory accesses crossing 256-byte page boundaries take an extra cycle
|
||||
* **Branching**: Conditional branches take different cycle counts depending on whether branch is taken
|
||||
|
||||
While some of this is handled (e.g., `DoBranch`), it is not applied universally, leading to small, cumulative errors.
|
||||
|
||||
#### Issue 2: Fragile Multi-Step Execution Model
|
||||
|
||||
The `step`/`bstep` mechanism in `Spc700::RunOpcode` is a significant source of fragility. It attempts to model complex instructions by spreading execution across multiple calls. This means the full cycle cost of an instruction is not consumed atomically. An off-by-one error in any step corrupts the timing of the entire APU.
|
||||
|
||||
#### Issue 3: Floating-Point Precision
|
||||
|
||||
The use of `double` for the `apuCyclesPerMaster` ratio can introduce minute floating-point precision errors. Over thousands of cycles required for the handshake, these small errors accumulate and contribute to timing drift between CPU and APU.
|
||||
|
||||
### Proposed Solution: Cycle-Accurate Refactoring
|
||||
|
||||
#### Step 1: Implement Cycle-Accurate Instruction Execution
|
||||
|
||||
The `Spc700::RunOpcode` function must be refactored to calculate and consume the *exact* cycle count for each instruction *before* execution.
|
||||
|
||||
* **Calculate Exact Cost**: Before running an opcode, determine its precise cycle cost by analyzing opcode, addressing mode, and potential page-boundary penalties
|
||||
* **Atomic Execution**: Remove the `bstep` mechanism. An instruction, no matter how complex, should be fully executed within a single call to a new `Spc700::Step()` function
|
||||
|
||||
#### Step 2: Centralize the APU Execution Loop
|
||||
|
||||
The main `Apu::RunCycles` loop should be the sole driver of APU time.
|
||||
|
||||
* **Cycle Budget**: At the start of a frame, calculate the total "budget" of APU cycles needed
|
||||
* **Cycle-by-Cycle Stepping**: Loop, calling `Spc700::Step()` and `Dsp::Cycle()`, decrementing cycle budget until exhausted
|
||||
|
||||
**Example of the new loop in `Apu::RunCycles`:**
|
||||
```cpp
|
||||
void Apu::RunCycles(uint64_t master_cycles) {
|
||||
// 1. Calculate cycle budget for this frame
|
||||
const uint64_t target_apu_cycles = ...;
|
||||
|
||||
// 2. Run the APU until the budget is met
|
||||
while (cycles_ < target_apu_cycles) {
|
||||
// 3. Execute one SPC700 cycle/instruction and get its true cost
|
||||
int spc_cycles_consumed = spc700_.Step();
|
||||
|
||||
// 4. Advance DSP and Timers for each cycle consumed
|
||||
for (int i = 0; i < spc_cycles_consumed; ++i) {
|
||||
Cycle(); // This ticks the DSP and timers
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3: Use Integer-Based Cycle Ratios
|
||||
|
||||
To eliminate floating-point errors, convert the `apuCyclesPerMaster` ratio to a fixed-point integer ratio. This provides perfect, drift-free conversion between main CPU and APU cycles over long periods.
|
||||
|
||||
---
|
||||
|
||||
## High Priority: Core Architecture & Timing Model
|
||||
|
||||
### CPU Cycle Counting
|
||||
|
||||
* **Issue:** The main CPU loop in `Snes::RunCycle()` advances the master cycle counter by a fixed amount (`+= 2`). Real 65816 instructions have variable cycle counts. The current workaround of scattering `callbacks_.idle()` calls is error-prone and difficult to maintain.
|
||||
* **Recommendation:** Refactor `Cpu::ExecuteInstruction` to calculate and return the *precise* cycle cost of each instruction, including penalties for addressing modes and memory access speeds. The main `Snes` loop should then consume this exact value, centralizing timing logic and dramatically improving accuracy.
|
||||
|
||||
### Main Synchronization Loop
|
||||
|
||||
* **Issue:** The main loop in `Snes::RunFrame()` is state-driven based on the `in_vblank_` flag. This can be fragile and makes it difficult to reason about component state at any given cycle.
|
||||
* **Recommendation:** Transition to a unified main loop driven by a single master cycle counter. In this model, each component (CPU, PPU, APU, DMA) is "ticked" forward based on the master clock. This is a more robust and modular architecture that simplifies component synchronization.
|
||||
|
||||
---
|
||||
|
||||
## Medium Priority: PPU Performance
|
||||
|
||||
### Rendering Approach Optimization
|
||||
|
||||
* **Issue:** The PPU currently uses a "pixel-based" renderer (`Ppu::RunLine` calls `HandlePixel` for every pixel). This is highly accurate but can be slow due to high function call overhead and poor cache locality.
|
||||
* **Optimization:** Refactor the PPU to use a **scanline-based renderer**. Instead of processing one pixel at a time, process all active layers for an entire horizontal scanline, compose them into a temporary buffer, and then write the completed scanline to the framebuffer. This is a major architectural change but is a standard and highly effective optimization technique in SNES emulation.
|
||||
|
||||
**Benefits:**
|
||||
- Reduced function call overhead
|
||||
- Better cache locality
|
||||
- Easier to vectorize/SIMD
|
||||
- Standard approach in accurate SNES emulators
|
||||
|
||||
---
|
||||
|
||||
## Low Priority: Code Quality & Refinements
|
||||
|
||||
### APU Code Modernization
|
||||
|
||||
* **Issue:** The code in `dsp.cc` and `spc700.cc`, inherited from other projects, is written in a very C-like style, using raw pointers, `memset`, and numerous "magic numbers."
|
||||
* **Refactor:** Gradually refactor this code to use modern C++ idioms:
|
||||
- Replace raw arrays with `std::array`
|
||||
- Use constructors with member initializers instead of `memset`
|
||||
- Define `constexpr` variables or `enum class` types for hardware registers and flags
|
||||
- Improve type safety, readability, and long-term maintainability
|
||||
|
||||
### Audio Subsystem & Buffering
|
||||
|
||||
* **Issue:** The current implementation in `Emulator::Run` queues audio samples directly to the SDL audio device. If the emulator lags for even a few frames, the audio buffer can underrun, causing audible pops and stutters.
|
||||
* **Improvement:** Implement a **lock-free ring buffer (or circular buffer)** to act as an intermediary. The emulator thread would continuously write generated samples into this buffer, while the audio device (in its own thread) would continuously read from it. This decouples the emulation speed from the audio hardware, smoothing out performance fluctuations and preventing stutter.
|
||||
|
||||
### Debugger & Tooling Optimizations
|
||||
|
||||
#### DisassemblyViewer Data Structure
|
||||
* **Issue:** `DisassemblyViewer` uses a `std::map` to store instruction traces. For a tool that handles frequent insertions and lookups, this can be suboptimal.
|
||||
* **Optimization:** Replace `std::map` with `std::unordered_map` for faster average-case performance.
|
||||
|
||||
#### BreakpointManager Lookups
|
||||
* **Issue:** The `ShouldBreakOn...` functions perform a linear scan over a `std::vector` of all breakpoints. This is O(n) and could become a minor bottleneck if a very large number of breakpoints are set.
|
||||
* **Optimization:** For execution breakpoints, use a `std::unordered_set<uint32_t>` for O(1) average lookup time. This would make breakpoint checking near-instantaneous, regardless of how many are active.
|
||||
|
||||
---
|
||||
|
||||
## Completed Improvements
|
||||
|
||||
### Audio System Fixes (v0.4.0)
|
||||
|
||||
#### Problem Statement
|
||||
The SNES emulator experienced audio glitchiness and skips, particularly during the ALTTP title screen, with audible pops, crackling, and sample skipping during music playback.
|
||||
|
||||
#### Root Causes Fixed
|
||||
1. **Aggressive Sample Dropping**: Audio buffering logic was dropping up to 50% of generated samples, creating discontinuities
|
||||
2. **Incorrect Resampling**: Duplicate calculations in linear interpolation wasted CPU cycles
|
||||
3. **Missing Frame Synchronization**: DSP's `NewFrame()` method was never called, causing timing drift
|
||||
4. **Missing Hermite Interpolation**: Only Linear/Cosine/Cubic were available (Hermite is the industry standard)
|
||||
|
||||
#### Solutions Implemented
|
||||
1. **Never Drop Samples**: Always queue all generated samples unless buffer critically full (>4 frames)
|
||||
2. **Fixed Resampling Code**: Removed duplicate calculations and added bounds checking
|
||||
3. **Frame Boundary Synchronization**: Added `dsp.NewFrame()` call before sample generation
|
||||
4. **Hermite Interpolation**: New interpolation type matching bsnes/Snes9x standard
|
||||
|
||||
**Interpolation options** (`src/app/emu/audio/dsp.cc`):
|
||||
|
||||
| Interpolation | Notes |
|
||||
|--------------|-------|
|
||||
| Linear | Fastest; retains legacy behaviour. |
|
||||
| Hermite | New default; balances quality and speed. |
|
||||
| Cosine | Smoother than linear with moderate cost. |
|
||||
| Cubic | Highest quality, heavier CPU cost. |
|
||||
|
||||
**Result**: Manual testing on the ALTTP title screen, overworld theme, dungeon ambience, and menu sounds no longer exhibits audible pops or skips. Continue to monitor regression tests after the APU timing refactor lands.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **Critical (v0.4.0):** APU timing fix - Required for music playback
|
||||
2. **High (v0.5.0):** CPU cycle counting accuracy - Required for game compatibility
|
||||
3. **High (v0.5.0):** Main synchronization loop refactor - Foundation for accuracy
|
||||
4. **Medium (v0.6.0):** PPU scanline renderer - Performance optimization
|
||||
5. **Low (ongoing):** Code quality improvements - Technical debt reduction
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### APU Timing Fix Success
|
||||
- [ ] Music plays in all tested games
|
||||
- [ ] Sound effects work correctly
|
||||
- [ ] No audio glitches or stuttering
|
||||
- [ ] Handshake completes within expected cycle count
|
||||
|
||||
### Overall Emulation Accuracy
|
||||
- [ ] CPU cycle accuracy within ±1 cycle per instruction
|
||||
- [ ] APU synchronized within ±1 cycle with CPU
|
||||
- [ ] PPU timing accurate to scanline level
|
||||
- [ ] All test ROMs pass
|
||||
|
||||
### Performance Targets
|
||||
- [ ] 60 FPS on modest hardware (2015+ laptops)
|
||||
- [ ] PPU optimizations provide 20%+ speedup
|
||||
- [ ] Audio buffer never underruns in normal operation
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- `docs/E4-Emulator-Development-Guide.md` - Implementation details
|
||||
- `docs/E1-emulator-enhancement-roadmap.md` - Feature roadmap
|
||||
- `docs/E5-debugging-guide.md` - Debugging techniques
|
||||
|
||||
---
|
||||
|
||||
**Status:** Active Planning
|
||||
**Next Steps:** Begin APU timing refactoring for v0.4.0
|
||||
66
docs/internal/research/web-ideas.md
Normal file
66
docs/internal/research/web-ideas.md
Normal file
@@ -0,0 +1,66 @@
|
||||
Here's a breakdown of how to execute that strategy:
|
||||
|
||||
That’s a fantastic approach. It's the classic and most-respected "build in public" model, where you lead with value, and the community supports you because they want to see you succeed. The motivation is pure, which is exactly why people will be happy to donate.
|
||||
|
||||
Here is a game plan to turn "Halext Labs" into the central hub for this, using your two key assets.
|
||||
|
||||
### 1. The "Hype" Engine: `Oracle of Secrets`
|
||||
|
||||
This is your "top-of-funnel" asset. It’s the flashy, exciting project that draws players and new fans in. Your goal here is to **capture and direct** that hype.
|
||||
|
||||
* **Dedicated Project Page (Priority 1):** That `/oracle-of-secrets` page we discussed is your most important new page. It needs to be the definitive, official source.
|
||||
* **Killer Feature:** A **gameplay trailer**. This is non-negotiable for a ROM hack. Make a 1-2 minute video showing off new areas, puzzles, and "wow" moments. Host it on YouTube (as "Halext Labs") and embed it at the top of this page.
|
||||
* **"The Pitch":** Screenshots, a bulleted list of new features, and a clear "Download Patch" button.
|
||||
* **The "Hook":** On this page, you add your first call-to-action: "Want to discuss the hack or get help? **Join the Halext Labs Discord.**"
|
||||
|
||||
* **Content Marketing (Your New Blog):**
|
||||
* **Blog Post 1: "The Making of Oracle of Secrets."** A full post-mortem. Talk about your inspiration, the challenges, and show old, "work-in-progress" screenshots. People *love* this.
|
||||
* **Blog Post 2: "My Top 5 Favorite Puzzles in OoT (And How I Built Them)."** This does double-duty: it's fun for players and a technical showcase for other hackers.
|
||||
|
||||
### 2. The "Platform" Engine: `Yaze`
|
||||
|
||||
This is your "long-term value" asset. This is what will keep other *creators* (hackers, devs) coming back. These are your most dedicated future supporters.
|
||||
|
||||
* **Dedicated Project Page (Priority 2):** The `/yaze` page is your "product" page.
|
||||
* **The "Pitch":** "An All-in-One Z3 Editor, Emulator, and Debugger." Show screenshots of the UI.
|
||||
* **Clear Downloads:** Link directly to your GitHub Releases.
|
||||
* **The "Hook":** "Want to request a feature, report a bug, or show off what you've made? **Join the Halext Labs Discord.**"
|
||||
|
||||
* **Content Marketing (Your New Blog):**
|
||||
* **Blog Post 1: "Why I Built My Own Z3 Editor: The Yaze Story."** Talk about the limitations of existing tools and what your C++ approach solves.
|
||||
* **Blog Post 2: "Tutorial: How to Make Your First ROM Hack with Yaze."** A simple, step-by-step guide. This is how you create new users for your platform.
|
||||
|
||||
### 3. The Community Hub: The Discord Server
|
||||
|
||||
Notice both "hooks" point to the same place. You need a central "home" for all this engagement. A blog is for one-way announcements; a Discord is for two-way community.
|
||||
|
||||
* **Set up a "Halext Labs" Discord Server.** It's free.
|
||||
* **Key Channels:**
|
||||
* `#announcements` (where you post links to your new blog posts and tool releases)
|
||||
* `#general-chat`
|
||||
* `#oracle-of-secrets-help` (for players)
|
||||
* `#yaze-support` (for users)
|
||||
* `#bug-reports`
|
||||
* `#showcase` (This is critical! A place for people to show off the cool stuff *they* made with Yaze. This builds loyalty.)
|
||||
|
||||
### 4. The "Support Me" Funnel (The Gentle Capitalization)
|
||||
|
||||
Now that you have the hype, the platform, and the community, you can *gently* introduce the support links.
|
||||
|
||||
1. **Set Up the Platforms:**
|
||||
* **GitHub Sponsors:** This is the most "tech guy" way. It's built right into your profile and `scawful/yaze` repo. It feels very natural for supporting an open-source tool.
|
||||
* **Patreon:** Also excellent. You can brand it "Halext Labs on Patreon."
|
||||
|
||||
2. **Create Your "Tiers" (Keep it Simple):**
|
||||
* **$2/mo: "Supporter"** -> Gets a special "Supporter" role in the Discord (a colored name). This is the #1 low-effort, high-value reward.
|
||||
* **$5/mo: "Insider"** -> Gets the "Supporter" role + access to a private `#dev-diary` channel where you post work-in-progress screenshots and ideas before anyone else.
|
||||
* **$10/mo: "Credit"** -> All the above + their name on a "Supporters" page on `halext.org`.
|
||||
|
||||
3. **Place Your Links (The Funnel):**
|
||||
* In your GitHub repo `README.md` for Yaze.
|
||||
* On the new `/yaze` and `/oracle-of-secrets` pages ("Enjoy my work? Consider supporting Halext Labs on [Patreon] or [GitHub Sponsors].")
|
||||
* In the footer of `halext.org`.
|
||||
* In the description of your new YouTube trailers/tutorials.
|
||||
* In a pinned message in your Discord's `#announcements` channel.
|
||||
|
||||
This plan directly links the "fun" (OoT, Yaze) to the "engagement" (Blog, Discord) and provides a clear, no-pressure path for those engaged fans to become supporters.
|
||||
414
docs/internal/research/yaze.org
Normal file
414
docs/internal/research/yaze.org
Normal file
@@ -0,0 +1,414 @@
|
||||
#+TITLE: YAZE Development Tracker
|
||||
#+SUBTITLE: Yet Another Zelda3 Editor
|
||||
#+AUTHOR: @scawful
|
||||
#+EMAIL: scawful@users.noreply.github.com
|
||||
#+DATE: 2025-01-31
|
||||
#+STARTUP: overview
|
||||
#+TODO: TODO ACTIVE FEEDBACK VERIFY | DONE CANCELLED
|
||||
#+TAGS: bug(b) feature(f) refactor(r) ui(u) performance(p) docs(d)
|
||||
#+PRIORITIES: A C B
|
||||
#+COLUMNS: %25ITEM %TODO %3PRIORITY %TAGS
|
||||
|
||||
* Active Issues [0/6]
|
||||
** TODO [#A] Overworld sprites can't be moved on the overworld canvas :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
- Issue: Sprites are not responding to drag operations
|
||||
- Location: Overworld canvas interaction
|
||||
- Impact: Blocks sprite editing workflow
|
||||
|
||||
** TODO [#A] Canvas multi select has issues with large map intersection drawing :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
- Issue: Selection box rendering incorrect when crossing 512px boundaries
|
||||
- Location: Canvas selection system
|
||||
- Note: E2E test exists to reproduce this bug (canvas_selection_test)
|
||||
|
||||
** TODO [#B] Right click randomly shows oversized tile16 :bug:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
- Issue: Context menu displays abnormally large tile16 preview
|
||||
- Location: Right-click context menu
|
||||
- Frequency: Random/intermittent
|
||||
|
||||
** TODO [#B] Overworld Map properties panel popup displaying incorrectly :ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
- Issue: Modal popup positioning or rendering issues
|
||||
- Similar to: Canvas popup fixes (now resolved)
|
||||
- Potential fix: Apply same solution as canvas popup refactoring
|
||||
|
||||
** TODO [#A] Tile8 source canvas palette issues in Tile16 Editor :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:DOCUMENTATION: E7-tile16-editor-palette-system.md
|
||||
:END:
|
||||
- Issue: Tile8 source canvas (current area graphics) shows incorrect colors
|
||||
- Impact: Cannot properly preview tiles before placing them
|
||||
- Root cause: Area graphics not receiving proper palette application
|
||||
- Related issue: Palette buttons (0-7) do not update palettes correctly
|
||||
- Status: Active investigation - graphics buffer processing issue
|
||||
|
||||
** TODO [#C] Scratch space implementation incomplete :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
- Feature: Temporary workspace for tile/object manipulation
|
||||
- Status: Partially implemented
|
||||
- Priority: Low (quality of life feature)
|
||||
|
||||
* Editors
|
||||
** Overworld [2/7]
|
||||
*** DONE Custom Overworld Map Settings Inputs
|
||||
CLOSED: [2024-11-14]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
|
||||
*** DONE Load ZSCOW data from ROM in OverworldMap
|
||||
CLOSED: [2024-11-14]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
|
||||
*** TODO [#A] ZSCustomOverworld Main Palette support :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:DEPENDENCIES: Custom overworld data loading
|
||||
:END:
|
||||
|
||||
*** TODO [#A] ZSCustomOverworld Custom Area BG Color support :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:DEPENDENCIES: ZSCOW Main Palette
|
||||
:END:
|
||||
|
||||
*** TODO [#B] Fix sprite icon draw positions :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
*** TODO [#B] Fix exit icon draw positions :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
*** TODO [#C] Overworld Map screen editor :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
** Dungeon [0/2]
|
||||
*** TODO [#B] Draw dungeon objects :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
- See E5-dungeon-object-system.md for design
|
||||
|
||||
*** ACTIVE [#A] Dungeon Maps screen editor :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
- Currently in active development
|
||||
- Supporting bin graphics for Oracle of Secrets
|
||||
|
||||
** Graphics [1/2]
|
||||
*** ACTIVE [#A] Tile16 Editor palette system :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:DOCUMENTATION: E7-tile16-editor-palette-system.md
|
||||
:STATUS: In Progress
|
||||
:END:
|
||||
- [X] Fix palette system crashes (SIGBUS errors)
|
||||
- [X] Three-column layout refactoring
|
||||
- [X] Dynamic zoom controls
|
||||
- [X] Canvas popup fixes
|
||||
- [ ] Tile8 source canvas palette issues (incorrect colors)
|
||||
- [ ] Palette button update functionality (not working)
|
||||
- [ ] Color consistency between canvases
|
||||
|
||||
*** TODO [#C] Fix graphics sheet pencil drawing :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
** Message [0/1]
|
||||
*** TODO [#C] Fix Message Parsing :bug:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
** Palette [0/1]
|
||||
*** TODO [#B] Persist color changes for saving to ROM :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
** Screens [2/5]
|
||||
*** ACTIVE [#A] Dungeon Maps :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
|
||||
*** ACTIVE [#B] Inventory Menu :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
*** TODO [#C] Overworld Map screen :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
*** TODO [#C] Title Screen editor :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
*** TODO [#C] Naming Screen editor :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-01]
|
||||
:END:
|
||||
|
||||
* Infrastructure [4/7]
|
||||
** DONE Package layout files with executable
|
||||
CLOSED: [2024-09-07]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
** DONE Create util for bundled resource handling
|
||||
CLOSED: [2024-09-07]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
** DONE DisplayPalette function extraction
|
||||
CLOSED: [2024-09-02]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-02]
|
||||
:END:
|
||||
|
||||
** DONE Header cleanup with LSP
|
||||
CLOSED: [2024-09-07]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
** TODO [#B] Update recent files manager for bundled apps :refactor:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
** TODO [#C] Make font sizes configurable :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
** TODO [#C] Cross-platform font/asset loading :refactor:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-09-07]
|
||||
:END:
|
||||
|
||||
* Testing [4/6]
|
||||
** DONE [#A] E2E testing framework infrastructure
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:DOCUMENTATION: A1-testing-guide.md
|
||||
:END:
|
||||
|
||||
** DONE [#A] Canvas selection E2E test
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Stable test suite (CI/CD)
|
||||
CLOSED: [2024-11-14]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
|
||||
** DONE [#B] ROM-dependent test separation
|
||||
CLOSED: [2024-11-14]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2024-11-14]
|
||||
:END:
|
||||
|
||||
** TODO [#B] Expand E2E test coverage :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#C] E2E CI/CD integration with headless mode :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
* CLI Tool (z3ed) [8/12]
|
||||
** DONE [#A] Resource-oriented command structure
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:DOCUMENTATION: E6-z3ed-cli-design.md
|
||||
:END:
|
||||
|
||||
** DONE [#A] FTXUI TUI component system
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Code quality refactoring
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Interactive palette editor (TUI)
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Interactive hex viewer (TUI)
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Command palette (TUI)
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#B] ROM validation commands
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#B] Agent framework foundation
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#A] Complete agent execution loop (MCP) :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:DEPENDENCIES: Agent framework foundation
|
||||
:END:
|
||||
|
||||
** TODO [#B] Agent GUI control panel :feature:ui:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#B] Granular data manipulation commands :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#C] SpriteBuilder CLI :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:STATUS: Deprioritized
|
||||
:END:
|
||||
|
||||
* Documentation [3/5]
|
||||
** DONE [#A] Consolidate tile16 editor documentation
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Merge E2E testing documentation
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** DONE [#A] Merge z3ed refactoring documentation
|
||||
CLOSED: [2025-01-31]
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#B] API documentation generation :docs:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#C] User guide for ROM hackers :docs:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
* Research & Planning [0/3]
|
||||
** TODO [#B] Advanced canvas rendering optimizations :performance:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:REFERENCES: gfx_optimization_recommendations.md
|
||||
:END:
|
||||
|
||||
** TODO [#B] Oracle of Secrets dungeon support :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
** TODO [#C] Plugin system architecture :feature:
|
||||
:PROPERTIES:
|
||||
:CREATED: [2025-01-31]
|
||||
:END:
|
||||
|
||||
* Org-Mode Productivity Tips
|
||||
** Quick Capture Templates
|
||||
Add to your Emacs config:
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-capture-templates
|
||||
'(("t" "TODO" entry (file+headline "~/Code/yaze/docs/yaze.org" "Active Issues")
|
||||
"** TODO [#B] %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n")
|
||||
("b" "Bug" entry (file+headline "~/Code/yaze/docs/yaze.org" "Active Issues")
|
||||
"** TODO [#A] %? :bug:\n:PROPERTIES:\n:CREATED: %U\n:END:\n")
|
||||
("f" "Feature" entry (file+headline "~/Code/yaze/docs/yaze.org" "Active Issues")
|
||||
"** TODO [#B] %? :feature:\n:PROPERTIES:\n:CREATED: %U\n:END:\n")))
|
||||
#+end_src
|
||||
|
||||
** Useful Commands
|
||||
- =C-c C-t= : Cycle TODO state
|
||||
- =C-c C-q= : Add tags
|
||||
- =C-c ,= : Set priority
|
||||
- =C-c C-x C-s= : Archive DONE items
|
||||
- =C-c C-v= : View agenda
|
||||
- =C-c a t= : Global TODO list
|
||||
- =C-c a m= : Match tags/properties
|
||||
|
||||
** Agenda Configuration
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-agenda-files '("~/Code/yaze/docs/yaze.org"))
|
||||
(setq org-agenda-custom-commands
|
||||
'(("y" "YAZE Active Tasks"
|
||||
((tags-todo "bug"
|
||||
((org-agenda-overriding-header "Active Bugs")))
|
||||
(tags-todo "feature"
|
||||
((org-agenda-overriding-header "Features in Development")))
|
||||
(todo "ACTIVE"
|
||||
((org-agenda-overriding-header "Currently Working On")))))))
|
||||
#+end_src
|
||||
|
||||
** Workflow Tips
|
||||
1. Use =C-c C-c= on headlines to update statistics cookies [/] and [%]
|
||||
2. Create custom views with =org-agenda-custom-commands=
|
||||
3. Use =org-refile= (C-c C-w) to reorganize tasks
|
||||
4. Archive completed tasks regularly
|
||||
5. Use =org-sparse-tree= (C-c /) to filter by TODO state or tags
|
||||
6. Link to documentation: =[[file:E7-tile16-editor-palette-system.md]]=
|
||||
7. Track time with =C-c C-x C-i= (clock in) and =C-c C-x C-o= (clock out)
|
||||
|
||||
Reference in New Issue
Block a user