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>
Two CDROM issues preventing BIOS from progressing past Sony logo:
1. cdrom_tick() was never called — delayed CDROM responses (GetStatus,
Init, Pause) never completed because pending.delay was never
decremented. Wired into psx_check_interrupts vblank interval.
2. exec_command() called set_irq() but not fire_cdrom_irq() for
immediate responses (GetStat, SetLoc, Demute, etc.). The CDROM
IRQ flag was set internally but i_stat bit 2 was never raised,
so the BIOS exception handler never delivered CDROM events.
BIOS still stalls at same draw count — next bug in chain is EvCB
event delivery (TestEvent returns 0 because VBlank events aren't
being written to the event control block table).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The BIOS shell code (ROM 0xBFC18000+) runs from RAM at 0x80030000+.
Jump tables in shell functions store RAM-space target addresses, but
at recompile time only the ROM data is available.
Three fixes:
1. function_discovery.cpp: detect jump table pattern (SLTIU/SLL/LUI/
ADDU/LW/JR) during function scanning, translate RAM table addresses
to ROM, push case targets onto BFS work queue so case body code is
included in the function's instruction set.
2. full_function_emitter.cpp: detect same pattern at jr emission time,
read table from ROM with RAM-to-ROM mapping, emit C switch/goto
instead of cpu->pc=reg;return for resolved tables.
3. code_generator.cpp: add ram_to_rom() helper for the existing jump
table detection (used by a different code path).
Result: 13 jump tables resolved across the BIOS, +6 functions discovered,
+2825 instructions included. Dispatch miss at 0x80058564 (memcard SIO
handler) eliminated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Integrated the salvaged gpu_sw_renderer.c into gpu.c's command pipeline:
- Textured triangles/quads now do real CLUT/texpage lookup instead of flat color
- Gouraud-shaded triangles/quads interpolate vertex colors via SW renderer
- Shaded+textured primitives extract texcoords and pass to SW renderer
- Textured rectangles (variable, 8x8, 16x16) use sw_draw_textured_rect
- VRAM→VRAM copy (GP0 0x80-0x9F) implemented via sw_copy_rect
- All GPU environment commands sync state to SW renderer (draw area,
draw offset, mask bits, texture window)
- Fixed generated/cpu_state.h to include gte_data/gte_ctrl arrays
(struct layout mismatch caused segfault on first run)
Validated: BIOS boots, GPU processes 16,859+ GP0 commands including
1,149 draw calls and 66 copy transfers. VRAM contains non-zero pixel
data across multiple regions confirming active rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Comprehensive handoff for the next session to begin Phase 2 (minimal
runtime). Documents all Phase 1 results, build environment, DuckStation
TCP debug server, Ghidra MCP, and the concrete Phase 2 work items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>