Commit Graph

13 Commits

Author SHA1 Message Date
Matthew Stanley 5a63372a51 fix: GPU shaded line interpolation + polyline support + stub audit
S1: gp0_exec_shaded_line() now interpolates between C0 and C1 colors
along the line instead of using flat C0 color for the entire line.
Added sw_draw_shaded_line() to the SW renderer with per-pixel RGB
interpolation using Bresenham stepping.

S2: Polyline commands (GP0 0x48-0x4F mono, 0x58-0x5F shaded) no longer
crash with exit(1). Implemented as GP0_POLYLINE_MONO/SHADED states
that accumulate vertices until the terminator word (bit pattern with
high bits set), drawing line segments between consecutive pairs.

Added STUBS_TO_FIX.md tracking all remaining stubs found in the
comprehensive audit (MDEC, SPU, DMA channels, polylines, shaded lines).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 14:38:38 -07:00
Matthew Stanley 4054dc1c08 fix: DMA ch2 GPU→RAM now reads GPUREAD instead of writing zeros
The VRAM-to-RAM DMA path (channel 2, direction 0) was stubbed to fill
the destination buffer with zeros. This broke all offscreen VRAM
readback — the BIOS shell draws spiral/splash textures to an offscreen
VRAM region, then DMA-transfers the rendered pixels back to RAM for
later use as texture atlas entries. With the stub, all texture buffers
stayed zero, producing invisible button backgrounds.

Fix: call gpu_read_gpuread() for each DMA word instead of writing 0.

Also includes: CDROM GetID two-phase response fix, GPU raw texture
mode fix (opcode bit 0 instead of color==0x808080), debug server
A0/C0 upload history commands with CPU context capture, and extended
write trace ranges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 14:30:57 -07:00
Matthew Stanley bd6dfa12da fix: memcard absent detection + raw texture bug + GTE exec counter + quad capture
- memcard.c: stop auto-creating blank cards when .mcd files don't
  exist; missing file now correctly means "no card in slot"
- gpu.c: fix setup_textured_draw raw texture flag (was ? 0 : 0,
  now ? 1 : 0 for identity-modulation bypass)
- gpu.c: add shaded quad vertex capture mechanism for Phase 4.5
  debug (capture_quads / get_quads debug commands)
- gte.cpp: add gte_exec_count to track total GTE compute commands
  executed (exposed via gte_state debug command)
- debug_server.c: wire up capture_quads, get_quads commands and
  gte_exec field in gte_state response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 15:33:19 -07:00
Matthew Stanley 2767018b46 fix: SIO pad detection + input override + CARD driver seeds
Root cause: BIOS pad handler polls I_STAT bit 7 waiting for SIO ACK
IRQ. sio_tick() was only called from psx_check_interrupts and SIO
register accesses, but I_STAT reads go through memory.c which never
called sio_tick(). The IRQ countdown was stuck during the polling
loop, causing the BIOS to time out and report "no pad found" (0xFF).

Additionally, sio_tick() in psx_check_interrupts fired the IRQ too
early — during BIOS delay loops BEFORE I_STAT bit 7 was cleared —
so the BIOS cleared it and never saw it re-appear.

Fixes:
- memory.c: Call sio_tick() on I_STAT reads (32/16/8-bit paths)
- interrupts.c: Remove sio_tick() from psx_check_interrupts
- debug_server.c: Don't clear input override on TCP disconnect
  (was wiping override before next VBlank could read it)
- debug_server.c: sio_state reports real pad_buttons via getter
- sio.c/sio.h: Add sio_get_pad_buttons() for debug server
- seeds: Add 20 new seeds for shell input and CARD driver functions

Result: Pad buffer at 0x80083E30 shows status=0x00 (pad found),
type=0x41 (digital), buttons reflect keyboard/override state.
Only 5 COP2/GTE dispatch misses remain (Phase 5).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 11:22:39 -07:00
Matthew Stanley 1a0213ec5e fix: RestoreState escape mechanism — VSync counter now increments
The kernel's inline RestoreState (at BFC1094C inside the exception
handler func_00000C80) restored all registers and did `jr $ra`, but
the generated `return;` went back to psx_check_interrupts instead of
dispatching to the restored $ra (0xBFC421D8 — the VSync callback
loop). Then psx_check_interrupts overwrote all GPRs from the
pre-exception save, wiping RestoreState's changes. VSync counter
was permanently stuck at 0.

Fix has three parts:

1. runtime/src/interrupts.c: Add psx_restore_state_escape() which
   longjmps with code 2 (vs code 1 for ReturnFromException).
   psx_check_interrupts now loops: code 2 re-dispatches to cpu->pc
   (still in exception context), code 1 exits, code 0 is normal.

2. recompiler full_function_emitter: Broaden longjmp-return detection
   from first-instruction-only to scanning ALL instructions for the
   exact encoding 0x8C9F0000 (lw $ra, 0($a0)). This catches both
   standalone RestoreState (func_1FC0227C) and the kernel inline
   copy (func_00000C80 at BFC10964).

3. Generated jr $ra terminators in matched functions now emit:
   cpu->pc = cpu->gpr[31]; psx_restore_state_escape(); return;

Result: VSync counter increments steadily (~256/vblank), zero crashes,
shell renders Memory Card / CD Player screen with no ADEL faults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:23:25 -07:00
Matthew Stanley e1dae7fcbd feat: native PSX oracle — R3000A interpreter + dual CMake targets
Adds psx-oracle.exe alongside psx-runtime.exe, following the Genesis
recomp hybrid pattern (two targets from shared sources):

- psx-runtime: recompiled BIOS (stub_interpreter.c), port 4370
- psx-oracle:  R3000A interpreter (psx_interpreter.c), port 4371

The interpreter implements the full MIPS I instruction set:
  ALU, shifts, mult/div, branches, jumps, loads/stores (incl LWL/LWR),
  COP0 (MFC0/MTC0/RFE), COP2/GTE (delegated to gte.cpp), SYSCALL/BREAK.

Includes 1M-entry per-instruction trace ring and 64 PC breakpoints —
the tooling gap that blocked VSync counter diagnosis (DuckStation's
TCP debug server only had 2-3 breakpoint slots, no PC breakpoints).

Also extends memory control register range to 0x1F80103C (was 0x1F801020)
for the interpreter's early BIOS init path.

Verified: oracle boots from BFC00000 and correctly executes the first
20+ BIOS instructions. Hits unknown SPECIAL func at PC=0x68 later in
boot — instruction coverage issue to debug next session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:34:28 -07:00
Matthew Stanley 70e7e7aea2 fix: enable DMA completion IRQ + DICR byte access + 5 VBlank seeds
Root cause of VSync counter stuck at 0: DMA completion IRQs were
deliberately suppressed in dma.c, preventing the shell's chain-E004
handler (func_1FC40628) from ever seeing I_STAT bit 3.  The handler
gates all VBlank callback processing on DMA interrupt presence.

Changes:
- dma.c: directly assert i_stat bit 3 on DMA ch2/ch6 completion
  (bypass DICR edge detection which gets stuck because the shell
  handler only acknowledges ch3 in DICR, not ch2/ch6)
- dma.c: expose dma_get_dicr()/dma_get_dpcr() for debug server
- memory.c: add DMA register range (0x1F801080-0x1F8010FF) to
  mmio_read8/write8/read16/write16 — fixes crash on DICR byte read
- debug_server.c: add DPCR/DICR to irq_state command
- seeds: add 0xBFC42BA0 (shell_vblank_dispatch), 0xBFC38AF0 (shell
  hot function, 178 misses), 0xBFC137D0 (kernel RAM), 0xBFC42BE0
  (shell_vblank_process), 0xBFC4226C (mid-function callback loop)
- regen: 1051 dispatch entries (was 1046)

Verified: chain-E004 handler + verify now dispatch.  VSync counter
still 0 because the VBlank callback loop inside func_1FC42160 is
gated by an init-done flag and the mid-function entry mechanism is
not yet resolved.  New EvCB crash at BFC12A84 exposed by DMA IRQ —
next session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 20:06:15 -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 a16e3a4408 Add CDROM controller and SPU register sink, wire into MMIO
CDROM (adapted from v3, 487 lines):
- Removed event_deliver() — HLE disease. IRQ delivery is now purely
  via i_stat bit 2; the recompiled BIOS exception handler processes
  CDROM events natively through its own EvCB table.
- Removed CPUState* from all function signatures — hardware registers
  don't need CPU state.
- Removed hard-coded RAM writes (0x800DEE54 etc.) — BIOS kernel state
  manipulation disguised as hardware sim.
- Removed all fprintf.
- Kept: register banking, command processing, sector reads, DMA interface.

SPU (new, 89 lines, register map from v2 reference):
- Accepts all SPU register reads/writes at 0x1F801C00-0x1F801FFF
- 512KB SPU RAM with transfer address tracking and FIFO writes
- DMA4 write interface for bulk transfers
- SPUSTAT returns 0 (ready, not busy)
- No audio synthesis — placeholder for SDL_audio integration later

Memory.c: replaced inline spu_regs[] array and stub CDROM handlers
with calls to the proper cdrom.c and spu.c modules.

Validated: BIOS boots identically (16,859 GP0 writes, 1,149 draws).
No regression from previous commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 21:20:12 -07:00
Matthew Stanley adaea3d8bf Salvage clean hardware sim from v2/v3: GTE, SW renderer, ISO reader, CRC32
Audited psxrecomp-v3 and psxrecomp-v2 hardware simulation files.
Carried forward only pure hardware simulation with zero HLE/BIOS
contamination. Each file was read in full and checked for:
- event_deliver / bios_ / interpreter_ references (none found)
- printf/fprintf (removed from GTE, none in others)
- stubs (return 0, TODO, FIXME) (none found)
- v3-specific state dependencies (removed psx_runtime.h, automation.h)

Added files:
- gte.cpp/h: COP2 coprocessor (876 lines, all GTE commands)
- gpu_sw_renderer.c/h: Software rasterizer with texture support (723 lines)
- crc32.c/h: CRC32 utility (27 lines)
- iso_reader.cpp/h + iso_reader_c.cpp: ISO 9660 reader (490 lines)

Modified:
- cpu_state.h: Added gte_data[32] and gte_ctrl[32] arrays
- CMakeLists.txt: Added new source files

Explicitly rejected (per CLAUDE.md rule 7):
- bios.c, events.c, threads.c, interpreter.c (HLE/kernel state)
- bios_trace.c, func_logger.c, main_runner.cpp (interpreter era)
- cdrom.c (has event_deliver + hard-coded RAM writes, needs surgery)
- spu.cpp from v2 (WinMM-only, needs SDL port)

Build verified: clean compile and link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:57:41 -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 a1165d51b7 Phase 2 complete: BIOS boots to GPUSTAT read
Full recompilation pipeline working:
- Recompiler emits 799 functions (46k instructions) from SCPH1001.BIN
- Tail-call trampoline in dispatch loop prevents stack overflow
- Timer registers (3 counters, mode, target) implemented
- Interrupt check at labels + dispatch boundaries with vblank scheduling
- SYSCALL 1/2/3 (Enter/ExitCriticalSection, ReturnFromException) handled
- EPC correctly set from SYSCALL instruction address

BIOS boot reaches first GPU hardware access:
  UNIMPLEMENTED MMIO READ32 (GPUSTAT) @ 0x1F801814

Phase 3 (Sony logo) starts here: GPU, DMA, display pipeline.

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