Commit Graph

4 Commits

Author SHA1 Message Date
Matthew Stanley 2d6a75d9a2 tooling: fatal halt-and-serve + always-on GP1 display ring
- psx_fatal_halt: deliberate fatal sites (unhandled MMIO access,
  trap_crash) now write the structured crash report AND a full
  freeze-style ring dump (wedge kind "fatal"), then keep pumping the
  TCP debug server forever in debug-tools builds so every ring stays
  queryable against the exact crash state (ring-buffer-first: crashes
  are interrogated, not snapshotted). Release builds exit(1) as before.
  Re-entry guarded; heartbeat JSON gains a "fatal" field.
- gp1_dump: dedicated always-on GP1 (0x1F801814 display control) ring,
  512K entries (~15 min of gameplay) with frame-window server-side
  filtering. The general MMIO ring rolls over in under a minute of SPU/
  DMA traffic, which kept evicting boot-window display history before a
  probe could ask "who toggled the display during the logo" (bug 1).
- mmio_dump/mmio_clear documented in TCP_COMMANDS.md.

The halt-and-serve path is what made the pig-crash root cause a
one-repro diagnosis.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:17:10 -07:00
Matthew Stanley cd32149a08 tooling: unify dump/screenshot surface, bound TCP sends, fix beetle input + big-dump truncation
- debug_server.c: dump_ram = alias of read_ram (one request, one response,
  up to full 2 MB); screenshot = screenshot_file (24-bit BMP, optional
  "path", default psx_screenshot.bmp); vram_peek cap 64->128; read_ram hex
  encode via nibble table (was 2M snprintf calls stalling the main thread);
  send_all_blocking bounded (1 MB chunks, SO_SNDTIMEO 2s, heartbeats the
  starvation watchdog per chunk) and drops the client on send failure
  instead of letting the watchdog kill the runtime; handle_quit tags exit
  origin.
- beetle_debug_server.c: read_ram cap 64 KB->2 MB via heap+send_raw;
  dump_ram/screenshot aliases; screenshot w/h->width/height parity;
  set_input now holds until clear_input (frames param optional) matching
  psx-runtime semantics — the old default expired after 1 frame and the
  off-by-one in the getter cleared the override before it was ever read,
  so scripted beetle navigation silently did nothing; wtrace_dump response
  heap-built + send_raw (per-entry send_fmt dropped chunks on multi-MB
  responses, producing broken JSON).
- crash_trace: new psx_crash_trace_set_exit_origin(); report gains
  exit_origin (tcp_quit / sdl_window_close / unknown).
- debug_client.py: O(n) string-aware brace parser (old per-byte loop was
  O(n^2) — minutes on a 2 MB response, which starved the server's send and
  got the runtime killed); generic key=value passthrough for any server
  command without a bespoke CLI mapping.
- TCP_COMMANDS.md updated for all of the above.

Verified: 2 MB read_ram round-trip 0.75 s; scripted beetle navigation
reaches the memcard file-select; wtrace_dump returns valid JSON at 21 MB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 07:49:35 -07:00
Matthew Stanley 6f7fcdc89b Remove all psxrecomp-v4 naming — rename to psxrecomp throughout
The "v4" suffix was a holdover from iterative project restarts.
There is now one psxrecomp; all identifiers, include guards, cmake
variables, function names, log strings, schema tags, and tool paths
drop the v4 suffix.

Changes:
- PSXRECOMP_V4_ROOT → PSXRECOMP_ROOT (cmake)
- PSXRECOMP_V4_RUNTIME_SOURCES/_INCLUDE_DIRS/_BIOS_GENERATED → no V4
- psxrecomp_v4_add_runtime_target → psxrecomp_add_runtime_target
- PSXRECOMP_V4_*_H include guards → PSXRECOMP_*_H
- "psxrecomp-v4" log/window strings → "psxrecomp"
- Schema version strings in recompiler JSON output
- Tool script hardcoded absolute paths updated

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 21:14:30 -07:00
Matthew Stanley cf87fd8fc6 crash_trace: structured post-mortem dump (mirrors SMW recomp pattern)
New runtime/src/crash_trace.c writes psx_last_run_report.json with:
  reason / timestamp / frame / last_func_addr / last_store_pc
  full CPU register state (all 32 GPRs + hi/lo/sr/cause/epc)
  dispatch_ring tail (last 256 dispatched func entries)
  unknown_dispatch tail (last 50 entries with addr/phys/ra/a0/a1)
  dirty_block_log tail (last 100 entries)
  Windows SEH info (when triggered from SEH filter)

Triggered from:
  - SIGSEGV / SIGABRT signal handlers
  - Windows SetUnhandledExceptionFilter (catches access violations)
  - atexit
  - psx_unknown_dispatch fail-fast path (set PSX_FAIL_FAST_UNKNOWN_DISPATCH=1)
  - trap_crash (existing fatal trap path)

File overwrites per dump (last-write-wins). Per CLAUDE.md §3 this is not
a log — it's a one-shot final state snapshot for crashes the running
TCP server cannot intercept.

Pattern mirrors F:/Projects/SuperMarioWorldRecomp/src/post_mortem.c.

Used the new infrastructure to capture a controlled crash with
0xBFC0989C re-seeded. Crash dump revealed the chain:
  - JAL at 0xBFC09788 is jalr $s3, NOT jalr $a0
  - $s3 was set in func_1FC09598 prologue: $s3 = $a2 (callee saves arg)
  - Caller at 0xBFC093C4 in func_1FC092EC: $a2 = $s3 - 128 (delay slot)
  - func_1FC092EC at 0xBFC09314: $s3 = $a3 (callee saves arg)
  So real bug is upstream-of-upstream caller passing $a3 = 131 (0x83)
  to func_1FC092EC, propagating down to a misaligned function pointer.
  The 0xA000E1E8 we suspected as a function-pointer table was a
  red herring — $a0 in the loop body is loaded from $fp+s1*4 (the
  ARG to pass to the called fn), not the call target.

0xBFC0989C reverted out of seed list (was diagnostic-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 23:45:33 -07:00