Files
yaze/docs/internal/debug/emulator-regressions-2025-11.md

355 lines
11 KiB
Markdown

# Emulator Regressions - November 2025
## Status: UNRESOLVED
Two regressions have been identified in the SNES emulator that affect:
1. Input handling (A button not working on file naming screen)
2. PPU rendering (title screen BG layer not showing)
**Note**: Keybindings system is currently being modified by another agent. Changes may interact.
---
## Issue 1: Input Button Mapping Bug
### Symptoms
- A button does not work on the ALTTP file naming screen
- D-pad works correctly
- A button works on title screen (different code path?)
### Root Cause Analysis
**Bug Location**: `src/app/emu/snes.cc:763`
```cpp
void Snes::SetButtonState(int player, int button, bool pressed) {
// BUG: This logic is inverted!
Input* input = (player == 1) ? &input1 : &input2;
// ...
}
```
When calling `SetButtonState(0, button, true)` (player 0 = player 1 in SNES terms), it incorrectly selects `input2` instead of `input1`.
**Introduced in**: Commit `9ffb7803f5` (Oct 11, 2025)
- "refactor(emulator): enhance input handling and audio resampling features"
### Attempted Fix
In this session, we updated the button constants in `save_state_manager.h` from bitmasks to bit indices:
```cpp
// Before (incorrect for SetButtonState API):
constexpr uint16_t kA = 0x0080; // Bitmask
// After (correct bit index):
constexpr int kA = 8; // Bit index
```
However, this fix alone doesn't resolve the issue because `SetButtonState` itself has the player mapping inverted.
### Proposed Fix
Change line 763 in `snes.cc`:
```cpp
// Current (wrong):
Input* input = (player == 1) ? &input1 : &input2;
// Should be:
Input* input = (player == 0) ? &input1 : &input2;
```
Or alternatively, to match common conventions (player 1 = first player):
```cpp
Input* input = (player <= 1) ? &input1 : &input2;
```
### Additional Notes
The title screen may work because it uses a different input reading path or auto-joypad read timing that happens to work despite the bug.
---
## Issue 2: PPU Title Screen BG Layer Not Rendering
### Symptoms
- Title screen background layer(s) not showing
- Timing unclear - may have been introduced in recent commits
### Potential Root Cause
**Commit**: `e37497e9ef` (Nov 23, 2025)
- "feat(emu): add PPU JIT catch-up for mid-scanline raster effects"
This commit refactored PPU rendering from a simple `RunLine()` call to a progressive JIT system:
```cpp
// Old approach:
ppu_.RunLine(line); // Render entire line at once
// New approach:
ppu_.StartLine(line); // Setup for line
ppu_.CatchUp(512); // Render first half
ppu_.CatchUp(1104); // Render second half
```
### Key Changes to Investigate
1. **StartLine() timing**: Now called at H=0 instead of H=512
- `StartLine()` does sprite evaluation and mode 7 setup
- May need to be called earlier or with different conditions
2. **CatchUp() vs RunLine()**: The new progressive rendering may have edge cases
- `CatchUp(512)` renders pixels 0-127
- `CatchUp(1104)` should render pixels 128-255
- But 1104/4 = 276, so it tries to render up to 256 (clamped)
3. **WriteBBus PPU catch-up**: Added mid-scanline PPU register write handling
- May interfere with normal rendering sequence
### Files Changed in PPU Refactor
- `src/app/emu/video/ppu.cc`: Added `StartLine()`, `CatchUp()`, `last_rendered_x_`
- `src/app/emu/video/ppu.h`: Added new method declarations
- `src/app/emu/snes.cc`: Changed `RunLine()` calls to `StartLine()`/`CatchUp()`
### Key Timing Difference
**Before PPU JIT (commit e37497e9ef~1)**:
```cpp
case 512: {
if (!in_vblank_ && memory_.v_pos() > 0)
ppu_.RunLine(memory_.v_pos()); // Everything at H=512
}
```
**After PPU JIT**:
```cpp
case 16: {
ppu_.StartLine(memory_.v_pos()); // Sprite eval at H=16
}
case 512: {
ppu_.CatchUp(512); // Pixels 0-127 at H=512
}
case 1104: {
ppu_.CatchUp(1104); // Pixels 128-255 at H=1104
}
```
The sprite evaluation (`EvaluateSprites`) now happens at H=16 instead of H=512. This timing change could affect games that modify OAM or PPU registers via HDMA between H=16 and H=512.
### Quick Test: Revert to Old PPU Timing
To test if the PPU JIT is causing the issue, temporarily revert to `RunLine()`:
In `src/app/emu/snes.cc`, change the case 16 and 512 blocks:
```cpp
case 16: {
next_horiz_event = 512;
if (memory_.v_pos() == 0)
memory_.init_hdma_request();
// Remove StartLine call
} break;
case 512: {
next_horiz_event = 1104;
if (!in_vblank_ && memory_.v_pos() > 0)
ppu_.RunLine(memory_.v_pos()); // Back to old method
} break;
case 1104: {
// Remove CatchUp call
if (!in_vblank_)
memory_.run_hdma_request();
// ... rest unchanged
```
### Debugging Steps
1. Add logging to PPU to verify:
- Is `StartLine()` being called for each visible scanline?
- Is `CatchUp()` rendering all 256 pixels?
- Are any BG enable flags being cleared unexpectedly?
2. Test reverting PPU changes:
```bash
git checkout e37497e9ef~1 -- src/app/emu/video/ppu.cc src/app/emu/video/ppu.h src/app/emu/snes.cc
```
3. Compare title screen behavior before and after commit `e37497e9ef`
---
## Git History Reference
### Key Commits (Chronological)
| Date | Commit | Description |
|------|--------|-------------|
| Oct 11, 2025 | `9ffb7803f5` | Input handling refactor - introduced player mapping bug |
| Nov 23, 2025 | `e37497e9ef` | PPU JIT catch-up - potential BG rendering regression |
| Nov 25, 2025 | `9d788fe6b0` | Lazy SNES init - may affect startup timing |
| Nov 26, 2025 | (this session) | SaveStateManager button constant fix |
### Commands to Investigate
```bash
# View input handling changes
git show 9ffb7803f5 -- src/app/emu/snes.cc
# View PPU changes
git show e37497e9ef -- src/app/emu/video/ppu.cc src/app/emu/snes.cc
# Diff current vs before PPU JIT
git diff e37497e9ef~1..HEAD -- src/app/emu/video/ppu.cc
# Test with old PPU code
git stash
git checkout e37497e9ef~1 -- src/app/emu/video/ppu.cc src/app/emu/video/ppu.h
cmake --build build --target yaze
# Test emulator, then restore:
git checkout HEAD -- src/app/emu/video/ppu.cc src/app/emu/video/ppu.h
git stash pop
```
---
## Attempted Fixes (Did Not Resolve)
### Session 2025-11-26
1. **Button constants fix** (`save_state_manager.h`)
- Changed from bitmasks to bit indices
- Status: Applied, did not fix input issue
2. **SetButtonState player mapping** (`snes.cc:763`)
- Changed `player == 1` to `player <= 1`
- Status: Applied, did not fix input issue
3. **PPU JIT revert** (`snes.cc`)
- Reverted StartLine/CatchUp back to RunLine
- Status: Applied, did not fix BG layer issue
## Investigation Session 2025-11-26 (New Findings)
### Input Bug Analysis
**SetButtonState is now correct** (`snes.cc:750`):
```cpp
Input* input = (player <= 1) ? &input1 : &input2;
```
**Debug logging already exists** in HandleInput():
- Logs when A button is active in `current_state_`
- Logs `port_auto_read_[0]` value after auto-joypad read
**CRITICAL SUSPECT: ImGui WantTextInput blocking**
In `src/app/emu/input/sdl3_input_backend.cc:67-73`:
```cpp
if (io.WantTextInput) {
static int text_input_log_count = 0;
if (text_input_log_count++ < 5) {
LOG_DEBUG("InputBackend", "Blocking game input - WantTextInput=true");
}
return ControllerState{}; // <-- ALL input blocked!
}
```
If ANY ImGui text input widget is active, ALL game input is blocked. This could explain:
- Why D-pad works but A doesn't → unlikely, would block both
- Why title screen works but naming screen doesn't → possible if yaze UI has text field active
**Diagnostic**: Check if "Blocking game input - WantTextInput=true" appears in logs when on naming screen.
### PPU Bug Analysis
**CRITICAL FINDING: "Revert" was incomplete**
Current `snes.cc:214` calls `ppu_.RunLine()`:
```cpp
case 512: {
next_horiz_event = 1104;
if (!in_vblank_ && memory_.v_pos() > 0)
ppu_.RunLine(memory_.v_pos()); // Looks like old code
}
```
BUT `RunLine()` in `ppu.cc:174-178` now calls the JIT mechanism:
```cpp
void Ppu::RunLine(int line) {
// Legacy wrapper - renders the whole line at once
StartLine(line); // <-- Uses new JIT setup
CatchUp(2000); // <-- Uses new JIT rendering
}
```
**Original `RunLine()` was a direct loop** (before e37497e9ef):
```cpp
void Ppu::RunLine(int line) {
obj_pixel_buffer_.fill(0);
if (!forced_blank_) EvaluateSprites(line - 1);
if (mode == 7) CalculateMode7Starts(line);
for (int x = 0; x < 256; x++) {
HandlePixel(x, line); // Direct loop, no JIT state
}
}
```
**Key Difference**:
- Old: Uses `line` parameter directly in `HandlePixel(x, line)`
- New: Uses member variable `current_scanline_` set by `StartLine()`
**Potential Bug**: If `current_scanline_` or `last_rendered_x_` have stale/incorrect values, rendering breaks.
**TRUE REVERT Required**: To test if JIT is the cause, must restore the original `ppu.cc` implementation, not just the `snes.cc` call sites.
---
## Investigation Session 2025-11-27 (snes-emulator-expert)
### PPU State Check (current dirty tree)
- `ppu.cc` has already been changed back to the legacy full-line renderer inside `RunLine()` (StartLine/CatchUp still exist but are unused). The earlier suspicion that the wrapper itself was blanking the BG no longer applies.
- `snes.cc` only calls `RunLine()` once per scanline at H=512; there are no remaining PPU catch-up hooks in `WriteBBus`, so the JIT path is effectively dead code right now.
### Runtime Observation (yaze_emu_trace.log)
- Headless run shows the CPU stuck in the SPC handshake loop at `$00:88B6` (`CMP.w APUIO0` / `BNE .wait_for_zero`), with NMIs never enabled in the first 120 frames.
- If the SPC handshake never completes, the game never uploads title-screen VRAM/CGRAM or enables 212C/212D, so the blank BG may be a fallout of stalled boot rather than a renderer defect.
### Next Steps (PPU-focused)
- First, confirm the SPC handshake completes (APUIO0 transitions off zero) so the game can reach module `0x01`; otherwise any PPU checks are moot.
- After the handshake, instrument `RunLine` (e.g., when `line==100`) to log `forced_blank_`, `mode`, and `layer_[i].mainScreenEnabled` to ensure BGs are actually enabled on the title frame.
- If layers are enabled but BG still missing, capture VRAM around the title tilemap upload to ensure DMA is populating the expected addresses.
---
## Updated Next Steps
### Priority 1: Input Bug
- [ ] Check logs for "Blocking game input - WantTextInput=true" message
- [ ] Verify if any ImGui InputText widget is active during emulation
- [ ] Test with `WantTextInput` check temporarily removed
- [ ] Trace: SDL key state → Poll() → SetButtonState() → HandleInput()
### Priority 2: PPU Bug
- [ ] **TRUE revert test**: Restore original `ppu.cc` from `e37497e9ef~1`
```bash
git show e37497e9ef~1:src/app/emu/video/ppu.cc > /tmp/old_ppu.cc
# Compare and apply the old RunLine() implementation
```
- [ ] Add logging to verify `current_scanline_` and `last_rendered_x_` values
- [ ] Check layer enable flags (`layer_[i].mainScreenEnabled`) during title screen
- [ ] Verify VRAM contains tile data
### Priority 3: General
- [ ] **Git bisect** to find exact commit where emulator last worked
- [ ] Coordinate with keybindings agent work
## Potentially Relevant Commits
| Commit | Date | Description |
|--------|------|-------------|
| `0579fc2c65` | Earlier | Implement input management system with SDL2 |
| `9ffb7803f5` | Oct 11 | Enhance input handling (introduced SetButtonState) |
| `2f0006ac0b` | Later | SDL compatibility layer |
| `a5dc884612` | Later | SDL3 backend infrastructure |
| `e37497e9ef` | Nov 23 | PPU JIT catch-up (reverted) |