Commit Graph

11 Commits

Author SHA1 Message Date
Matthew Stanley 101fd2e1a9 feat: 46 GTE library seeds + DuckStation oracle SW readback + debug tooling
Add 46 seeds for the libgte helper functions at BFC34E00-BFC36D00.
These wrap individual GTE operations (RTPS, RTPT, NCLIP, MVMVA, etc.)
and were never discovered because they're called via JALR function
pointers, not direct JAL. Coverage: 52→114 gte_execute, 166→429
LWC2/SWC2. Emitted functions: 1178→1214. Seeds: 960→1006.

Fix DuckStation oracle VRAM readback by setting
UseSoftwareRendererForReadbacks=true in settings.ini.

Add debug/analysis tools: GTE audit, VRAM stitching, rapid polling,
GPU info, divergence comparison, jump table dumps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 00:29:06 -07:00
Matthew Stanley 3b9ac6b84b perf: read_ram 16KB cap + 4KB divergence chunks + send_fmt 64KB buffer
- debug_server.c: increase read_ram from 256 bytes to 16KB max,
  use dynamic allocation for hex buffer; increase send_fmt buffer
  from 16KB to 64KB to hold large responses
- find_divergence.py: use 4KB chunks (was 256), add total diff byte
  count, remove run-length cap, remove hardcoded VSync exclusion,
  add data length verification on reads

512KB paused scan: 128s (was 1393s with 256-byte chunks)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 17:50:47 -07:00
Matthew Stanley 53ca0d54d1 fix: relocate $ra to RAM addresses for shell/kernel code regions
The recompiler set $ra to ROM addresses (0xBFC18xxx) for jal/jalr in
shell-region functions. On real hardware, $ra uses RAM addresses
(0x80030xxx) because the shell runs from RAM. This caused handler/
callback tables to contain ROM addresses instead of RAM, breaking
pointer comparisons that gate the BIOS shell's 3D rendering pipeline.

Also adds:
- tools/find_divergence.py: golden-oracle RAM comparison tool that
  diffs recomp (port 4370) vs DuckStation (port 4371) byte-by-byte
  using persistent TCP connections
- gte_exec counter in oracle VSync output for interpreter validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 17:04:26 -07:00
Matthew Stanley b3833aa253 fix: exception handler chain walk + shell reaches Memory Card/CD-ROM screen
Root cause: the recompiler split the kernel exception handler at the
jalr $s1 (chain handler dispatch) into func_00000C80 and func_00000E10.
The chain-walker continuation at BFC10910 never executed — only the
first handler in chain 0 was ever called.  I_STAT bits 0 (VBlank) and
2 (CDROM) were never acknowledged, causing an interrupt livelock.

Fixes:
- Emitter jalr/jal safety-net: when the return address falls outside
  the current function, emit a continuation psx_dispatch call after
  the jalr dispatch.  This bridges split functions at indirect calls.
- Merged exception handler: removed BFC10910 seed so the chain walker
  continuation is part of func_00000C80 (C fall-through after jalr).
- 0xBFC10910 dispatch special case in psx_unknown_dispatch: chain
  handlers return to $ra=BFC10910 via jr $ra (compiled as `return;`);
  the dispatch loop picks up the $ra value — handled as a no-op since
  the continuation already ran via C fall-through.
- Post-exception adaptive cooldown: if the handler didn't clear any
  I_STAT bits, give main code 500 blocks before re-firing.  Prevents
  VBlank livelock when no handler claims the interrupt.
- SIO byte write handler in mmio_write8 (was missing, caused crash).
- GPU→RAM DMA ch2 stub: fills target with zeros, completes transfer.
- Dispatch trace (debug_server_trace_dispatch) added to emitter
  template so it survives regeneration.

Result: shell device detection completes, StartPad dispatched, chain
E014 populated, GPU actively rendering the Memory Card / CD-ROM
screen.  VSync counter still 0 (handler installed but not yet
incrementing — next investigation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:13:51 -07:00
Matthew Stanley 8e81afd111 fix: conditional branch fall-through + Tier 1 reverse debugger
Recompiler bug fix: full_function_emitter treated conditional branch
delay slots at function boundaries as fully resolved control flow,
suppressing the fall-through tail call for the "not taken" path.
This broke 15 cross-function fall-throughs across the BIOS, most
critically the find-free-EvCB-slot function (BFC11800) whose search
loop was silently skipped — causing OpenEvent to return garbage handles
(0xF100E1E8) instead of valid slot indices (0xF1000000..0xF1000005).

Fix: only suppress fall-through for unconditional flow (j/jr/jal/jalr/rfe).
Conditional branches (beq/bne/blez/bgtz/bltz/bgez) now always emit
`cpu->pc = next_addr; return;` for the not-taken path.

Also included:
- Tier 1 reverse debugger: 1M-entry write trace ring (32 MB),
  64K-entry MMIO trace ring (2 MB), 8-slot multi-range address
  filtering, function attribution via g_debug_current_func_addr.
  6 new TCP commands: wtrace_add/del/ranges/stats, mmio_dump/clear.
- 3 new dispatch seeds for functions exposed by fall-through fix
  (SIO epilogue, exception chain walker, GTE init).
- Diagnostic tools: analyze_sio_trace, decode_insn, decode_jt, mmio_tally.
- Pattern 5 trampoline fix + PLAN.md Phase 4 status update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:23:09 -07:00
Matthew Stanley 957256134b duckstation oracle: fix headless boot — rcc target + first-run wizard
Root-caused the post-rebuild boot failure:

1. MSBuild's duckstation-qt target does NOT include the Qt resource compile.
   The separate duckstation-qt-rcc target produces
   build/bin/resources/duckstation-qt.rcc (~740 KB). Without it the binary
   shows a "duckstation-qt.rcc could not be loaded" dialog and exits.
   build.sh now builds both targets in one MSBuild invocation.

2. A first-time GUI launch is also required so DuckStation's setup wizard
   can write out a working settings.ini. Our auto-generated settings.ini
   (with SetupWizardIncomplete=false etc.) is insufficient — the wizard
   records other fields (probably Graphics API init, controller profile,
   achievement opt-out) that are needed for headless emulation to start.
   Thanks to mstanley for running the wizard interactively.

After those two fixes, verified end-to-end:
- `python3 tools/duckstation/launch.py -bios -nogui -fastboot` runs
- port 4371 binds
- read_ram 0x00079D9C returns an incrementing VSync counter
- read_ram 0x000DFEE0 returns 0x8005A5BC (VBlank chain handler — patch live)
- pc_break_list responds (proves my compile-but-untested pc_break family
  from the uncommitted additions is now tested via embedded binary)

tools/duckstation/launch.py — detached-spawn wrapper (uses
CREATE_NEW_PROCESS_GROUP only, no DETACHED_PROCESS so the GUI stays
visible on first-run). Moved from tools/launch_ds_detached.py to live
alongside setup.sh / build.sh / README.md.

README updated to replace the "known issue" section with the correct
recovery steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:49:45 -07:00
Matthew Stanley 6a4f1d027b duckstation oracle: document post-rebuild boot-failure investigation
Follow-up to the restructure commit. Attempted to verify end-to-end
`-bios -nogui -fastboot` behaviour on the rebuilt binary; it does not start
BIOS emulation regardless of launch-flag permutations. Process runs stably at
~34 MB but `PSXRecompDebug::Initialize(4371)` is never reached because the
call sits inside `System::Initialize` which returns `false` silently.

Verified (by this session's investigation) as NOT the cause:
- patch present in binary (`tools/check_ds_patch.py` finds PSXRecompDebug,
  pc_break, read_ram, psxrecomp_debug_server literals)
- BIOS file md5 matches DuckStation's known SCPH-1001 hash
- prebuilt deps extracted + `_IMPORT_PREFIX` paths rewritten across 27 files
- settings.ini has all the obvious gates flipped (BIOS path, setup-wizard
  bypass, auto-update disabled, explicit region, resources + translations
  restored next to exe)
- portable.txt in place, submodule pinned to upstream ffb33c281

Still unknown: whether it's a GPU/Vulkan/D3D init path failing under -nogui
on this machine's environment, or a baked path in the prebuilt deps we didn't
rewrite. Documented in tools/duckstation/README.md for pickup next session.

Also adds tools/check_ds_patch.py — verifies the patch is compiled into the
binary via string-search of marker symbols.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 02:03:14 -07:00
Matthew Stanley 685687f36f Restructure duckstation as submodule + patch; salvage TCP docs + debug_client
Recovery + restructure after rm -rf destroyed duckstation/build/ earlier this
session. Adopts the nesrecomp pattern (see F:/Projects/nesrecomp): pristine
upstream as submodule, our changes as a patch file that gets auto-applied by a
setup script.

Submodule:
- duckstation/ now pins stenzek/duckstation@ffb33c281 (release-20260328 era)
- .gitmodules records the pin; .gitignore stops ignoring the submodule path but
  still ignores build/ and dep/prebuilt/ (the submodule's own .gitignore also
  handles these)

Patch + setup:
- tools/duckstation/psxrecomp_oracle.patch (1187 lines, 7 files) captures our
  TCP debug-server on port 4371 plus the unfinished pc_break family of commands
  (with the VirtualMemoryAddress compile fix)
- tools/duckstation/setup.sh clones submodule, fetches + verifies + extracts
  prebuilt Windows deps, rewrites stale absolute paths baked into the prebuilt
  CMake metadata, applies the oracle patch. Idempotent.
- tools/duckstation/build.sh configures CMake (VS 17 2022) + runs MSBuild, then
  provisions bin/ with portable marker, BIOS copy, and settings.ini post-fixes
- tools/duckstation/README.md documents the layout, the regeneration flow, and
  the known headless-boot configuration issue

TCP documentation (salvaged from v3):
- TCP_COMMANDS.md: full command table with native (4370) / duckstation (4371)
  / both columns, reflecting actual v4 implementation including wtrace_* and
  pc_break/pc_hit_last families
- tools/debug_client.py: unified CLI from v3 (ports 4370/4371 match v4)
- DEBUG.md: merged v3's END-TO-END COMPLETION RULE, Stale Hypothesis Rule,
  TOOLING RULE, and TOOL LIFECYCLE RULE sections into v4's existing loop

Phase 4 diagnostic artefacts from this session:
- generated/relocation_proofs/vblank_registrar/ — corrected VBlank-stall
  diagnosis. Prior handoff claimed the shell VBlank handler was never installed;
  live TCP inspection of both runtime and DuckStation oracle proved handler IS
  installed at 0x800DFEE0. Real gap is one level up in the kernel IRQ chain at
  *(0xA000E014).
- tools/duck_query*.py, duck_search.py, disasm_ram.py, scan_*.py — focused
  probes used to establish the above; reusable against either server

CLAUDE.md §2 and new §15 document the submodule-based oracle setup and the
"never edit submodule, regenerate patch instead" rule.

Known issues (tracked in tools/duckstation/README.md):
- Fresh settings.ini doesn't complete BIOS boot under -nogui; one-time launch
  with GUI is required to warm up the settings before headless mode works

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 01:34:27 -07:00
Matthew Stanley e8094affb4 Phase 4: seed VBlank chain handler + add RAM write-trace
EvCB write-trace instrumentation confirmed OpenEvent is never called for
any event class during boot, not only VBlank. Static ROM analysis found
that the only instruction in SCPH1001.BIN that stores to the VSync
counter at 0x80079D9C lives in an unrecompiled 15-instruction function
at 0xBFC425BC (identified as the VBlank chain handler: increments the
counter, optionally calls the user callback at *0x800DFF40). Added as a
seed so the function is now compiled and present in the dispatch table.

Recompile alone does not unblock the boot — no JAL, literal, or
lui 0xBFC4 exists in ROM that would load this address, so the kernel
registrar that enqueues it on the root-counter chain is itself missing
and needs to be found (next session, via DuckStation cross-reference).

Changes:
 - recompiler/seeds: +0xBFC425BC (vblank_chain_handler), 825 seeds total
 - debug_server: wtrace_range / wtrace_dump / wtrace_clear TCP commands,
   1024-entry ring buffer capturing (addr, old, new, ra, width); default
   range 0x0000E000..0x0000E1F0 active from process start so early-boot
   writes are captured before a TCP client can attach
 - memory.c: inline bounds check + hook call in psx_write_{word,half,byte}
 - tools: print_wtrace.py, sample_ra.py, sample_ra_long.py,
   scan_f2_sites.py — investigation helpers

Runtime behaviour unchanged (still hangs in VSync spin at Sony logo) —
the seed is a prerequisite; the upstream registrar is the next bug.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:19:32 -07:00
Matthew Stanley fba58d76b3 Phase 4 interrupt fixes: RFE, GPR save/restore, DMA IRQ edge-trigger
Three interrupt delivery bugs fixed that were blocking shell progression:

1. RFE after handler exit: The exception handler's normal exit path
   (jmpbuf longjmp at 0xBFC10944) does not execute RFE. In our model
   the handler returns to psx_check_interrupts, not to the WaitEvent
   caller. Without RFE, IEc stays 0 forever after the first interrupt.
   Fix: apply RFE in psx_check_interrupts if IEc is still 0 after the
   handler returns.

2. GPR save/restore around handler dispatch: The handler's jmpbuf exit
   restores callee-saved registers from the kernel jmpbuf (intended for
   a non-local jump to WaitEvent's caller). In our model this corrupts
   the interrupted code's registers. Fix: save all GPRs before handler
   dispatch and restore them after, matching real hardware where the
   interrupted code's registers are preserved via TCB save/restore.

3. VBlank tick gated on !in_exception: The handler runs thousands of
   block leaders, each calling psx_check_interrupts. Without gating,
   dispatch_count advances inside the handler, firing new VBlanks that
   stack up and starve the main code. Fix: only increment dispatch_count
   and fire VBlank/timer ticks when not in exception handling.

4. DMA IRQ edge-triggered: update_master_irq() was level-triggered,
   re-asserting i_stat bit 3 on every call while master_flag=1. Combined
   with no BIOS callback to acknowledge DMA IRQs, this caused an
   interrupt storm. Fix: edge-detect master_flag 0->1 transition, and
   suppress DMA completion IRQs entirely since transfers are synchronous.

Also adds GPU debug diagnostics: GP0 command counters (nop/fill/draw/
env/copy), draw area/offset in gpu_state, vram_peek debug command.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:29:03 -07:00
Matthew Stanley 7c40d33db3 Phase 1 complete: recompiler proven correct on SCPH1001.BIN
Phase 1a (boot slice): 30 instructions at 0xBFC00000, compiles clean.
Phase 1b (instruction coverage): 54 opcodes implemented in strict_translator,
  44,629 walker-reachable instructions covered, 0 missing.
Phase 1c (function discovery): 76 functions from 2 seeds, 2,988 instructions,
  199 call edges, 27 indirect jumps. All validated through StrictTranslator.
Phase 1d (indirect control flow): 27 sites classified — 21 A0/B0/C0 dispatch
  stubs, 4 computed calls, 2 computed tail calls. Ghidra cross-referenced
  with 0 disagreements.
Phase 1e (relocation): ROM-to-RAM copy at FUN_bfc00420 proven with both
  Ghidra (static) and DuckStation (runtime). 69 address aliases, 68/69
  function entries match byte-for-byte in RAM. Normalization rule documented.

Includes PRINCIPLES.md and DEBUG.md adapted from recomp-template.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:26:04 -07:00