Commit Graph

69 Commits

Author SHA1 Message Date
Shokunin cba31b1923 guest_cycle_scale: range-check TOML integers before narrowing
Reject negative and >32-bit addr/value/size/mask in guest_cycle_scale_gate
and out-of-range guest_cycle_scale instead of wrapping or clamping; add
regression cases. Regenerate the OpenBIOS emitter stamp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:49:37 -07:00
Shokunin 91cd6db705 guest_cycle_scale: one CPU charge for every tier, declarative gate, rollback
Review of #561. Guest time now has two entry points (psx_cycles.h): the
CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion,
i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale,
and the unscaled device advance psx_advance_cycles() (DMA, idle skips,
mul/div and GTE deadline waits). The emitters' block-mode charges emit
psx_cpu_charge; overlay DLLs accumulate it raw and publish through the
cpu_charge callback (was advance_cycles, same slot) into the same host
function, so nothing is scaled at the DLL boundary. The batched
psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache
refills and bios_hle.c all charge through it. At scale 1 it is exactly
psx_advance_cycles.

Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged
at every VBlank edge (deterministic, works online, no mod needed), plus
psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or
to tie the scale to a mod (guest_cycle_scale_gated = true; every mod
session reset shuts it). The carried fraction and both gate states are a
boot_state section (BS_SEC_GCS, written only when the scale is not 1), so
savestates and the netplay rollback snapshot re-simulate the same cycles.
psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback
re-simulation against the real psx_cycles.c.

OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:41:39 -07:00
Matthew Stanley b6e34d24e3 Stop tracking SCPH1001 generated sources
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 20:56:48 -07:00
Matthew Stanley bd10ee3ba4 Regenerate BIOS emitter stamps (SCPH1001, OpenBIOS)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 18:20:59 -07:00
Matthew Stanley cbf005deef fix(bios): refresh retail emitter stamp after actual regeneration 2026-10-07 13:33:51 -07:00
Shokunin fbfc2b8ce3 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:58:38 -07:00
Shokunin 1df2e235f0 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:56:48 -07:00
Shokunin 5e33dad246 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:53:17 -07:00
Shokunin 2bc29cde64 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:50:16 -07:00
Shokunin 024c7bb3b6 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:47:54 -07:00
Matthew Stanley 3a11aea16d Refresh netplay validation against master retaining audited startup cache and local views 2026-10-07 11:59:16 -07:00
Matthew Stanley 7d0a18ebc6 Integrate netplay pad and own-view stack without dropping current-master behavior
Preserve negotiated content and dual-raster pass gates. Retain both fixture assertion sets and HD-off seams. Regenerate both BIOS with actual native emitter. Fix type-only rollback corrections and exclude extra-pad changes from digital-only promote gates.
2026-10-07 11:06:57 -07:00
Matthew Stanley c6b089b896 Combine controller stack with validated PGXP fixes and current master; regenerate both BIOS backends 2026-10-07 10:01:37 -07:00
Matthew Stanley c1a1e18f32 Refresh BIOS emitter stamps after UNC persistence change 2026-10-06 22:06:03 -07:00
Matthew Stanley d66401751e Integrate controller input stack for review with current command index and both BIOS backends 2026-10-06 19:21:51 -07:00
Matthew Stanley 8f126c595b Integrate PR 513 retaining current COP2 hooks and regenerate both BIOS backends 2026-10-06 19:13:37 -07:00
Matthew Stanley f9c7bb0a1c PGXP: carry precision through GTE-as-multiplier arithmetic and lwl/lwr word copies
Fixes Ape Escape terrain seams: CPU-built subdivision midpoints and lwl/lwr-copied
grid corners drew native next to precise neighbours. Adds pgxp_tri_ring/pgxp_store_ring.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 13:35:43 -07:00
Matthew Stanley 66482a7cd8 Add opt-in netplay content negotiation with a backward-compatible LAN protocol
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 22:52:52 -07:00
Shokunin 31d438e76b Refresh the OpenBIOS emitter stamp after the config loader change
The direct_shortcut keys touch recompiler/src/config_loader.*, which the
BIOS emitter fingerprint covers, so configure warned that OpenBIOS was
stale. tools/regen_bios.sh --config bios/OpenBIOS.toml regenerates
byte-identical OpenBIOS_*.c; only the stamp changes. (Master 78f60d59 was
already stale against its own emitter; this stamp covers that too.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:26:25 -07:00
Matthew Stanley d247ce8b88 Add opt-in netplay local viewport width sites, fixed aspect and native-wide per-camera views
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 20:20:18 -07:00
Matthew Stanley 8aa3c63779 Refresh BIOS emitter stamps and fix SDL alias and external net root builds
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 20:20:07 -07:00
Shokunin 1a90c04c27 Hook coprocessor reads into GPRs in the PGXP shadow
MFC0 and CFC2 overwrite a GPR, but the recompiled code did not hook them
(and neither interpreter hooked MFC0/CFC0), so the register kept the
shadow of whatever vertex it held before. Validation by value cannot
prove provenance when the new word equals the old one. Emit PGXP_ALU
after MFC0 and PGXP_COP2 after CFC2, matching the interpreters' CFC2
hook, and hook MFC0/CFC0 in both interpreters. OpenBIOS regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:15:44 -07:00
Shokunin 1c43f638bd Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:28:32 -07:00
Matthew Stanley fe42da2744 Merge release/native-rendering-20261003 into master
Keeps both lines: master's PGXP session, draw-distance clamps, masked rejects
and saturated-winding NCLIP; the release's render-pass kit additions, verified
media/disc extents, timeline/present rings, cull-scale sites. Overlay ABI v30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 19:27:02 -07:00
Matthew Stanley d2c8f03e76 Add guarded native draw replay, motion interpolation and stable world filtering 2026-10-03 23:46:25 -07:00
Matthew Stanley f7f0ad1097 Refresh existing BIOS backends and fingerprints with the pinned emitter 2026-10-03 11:47:10 -07:00
Matthew Stanley 6674024285 Merge master into feat/medievil-native-wide-guards
Reconciled with #470 (PGXP): gpu.c precise vertices take PGXP only under geometry correction and count it, then native-wide edge recovery; the PGXP triangle stat stays PGXP-only. Stubs and tests from both kept; stamps recomputed.
2026-10-02 20:00:23 -07:00
Matthew Stanley 40af8a428c Merge master into feat/pgxp
Master moved the textured-dot test stubs into gpu_exec_stubs.inc; the PGXP stubs move there too. pgxp_note_rect_bypass stub takes (int). Stamps recomputed.
2026-10-02 19:30:59 -07:00
Matthew Stanley 30a4072c25 Merge master into feat/max-detail
Stamps recomputed with tools/bios_emitter_fingerprint.cmake after #480's restamp.
2026-10-02 19:22:24 -07:00
Matthew Stanley 545194860d bios: restamp the emitter fingerprints
2163035e and 85c88d26 changed config_loader.cpp (a fingerprint input) without restamping; a full regen is byte-identical, so only the stamps move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:41:01 -07:00
Matthew Stanley 6b27a1a952 Merge fix: guest-function hooks carry the filter slot 2026-10-02 08:44:21 -07:00
Shokunin 6e1bb6e93d Refresh the BIOS emitter stamps
The draw-distance clamp changes the emitter sources. Both BIOS backends
regenerate byte-identically (no clamp sites outside a game config), so
only the fingerprints move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:36:48 -07:00
Shokunin 8a73babcd9 bios: refresh emitter stamps after byte-identical regeneration
recompiler/src/config_loader.cpp is an input of the BIOS emitter
fingerprint, and this branch adds the [video] PGXP keys to it, so the
committed stamps no longer matched. A release-style configure
(PSXRECOMP_BIOS_STALE_FATAL) and tools/ci/check_bios_stamps.sh refused the
tree. tools/regen_bios.sh for OpenBIOS and SCPH-1001 regenerated both
backends byte-identically; only the two .emitter.sha stamps change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 06:08:55 -07:00
Matthew Stanley 7c7133fbb4 Integrate MMX6 adaptive renderer with current framework master
Preserve current segmented dispatch, active-plan mod hooks and software scale limits. Extend function filters through the active plan, retain the last-store pointer with overlay ABI 25, and keep default config fingerprints stable. Refresh BIOS emitter stamps after byte-identical regeneration and reconcile regression fixtures.
2026-09-30 20:23:16 -07:00
Alex Vanderveen aa4cfea81b bios fingerprint: hash sources with carriage returns stripped
Git for Windows checks text files out with CRLF, so the windows-2022 release
job hashed a byte-identical tree to a different value and BIOS_STALE_FATAL
refused the committed backends there while Linux and macOS passed. Stripping
CR before hashing makes the fingerprint line-ending independent (verified:
an LF tree and a CRLF copy of it now agree). Stamps rewritten to the new
canonical value; the generated C is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 20:38:18 -04:00
Alex Vanderveen f180a32ec1 bios: commit the OpenBIOS and SCPH-1001 backends; releases link both
Decision (Alex, 2026-09-30): the recompiled BIOS carries the same risk as
the recompiled game, which titles already commit, so the retail backend is
compiled in rather than built on the player's machine. The retail image is
still never shipped: the runtime needs its data at run time and accepts only
the exact image the backend was built from, so the player supplies their
own dump as before.

- generated/{OpenBIOS,SCPH1001}_{full,dispatch}.c and their .emitter.sha
  stamps are tracked (.gitignore un-ignores exactly those); regenerated with
  the current emitter.
- bios_emitter_fingerprint.sh no longer hashes the ROM image: the profile's
  SHA-256 pin stands for it and the emitter refuses any other image, and CI
  has no dump, so hashing it made every CI configure read "stale".
- runtime.cmake: the staleness check covers every LINKED stem and
  -DPSXRECOMP_BIOS_STALE_FATAL=ON makes a stale stamp fail configure.
- game-release.yml: no CI-side OpenBIOS emission (generate_openbios.sh is
  gone); configure with BIOS_STALE_FATAL and assert both backends linked.
  check_generated.sh asserts the submodule carries both backends tracked
  and no dump is tracked anywhere.
- The player-side module build stays for a known image a build does not
  link (SCPH-5552).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 20:27:41 -04:00
Matthew Stanley 802e0fb16d chore: stop tracking BIOS-derivative generated/ output
The `generated/` tree contains C and JSON produced by the recompiler from
SCPH1001.BIN — derivative of copyrighted Sony BIOS code. It is regenerated
from a user-supplied BIOS on local build; not committed.

Mirrors the convention used by nesrecomp and snesrecomp.

Also covers release-stage/ and *.zip so local release packaging artifacts
stay out of the tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:06:40 -07:00
Matthew Stanley cf05a0ab1f runtime + recompiler: Tomba boot through BIOS into Whoopee Camp FMV
Recompiler — `recompiler/src/function_analysis.cpp` now stops a
function's end_addr at its mapped `jr $ra` + delay slot instead of
stretching through trailing data. Fixes Tomba's 0x80076DB0, a real
136-byte function that was being emitted as a psx_unknown_dispatch
stub because data after it poisoned the boundary heuristic.
`code_generator.cpp` emits `func_<next>(cpu); return;` as a tail
call when a mid-function seed split leaves the physical fall-through
in a different C function, plus a `debug_server_log_call_entry` at
every function entry. `full_function_emitter.cpp` splits dispatch
into `psx_dispatch_impl(cpu, addr, stop_addr)` with public
`psx_dispatch` and new `psx_dispatch_call(cpu, addr, return_addr)`
so JAL/JALR into RAM stubs can return cleanly without growing the
native stack.

Runtime — `runtime/src/dma.c`/`include/dma.h`/`src/memory.c` now
expose `dma_write_masked()`; the 16-bit and 8-bit mmio paths route
DICR/DPCR/CHCR partial writes through it so a partial DICR write no
longer accidentally clears unrelated channels' pending IRQ flags,
and CHCR only triggers DMA when the written mask actually includes
the start bit. Unified `complete_transfer(ch)` across all channels,
removed `prev_master_flag` edge detection.

MDEC (`runtime/src/mdec.c`, `include/mdec.h`) gains a full debug
surface: `MDECDebugState` snapshot, an always-on 4096-entry
`MDECDebugEvent` ring with 11 event kinds (reset, ctrl_write,
cmd_begin/done, decode_done, dma_in/out start/end, output_drained,
read_underflow), and `mdec_state` / `mdec_trace` / `mdec_trace_clear`
debug-server commands. DMA ch0/1 emit start/end events around MDEC
transfers.

CDROM (`runtime/src/cdrom.c`, `include/cdrom.h`) — read stream
separated from one-shot pending state (`read_cmd`, `read_delay`,
`start_read_stream`, `stop_read_stream`, `deliver_read_sector`);
`cdrom_state`/`cdrom_trace` report the new fields.

Thread/fiber tracing — `traps.c`'s Win32-fiber TCB save/restore now
emits 12 thread-event kinds; new `ThreadTraceEntry` ring (64 K) plus
`thread_trace` / `thread_trace_clear` debug-server commands report
both live CPU regs and the target TCB's full saved register block.

wtrace / mmio rings — `WriteTraceEntry` grew (cpu_pc, sp, v0/v1/a0-a3/
t0/t1); capacity 1 M -> 4 M. `MmioTraceEntry` grew (pc, cpu_pc, sp,
a0-a3, sr, epc, i_stat, i_mask). Both dump commands accept
`count`/`newest=1` for reverse iteration. `restore_trace` and
`dirty_block_log` emit extended register fields (a2/a3/sp).

Misc — `interrupts.c` polls debug-server every 16K checks so it
stays responsive without a separate thread. `psx_runtime.h`'s
`call_by_address` routes through `psx_dispatch_call`. Dirty-RAM
interp JAL/JALR consult `psx_dispatch_game_compiled` when the game
is wired in. iso_reader gains the entry points needed by the game
disc path. New `tools/_tomba_probe.py` for port-4470 health checks.

Generated — regenerated `SCPH1001_dispatch.c` against the new
`psx_dispatch_impl` shape.

ISSUES.md — opened Issue #3 (BIOS PS-logo glyph missing on the
disc-detected screen, cosmetic).

.gitignore — added `recompiler/build-asan*/`, `build-checked/`,
`__pycache__/`.

Validated end-to-end: BIOS boots, disc detected, Whoopee Camp FMV
plays with audio. Frame count advances through ~40K frames without
freezes. `unknown_dispatch_log.total = 0`. Remaining FMV cluster
issues (1/4-size render, FMV-2 audio missing, no skip) tracked
under TombaRecomp/ISSUES.md.

Co-Authored-By: Codex (OpenAI) <noreply@openai.com>
2026-05-11 12:34:49 -07:00
Matthew Stanley 48d9971f26 generated: regen against new discovery + seeds
Driven by:
- recompiler state-continuation pass (Pass 2.6)
- 3 new Tomba boot dispatch_miss seeds
- 4 BANDAID seeds retired (validated against memcard COPY/DELETE/persist)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:50:25 -07:00
Matthew Stanley 696ffbd1f6 Persist BIOS memory-card copies + dev/prod memcard layout
The COPY/DELETE flows were silently no-oping the SIO write protocol:
60 dispatches per copy were missing the 3-instruction predicate fn at
0xBFC0989C (the WRITE-command sibling of 0xBFC09890), so the BIOS's
write-handler fork test returned junk via uninitialized v0 and the
BIOS skipped sending 0x57 over SIO entirely. The in-memory card cache
appeared updated (visual UI worked, leave/re-enter showed copies) but
nothing reached memcard_write_sector or the .mcd files.

Also reorganize memcard directory resolution: production builds put
cards next to the exe; dev builds (CMake build tree detected) keep
them in the project root next to bios/ so a runtime rebuild doesn't
delete them. --memcard-dir still overrides.
2026-05-09 13:15:33 -07:00
Matthew Stanley 3ef8b72791 Fix BIOS delete modal delay-slot dispatch 2026-05-08 23:08:29 -07:00
Matthew Stanley 122dc49f42 Fix BIOS memory card copy writes 2026-05-08 20:50:31 -07:00
Matthew Stanley 97f30d2a76 Preserve PSX memory-card debug state 2026-05-07 17:57:34 -07:00
Matthew Stanley 29ebe255bf recomp: safe-state seeds (02918+09890) + turbo mode + Beetle-sourced card2.mcd
Outcomes:
  - YES/NO COPY confirm modal closes correctly on CROSS press (returns to
    COPY menu — matches Beetle behavior)
  - No crash, no freeze
  - Memory card 2 visible with a known-good save (sourced from Beetle's
    dummy.1.mcr; backup of prior card2.mcd kept as .bak.before_beetle_copy)
  - TAB held = turbo mode (skips VRAM->ARGB conversion + SDL_RenderPresent
    every frame except every 30th); useful for navigation cycles

Remaining bug:
  - The actual save data is NOT being copied. Source card content is not
    written to destination. card1.mcd hash unchanged after a copy attempt.
  - 0xBFC0989C still hits psx_unknown_dispatch 60x per copy. Bisect
    confirmed seeding 0xBFC0989C alone causes a misaligned-dispatch crash
    from caller 0xBFC09790 (function-pointer table at runtime addr
    0xA000E1E8 indexed by predicate result, gives garbage target 0x3).
    Seeding both 0xBFC09890 + 0xBFC0989C causes a softlock instead of crash.
    Therefore 0xBFC0989C is intentionally NOT seeded; the copy data path
    short-circuits because the predicate's stale-v0 result fails to enter
    the actual write code.

Status of the function-discovery-miss bug class:
  - VALIDATED for 0xBFC1A1BC (commit 6889f5c) — bandaid worked, modal
    closes
  - PARTIALLY VALIDATED for the BFC09xxx area — adding 0xBFC09890 alone is
    safe but doesn't unblock the copy. 0xBFC0989C cannot be safely seeded
    because it's likely a mid-function label, not a function entry. The
    proper fix requires recompiling the full function containing 0xBFC09790
    (the caller of these predicates) and ensuring the function-pointer
    table at runtime address 0xA000E1E8 is populated with valid recompiled
    function targets.

Turbo mode: hold TAB on the SDL window. Suppresses 29/30 frame presents,
keeps simulated CPU advancing at full speed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:52:55 -07:00
Matthew Stanley 6889f5c5d2 recompiler: seed missing modal_stub function (BFC1A1BC), add unknown_dispatch_log ring
Modal close on YES/NO COPY confirm now works on recomp. Cause: function
discovery (Ghidra + transitive walk) missed the function at 0xBFC1A1BC
which the BIOS shell calls indirectly during modal close. The 5 nearby
"missing" PCs the diagnostic identified are all in the same gap from
0xBFC1A1B8 to 0xBFC1A3B8; seeding 0xBFC1A1BC discovers the rest
transitively.

Status: modal closes cleanly on X. Actual copy execution still has
downstream missing-function-discovery issues (3 more PCs hit
psx_unknown_dispatch on copy: 0xBFC09890, 0xBFC0989C, 0xBFC02918)
plus likely additional bugs that softlock when those are seeded.
Those remain for follow-up; this commit is the modal-close milestone.

This is a BANDAID per user permission. The proper fix is to improve
function discovery so missed functions like 0xBFC1A1BC are found
automatically. Ghidra didn't see this function, and our transitive
walk didn't reach it either.

Tooling additions:
  - psx_unknown_dispatch_record() ring buffer + unknown_dispatch_log
    debug command. Replaces the prior file-based miss log per
    CLAUDE.md §3 (no log files, ever). Always-on; queryable summary
    (per-target counts) or tail (last N entries with addr/phys/ra/a0/a1).

  - WTRACE_MAX_RANGES bumped from 16 -> 64 (boot defaults were
    crowding out investigative arms).

  - DirtyRamBlockLogEntry +a0/a1 fields, ring CAP 16K -> 4M
    (previous 28ms eviction made retroactive press-window analysis
    impossible).

Diagnostic scripts (kept):
  tools/_unknown_dispatch.py    — query unknown_dispatch_log
  tools/_dispatch_check_v3.py   — was a PC ever dispatched (RAM->ROM map)
  tools/_dispatch_tail.py       — last N dispatched targets
  tools/_freeze_diag2.py        — softlock investigator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:13:06 -07:00
Matthew Stanley a9ab4d914d recompiler: seed 50 missing trampolines + extend shell relocation bound
Codegen audit (tools/_codegen_audit.py) revealed two related dispatch-miss
classes after the predicate-init fix.

(1) Discovery missed the A0/B0/C0 syscall trampolines at BFC428D0-BFC42C00.
    These are 8-16 byte stubs of the form
        li t2, 0xa0/b0/c0
        jr t2
        _li t1, FUN_ID
    that wrap kernel syscall dispatch. Ghidra had no xrefs to them (callers
    reach them via patterns auto-analysis didn't trace), so they never made
    it into ghidra_function_starts.json and from there into seeds. The
    recompiler decoded JALs targeting them (visible as `psx_dispatch(cpu,
    0xB005A8F0u)` etc. in regen output) but with no dispatch table entry,
    these calls hit psx_unknown_dispatch and silently no-op'd.

    For the YES/NO confirm modal, this likely manifested as the modal's
    CROSS handler calling e.g. TestEvent (B0:0xB at BFC42A00) and getting
    no result, leaving the modal in indeterminate state.

    Added 50 explicit seeds for BFC428D0..BFC42C00 (trampolines) plus four
    small kernel/libgte stubs (BFC304AC, BFC30584, BFC33C90, BFC33D1C —
    `jr ra; _li v0, X` return-constant helpers) which were also dispatch
    misses via tail-call paths.

(2) full_function_emitter.cpp's relocate_j_target/relocate_ra clamped the
    shell relocation range at phys 0x1FC427FF, but the runtime dispatcher's
    normalize() (SCPH1001_dispatch.c) handles shell up to phys 0x1FC42FFF.
    This left JALs in BFC42800-BFC42FFF un-relocated — the JAL target kept
    ROM PC[31:28]=0xB instead of being remapped to RAM PC[31:28]=0x8.
    Concrete example: `jal 0xB005A910` at BFC4282C, where the intended
    runtime target 0x8005A910 (a trampoline at RAM offset 0x2A910 = ROM
    BFC42910) became 0xB005A910 (KSEG1 mirror of RAM 0x005A910 — random
    address far outside any dispatch-table entry).

    Extended bound to 0x1FC42FFF in both lambdas to match the runtime.

Audit results: direct dispatch misses 47 -> 0, tail-call misses 5 -> 1.
Remaining 1 is RAM 0x00000CF0 (dynamic install stub, handled by
dirty_ram_dispatch per Rule 18).

Awaiting user repro to confirm YES/NO modal action handlers now fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:56:32 -07:00
Matthew Stanley e54e63131c recompiler: fix chained-branch delay-slot predicate-init UB
When a `beq+delay-slot` terminator's delay-slot address is also a basic-block
label (target of an external `b BFCxxxx` from a sibling branch), the previously
emitted C placed the variable declaration BEFORE the next block label and the
dangling if-goto AFTER it:

  label_BFC2185C:
      capture brA, brB
      int psx_taken_BFC2185C = (brA == brB);     // declaration
  label_BFC21860:                                 // goto target
      cpu->gpr[1] = ...;                          // delay-slot c_code
      if (psx_taken_BFC2185C) goto label_BFC21800; // ← dangling

When control reaches `label_BFC21860` via a goto from elsewhere (e.g., the
COPY branch ends with `b 0xbfc21860`), C semantics make `psx_taken_BFC2185C`
indeterminate because its declaration with initializer was skipped. On MinGW,
stack garbage is non-zero → the if-goto fires unconditionally → spurious
cross-branch jump → infinite loop in DELETE branch (BFC21800).

Concretely this caused the memcard COPY confirm-modal freeze: pressing CROSS
on a save-block target should push widget 0xA modal, but the COPY branch
would push 0xA correctly, then on `b 0xbfc21860` cleanup the dangling
if-goto would fire spuriously and jump into the DELETE branch, which keeps
re-pushing widget 0xB and reverting state.

Fix: in the first pass, collect every branch terminator address and emit
`int psx_taken_BFCxxxx = 0;` at function entry. Change the terminator-emit
from declaration to assignment. Now external gotos that bypass the
assignment see the zero default and the dangling if-goto correctly does
not fire.

Regenerated `generated/SCPH1001_full.c`: 6083 zero-init declarations,
all `int psx_taken_*` at terminator sites converted to assignments.

User-verified: COPY confirm-modal now appears. (YES/NO button/close
behavior still broken — likely additional bugs in the same class or
adjacent codegen edge cases; tracked separately.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:28:26 -07:00
Matthew Stanley 87d99d4f1d seeds: 5 dispatch_miss entries for chain writer PCs (vertical slice partial)
Adds the Beetle-discovered chain writer/init PCs as dispatch_miss seeds
so the recompiler emits standalone functions for them at their RAM
destination addresses (via the existing Phase 1e ROM↔RAM alias rule
RAM = ROM - 0xBFC10000 + 0x500):

  0xBFC14A54 → 0x00004F54 (chain BUSY-writer)
  0xBFC158D0 → 0x00005DD0 (chain init slot-0 BUSY)
  0xBFC158D8 → 0x00005DD8 (chain init slot-1 BUSY)
  0xBFC159F4 → 0x00005EF4 (chain step 0x02-OPEN writer — KEY)
  0xBFC15AA8 → 0x00005FA8 (chain step 0x04-OPEN writer — KEY)

Post-regen dispatch_table contains all 5 as standalone entries. e.g.
func_00005EF4 is a clean 3-instruction leaf (sb $t3, 0($a3); v0=1; jr ra).

Validation (full output in docs/CASE_A_AOT_GAP.md "Vertical slice result"):
- ✓ dispatch_table contains all 5 entries
- ✗ none of the 5 are reached at runtime — chain still doesn't dispatch
- ✗ [0x80007510..0x7570] all-zero — the chain init function FUN_bfc158a8
  (= func_00005DA8) never runs in our recomp during this session, gates
  haven't even reached the initial 0x01 BUSY state
- ✓ shell IS on memcard screen ([0x80066948] = 0x32)

The seed addition is correct and necessary work — it removes one of two
layers of "writer is unreachable". The remaining layer is upstream:
nothing in our recomp causes FUN_bfc14b00 / FUN_bfc158a8 to run during
the memcard flow. That's a different category from "chain runs but
fails on byte N" and is not chased here per Case A directive.

Beetle wtrace also revealed the writers at 0x5EF4 / 0x5FA8 are tiny
leaf utilities called from BIOS code in BFC08{B98,C94,D08,F28} — NOT
chain-step jump-table targets as initially assumed. The chain step
tables at RAM 0x6c70/0x6c98/0x6ccc point at 0x52xx / 0x56xx-0x5Axx /
0x5Bxx-0x5Dxx ranges. Memo updated accordingly.

Includes 4 new validation scripts under tools/ used during this audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 14:26:19 -07:00
Matthew Stanley 77e66f9da3 phase1.0e-d: per-block guest cycle accounting in codegen
Codegen-side foundation for cycle-driven peripheral timing per
docs/CYCLE_TIMING_ARCH.md. Each emitted basic block now calls
psx_advance_cycles(N) where N is the conservative 1-cycle-per-MIPS-
instruction count for the block (sum from leader to next leader,
including delay slot).

Generic per-instruction emit, no card-specific paths. Macro-gated by
PSX_ENABLE_BLOCK_CYCLES so generated output is comparable on/off.

This slice does NOT wire psx_advance_cycles to peripheral advance.
Reason: sio_tick has a legacy IRQ-countdown body that decrements
unconditionally per call; calling it from psx_advance_cycles (which
fires per block, ~100M+ times/sec) drains that countdown in
microseconds and breaks pad timing. A future slice with a peripheral-
only sio_advance (no legacy side effect) will wire SIO. Card byte-15
abort persists for now — expected; the fix needs that peripheral wiring.

Files:
- recompiler/src/full_function_emitter.cpp:
  - precompute block_cycles map (instr count between leaders)
  - emit psx_advance_cycles(<N>u) under #ifdef PSX_ENABLE_BLOCK_CYCLES
    at every block leader, before psx_check_interrupts
  - emit extern decl in generated header
- generated/SCPH1001_full.c: regenerated; 12794 PSX_ENABLE_BLOCK_CYCLES
  guards, one per block leader
- runtime/CMakeLists.txt: PSX_ENABLE_BLOCK_CYCLES=1 added to both
  psx-runtime and psx-beetleoracle target_compile_definitions
- runtime/src/psx_cycles.c: psx_advance_cycles accumulates counter only;
  peripheral dispatch deferred to next slice (rationale in comment)

Validation against 5d4d4bd baseline:
- Build clean both targets
- t=20s tx_writes=928, t=30s tx_writes=5000 (matches baseline)
- pad arm audit 5056/5056 armed, no_ack=0
- 4x CROSS press all succeed
- card txn distribution unchanged: txns reach READ_DATA, "MC" bytes
  flow at txn 0 bytes 10-11 (0x4D 0x43)
- psx_cycle_count grows: 1.72B (t=20s), 2.33B (t=30s), 10.04B
  post-CROSS — proves codegen emit live and per-block accounting works
- Macro=0 build (verified before macro flip): output identical to
  baseline

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:03:07 -07:00
Matthew Stanley 5b3c7dd08d phase4: freeze_check diagnostic + accumulated overnight infra
Today's session work
- runtime/src/debug_server.c: freeze_check TCP cmd snapshots all liveness
  counters (PC, exception entries, exception_reentry_blocks, dirty_ram
  blocks/insns, fn_entry total + top-K histogram, SIO IRQ pending +
  countdown, sio_stat/i_stat/i_mask, mc_max_state) for tight-loop
  diagnosis when the runtime hangs
- runtime/src/debug_server.c: wtrace slot 12 covering [0x72F0..0x72F4]
  (data-byte counter); measured peak=1 confirming setup_data_phase resets
  the counter mid-cycle
- runtime/src/interrupts.c: exception_entries_total + exception_reentry_blocks
  counters + psx_get_freeze_diag accessor
- runtime/src/sio.c: sio_get_freeze_diag accessor for IRQ pacing internals
- memory entries: phase4_card_gate_setup_writes_2026_04_28 (refutes the
  prior \"missing [0x7568+slot] clearer\" framing — setup functions write
  LSB=0 directly), phase4_72f0_one_byte_per_vblank_2026_04_28 (peak=1
  measurement)

Overnight session work bundled in
- runtime/src/dirty_ram_interp.{c,h}: small MIPS interpreter for
  install-at-runtime kernel RAM stubs (Rule 18; e.g., SIO data-byte
  handler the BIOS writes to RAM 0xCF0)
- recompiler/src/full_function_emitter.{cpp,h}: 3057 jal/jalr continuation
  return labels added to dispatch table (continuation fix); branch
  fall-through fix for conditional-branch delay slots
- recompiler/src/strict_translator.cpp: matching translator updates
- generated/: regenerated SCPH1001 dispatch + full C output reflecting
  the recompiler changes
- runtime/include/debug_server.h, src/memory.c: card_txn ring + SIO IRQ
  ring + EvCB walk ring + per-PC dirty_ram counters + always-on wtrace
  coverage for [0x755A], [0x7514], [0x7528], [0x7520], [0x74A4],
  [0x75C0..C8], [0x74B8..C0]
- recompiler/tests/, runtime/tests/: SIO/card protocol unit tests
- tools/: 60+ Python diagnostic scripts (chain_trace analysis, SIO trace
  diff, EvCB walker, wtrace dumpers, etc.)
- docs/: psx_bios_disasm reference + dynamic_handler_install rationale
- OVERNIGHT_2026_04_28.md, SUMMARY_20260412_2.md: session logs

Phase 4 status (still blocked, root cause better understood)
- BIOS card chain runs through R-handlers correctly (chain_trace shows
  R1..R11 cycling 26x); shell IS retriggering _card_read
- Install handler at ROM 0xBFC15F1C increments [0x72F0] correctly per
  SIO IRQ; gate at <0x80 / <0x7F is correct
- Bottleneck: SIO IRQ countdown is access-paced, fires ~1× per VBlank
  vs ~128× needed; setup_data_phase (called from outer coord on VBlank)
  zeroes [0x72F0] before counter reaches 128
- Both attempted fixes today caused freezes:
  * VBlank deferral on [0x75C0]==1 — pending VBlank already in i_stat
    delivered anyway; tighter dispatch gate also locked up
  * Cycle-paced sio_tick at rates 50, 200 (and gated on [0x75C0]==1) —
    runtime freezes once data phase activates, root cause unidentified
- delay=5 narrows the threshold (peak=2 vs peak=1, no freeze) but
  doesn't get to 0x80
- Real fix likely requires deeper instrumentation of the freeze (call
  stack / dispatch state at the moment of TCP starvation)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:29:19 -07:00