Commit Graph

83 Commits

Author SHA1 Message Date
Alex Vanderveen 94cb3807a9 Merge origin/master into integrate/game-codegen-hooks-into-rollback
Keep rollback/netplay multiplayer routing, dual-raster GL present, and TOC
fingerprints while taking master's CHD support, mod load acceleration,
adaptive widescreen, and geometry/perspective APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 22:50:38 -04:00
Alex Vanderveen 652be7ba02 rollback FMV patches and overlay 2026-08-06 00:52:14 -04:00
Matthew Stanley b2af4e89c9 feat(mods): add trusted game enhancement hooks 2026-08-03 15:17:39 -07:00
Matthew Stanley a292a7c136 Add activation-only widescreen cull margin 2026-07-28 11:08:03 -07:00
Matthew Stanley a6bd9455ca fix: exclude fail-closed stubs from game dispatch 2026-07-27 10:48:41 -07:00
Matthew Stanley 2696c391b8 feat: add aspect-aware world participation 2026-07-27 10:48:41 -07:00
Matthew Stanley 436fe14101 widescreen: support guarded object participation overrides 2026-07-25 23:24:41 -07:00
Matthew Stanley 986d8be94c Merge remote-tracking branch 'origin/master' into integrate/openbios-bundle
# Conflicts:
#	recompiler/src/full_function_emitter.cpp
#	recompiler/src/full_function_emitter.h
#	recompiler/src/main_bios.cpp
#	runtime/runtime.cmake
#	runtime/src/main.cpp
2026-07-25 12:31:05 -07:00
Henrique Guedes 6749b826d4 feat: OpenBIOS support — profile-driven BIOS recompilation
Adds PCSX-Redux's MIT-licensed OpenBIOS as a second BIOS the framework can
statically recompile, next to retail SCPH1001. Because OpenBIOS is
redistributable, a game verified against it ships its BIOS with the build —
players supply only a disc, and the launcher BIOS picker disappears.

The mechanism is general, not OpenBIOS-specific: every BIOS image gets a
bios/<STEM>.toml profile that is the single source of truth for its identity
and layout.

Recompiler
- bios/<STEM>.toml profiles: [program.image] (sha256 pin, redistributable
  flag), [recompiler.address_model] (boot-time ROM->RAM copy windows,
  kernel-bless window), install_slots, and [recompiler.runtime_exports]
  HLE anchors (0 = feature structurally unavailable on that BIOS).
- BiosAddressModel (new): validated model built from the profile, replacing
  ~9 hardcoded SCPH1001 window constants that had drifted apart across the
  emitter / discovery / game codegen. The emitted C normalize() derives
  from the same table. from_config rejects overlapping/misaligned windows
  and orderings where sequential folding != first-match. A CTest-registered
  equivalence test sweeps every RAM/ROM address against verbatim copies of
  the old helpers.
- Orphaned delay slots (the OpenBIOS-critical emitter fixes): jr/jalr
  slots are inlined before the jump resolves — previously their side
  effect was silently LOST (e.g. a case-return fragment's addiu $sp pop,
  which unwound a shifted frame and walked the exception handler's IntRP
  cursor into a wild dispatch). Orphaned branches resume at ds+4 on
  not-taken instead of re-executing the slot (a read-modify-write slot
  doubling twice broke OpenBIOS readPad). Jump targets latch pre-slot.
- MIPS-I load-delay modeling for dependent load pairs: the successor sees
  the pre-load value (OpenBIOS's cardfasttrack.s relies on it on purpose —
  "gotta break those bad emulators"). Unmodeled shapes warn at build time.
- Discovery hardening: branch-target dispatchability closure to fixpoint;
  jump tables placed at jr+8 no longer walked as code; cross-function
  continuations routed by an instruction->owner map.
- Signed MULT support (unreachable from the retail seed set; modern-GCC
  OpenBIOS emits it freely), latency-modeled like MULTU.
- Seeds from ELF symbol tables + data-section code-pointer harvest
  (tools/gen_bios_seeds_from_elf.py) — a source-built BIOS needs no
  disassembly pass. 646 OpenBIOS seeds, incl. 5 empirical dispatch-miss
  seeds.
- Stemmed outputs (<out_stem>_full.c / _dispatch.c) so two BIOSes coexist
  in one out_dir. tools/regen_bios.sh is profile-driven (--config).

Runtime
- psx_bios_image: the generated dispatch now self-describes the BIOS it
  was compiled from (identity: size/CRC32/SHA-256/wordsum; kernel-bless
  window; HLE anchors; bundled flag). Runtime SCPH1001 hardcodes removed.
- Launch check is a hard identity gate (a mismatched image under
  statically recompiled code is a guaranteed wild jump). Bundled builds
  hide the BIOS picker and treat a missing/tampered bundled image as an
  installation defect.
- Faithfulness fixes surfaced by OpenBIOS's tighter timing loops (all
  global, argued in code comments, regression-tested against SCPH1001):
  - SIO pad RX is cycle-paced like the memcard path (RX_RDY at
    shift-complete, ACK +170); total ack-IRQ latency unchanged.
    PSX_SIO_PAD_SYNC_RX=1 restores the legacy synchronous path for A/B.
  - SPUSTAT derived from SPUCNT instead of permanently-asserted busy.
  - GPUSTAT.13 (interlace field) alternates per vblank while interlaced,
    pinned 0 in progressive.
  - CD Init(0x0A) completes in ~3.9 ms instead of borrowing Beetle's
    34 ms Reset figure (OpenBIOS's short bounded retry loop livelocked).
  - Exception EPC guard accepts ROM-window block leaders, not just RAM.
- New TCP debug commands (documented in TCP_COMMANDS.md): bios_info,
  s3_smear_watch, callret_watch; write-trace ring gains $s0-$s5.

bios/openbios.bin is vendored (MIT — notice in bios/OpenBIOS.LICENSE,
attribution in THIRD_PARTY_ATTRIBUTION.md, source pin + build recipe in
bios/OpenBIOS.toml). The emitter fingerprint digest is now content-only:
existing generated/*.emitter.sha will read STALE once; re-run
tools/regen_bios.sh.
2026-07-23 17:10:23 -03:00
Claudio Varandas 64ca4c3dba recompiler: [widescreen.cull] bltz_sites (explicit left-edge funnel widen)
Left-edge counterpart to slti_sites: emit psx_ws_cull_bltz at configured
bltz PCs. detect_cull_bltz_sites only classifies left-edge bltz for
auto_screen_x-qualified functions, so an X-only funnel wired via explicit
slti_sites had no left-edge widen. bltz_sites closes that gap. Identity at
4:3; regen-class.
2026-07-22 22:36:41 +01:00
Matthew Stanley f1bdefad20 runtime: batch faithful FMV load-cycle charges 2026-07-21 22:23:15 -07:00
Alex Vanderveen 31015ceab1 feat: launcher polish and recomp-net lobby netplay
Wire MotK-style lobby (recomp-net submodule, WS client, match_caps),
netplay delay-sync, and launcher UX so host sim settings stay aligned
across peers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 21:37:57 -07:00
Opok 4412bc7a7d widescreen: add per-primitive X-reject bound-load cull sites
Add opt-in [widescreen.cull] xclip_load_sites: a configured `lw rt,off(rs)`
loading a per-primitive X-reject bound (sltu-compared against ANDI-masked
u16 screen X) routes through psx_ws_xclip_bound() - INT32_MAX while the
margins are revealed (reject disabled; the wide-surface scissor clips the
overflow, wrapped off-left u16 coords pass), vanilla value at 4:3. Matched
native codegen emit and interpreter hook; overlay ABI v18 forwarder (NULL
preserves the vanilla loaded value); codegen ver 8. Parser, codegen and
overlay-mismatch regression coverage.

Split from #49: replaces the xclip_globals RAM poke with a recompile-time
instruction transform so native shards and the interpreter behave identically.
2026-07-21 15:02:20 -07:00
Opok a5fdf1ad9a widescreen: add side frustum-plane normal-X cull sites
Add opt-in [widescreen.cull] plane_nx_sites: a configured `lw rt,off(rs)`
loading the X component of a side frustum-plane normal (sign-tested per
corner, dot = nx*px + nz*pz) routes through psx_ws_plane_nx() — scaled by
the inverse aspect factor (4*den)/(3*num) while the margins are revealed,
widening the plane cone by exactly atan((3*num)/(4*den)*tan(theta));
identity at 4:3. Matched native codegen emit and interpreter hook; overlay
ABI v17 forwarder (NULL preserves the vanilla normal); codegen ver 7.
Parser, codegen and overlay-mismatch regression coverage.

Split from #49: replaces the plane sign-test RAM pokes with a
recompile-time instruction transform so native shards and the interpreter
behave identically.
2026-07-21 14:38:55 -07:00
Matthew Stanley 8f78ddbd89 integrate interpreter hotpath and AOT sharding 2026-07-20 15:58:43 -07:00
Matthew Stanley fa626bdb4c feat: add aspect-scaled depth cull sites
Add opt-in slti/sltiu far-bound scaling with matched native and interpreter semantics, correct immediate sign extension, and an overlay ABI forwarder for live aspect state.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-20 12:14:18 -07:00
Matthew Stanley 288926111f feat: add masked-u16 widescreen cull sites
Add a shared native/interpreter helper for sltiu classifiers operating on ANDI-masked 16-bit screen X values, preserving MIPS immediate sign extension and overlay linkage.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-20 12:11:58 -07:00
Matthew Stanley a6ba6b3cfd recompiler: survive data-as-code discovery — emit hardware decode, never abort the regen
'Every title regenerates against the tip and just works' broke on Tomba 2:
discovery sweeps disc-sector ASCII strings ('STAT', 'ERRO', '[%s]') into
function bodies, and 7376e6b's hardening threw at emission time for the
words that decode as branch-likely / undefined-REGIMM — aborting the WHOLE
regen over four data words (the old emitters silently mis-emitted them
instead, which is why pre-7376e6b binaries appeared to work).

Faithful class-level fix, both cases cross-checked against in-tree Beetle:

- Opcodes 0x14-0x17 (MIPS-II branch-likely) are RESERVED on the R3000A
  (Beetle opcode table: op_ILL): emit the architectural Reserved
  Instruction exception raise inline (EPC=pc, Cause=RI, SR push, vector by
  BEV) instead of throwing. ABI-neutral — the dispatch trampoline / CPS
  exit contract re-dispatches cpu->pc after the return. Data that is never
  executed costs nothing; if the guest genuinely reaches the word, the
  kernel sees exactly what hardware would deliver. A loud per-word WARNING
  line surfaces every such site at regen time.
- Undefined REGIMM rt values are NOT reserved on hardware: Beetle op_BCOND
  decodes every rt (branch sense = rt bit 0, link iff (rt & 0x1E) == 0x10,
  which only ever matches the defined bltzal/bgezal). Generalized
  generate_branch_condition to the same decode and dropped the throw; the
  link path needs no change.

Tomba 2 now regenerates cleanly at the integration tip (2698 dispatch
entries, 4 RI-raise sites, all ASCII data words).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:53:10 -07:00
Matthew Stanley 5ded80e61e Merge master (AOT overlay generation) into perf/interpreter-hotpath
Integration branch: the interpreter-perf series (interpreter fallback +
additive shard autocompile + always-on production observability + the
fail-closed alias-body emitter fix) rebased-by-merge onto master's AOT
overlay generation (play-free extraction, bounded indirect discovery,
AOT recall, shadow-diff redesign).

Semantic-union highlights:
- gte.cpp: HEAD's direct-field marshaling fast paths kept; master's
  s_gte_replay_sandbox guards grafted onto every affected site.
- psx_icache.c: HEAD's global tag array + fetch_miss split kept;
  master's transactional shadow record/replay retargeted onto it.
- overlay_capture.c: HEAD's commit/retry/wait-pending pipeline kept;
  master's durable history + autocapture APIs wired into it
  (persist_history_snapshot fed from the immutable .d contribution).
- overlay_loader.c: master's interp-record/native-replay shadow-diff
  redesign kept as base with HEAD's scheduler-bail hardening grafted
  (new SHDIFF_SCHED divergence kind); strict manifest v2 parsing keeps
  master's P pair-id lines; always-on native-call ring preserved.
- main.cpp shutdown calls BOTH overlay_autocapture_shutdown() and
  overlay_capture_wait_pending() (the latter drains the preserve-write
  FIFO the former does not).
- recompiler: master's generate_alias_group refactor retains the
  fail-closed alias-body re-entry defaults from c85f1d1.

Validated on this tree: recompiler + runtime + tests build; all six
runtime unit tests PASS; interpreter perf guards PASS; recompiler
patch/structural tests PASS (l1 decoder oracle noise = 376 pre-existing
on both parents).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:43:41 -07:00
Matthew Stanley c85f1d1345 recompiler: fail-closed overlay alias-body re-entry (Tomba 2 splash reload death)
Root cause of the Tomba 2 warm-cache boot death at the splash->title
transition (production-reachable, pre-existing): the game reloads its
whole program image from disc and re-enters it; dispatch of the fresh
image's ctor thunk (0x80089788) resolved by range to the ALIAS candidate
func_80089770, whose generated body's continuation switch had
'default: break' falling through to 'switch (entry)' -- so a re-entry PC
outside the captured continuation set silently ran the ALIAS'S OWN block
(the crt0 shim tail: reload $ra, call main-init). Result: a 21-cycle
main-init<->alias dispatch loop leaking 0x40 of guest SP per round,
marching the stack down 1.5 MB of RAM until the frames overwrote the
freshly loaded module code itself and execution died in its own stack
garbage (the 'execution completed, PC=0' exit at host frame ~1374).
Cold cache passed because interior PCs fell to the dirty-RAM
interpreter, which is faithful.

Fix, mirroring the PR #46 entry-switch contract:
- alias-body continuation switch: emit the alias entry PCs as cases
  (legal range re-entries) and FAIL CLOSED on anything else
  (cpu->pc = _cont; psx_native_bad_entry; return) so the overlay
  dispatch routes the PC to the interpreter. NEVER fall through.
- alias-body entry switch: default also fails closed instead of a
  silent no-op return the loader would count as executed.
- overlay mode now emits the cpu->pc re-entry guard even when the
  continuation set is EMPTY (generate_function + alias body): a range
  re-entry into a single-block function otherwise runs from the top
  regardless of the requested PC -- the same corrupting fall-through.

Emitter change -> codegen hash rolls (6326a16c -> 7b3e9e15); caches
reshard under the new tag. Validated: fresh-cache reshard run + 3 warm
runs + dbg build all boot past the transition to the attract demo at
60 fps; unit tests + structural guards pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:07:19 -07:00
Matthew Stanley bdeb106e5c feat: add negsub widescreen cull sites
Add an opt-in horizontal low-edge transform for subu rd,zero,rt classifiers, with matching native and dirty-RAM interpreter behavior and guarded overlay variants.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-19 23:25:03 -07:00
Matthew Stanley 504db26445 feat: add bounded indirect overlay discovery 2026-07-19 17:15:17 -07:00
Matthew Stanley 05f6ef3e02 Optimize GTE fallback and isolate interpreter perf runs 2026-07-19 14:23:36 -07:00
Matthew Stanley 7376e6b042 Harden additive overlay shards and speed interpreter fetch 2026-07-19 12:11:56 -07:00
Matthew Stanley 3949854981 Optimize interpreter fallback and additive overlay autocompile 2026-07-18 08:32:42 -07:00
Matthew Stanley 41370a64e4 recompiler: split SCUS_*_full.c into parallel-compilable shards
Emit a shared <stem>_decls.h (runtime extern prologue, static-inline
unaligned helpers, all func + alias-body forward decls) and distribute
function bodies across N ~40k-line <stem>_full_NN.c shards instead of one
monolithic <stem>_full.c. Overlapping-alias host bodies become externally
linked so shard bucketing has no atomicity constraint. runtime.cmake's
GAME_GENERATED_FULL_C is now multi-value (single path still works, so
other games are unaffected until they regen + glob).

Tomba: SCUS_942.36_full.c 41MB single TU -> 36 shards; compile (-O3)
4m35s -> 51s (-j16, ~5.4x). Generated function bytes verified byte-identical
(6245/6245 functions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:54:11 -07:00
Matthew Stanley 5b7e69b441 Remove the sljit Tier-2 in-process JIT backend
The tier mis-compiled and has been hard-gated off since 2026-06-25; overlay
gaps fall to gcc/tcc shards and the dirty-RAM interpreter, unchanged.

Re-do on master of the removal validated on the stale branch (6da8c02),
reconciled with everything master grew since the fork:

- Delete overlay_sljit.{c,h}, overlay_compile_worker.{c,h}, lib/sljit,
  runtime/tests/sljit_*.c, and the runtime.cmake sources/include-dirs.
- Strip the sljit provider/backend from code_provider.{c,h},
  overlay_backend.{c,h} ("sljit" in overlay_backend/PSX_OVERLAY_BACKEND now
  degrades to auto so stale configs still boot), main.cpp, debug_server.c
  (sljit_status/sljit_async/sljit_try TCP commands), overlay_loader.{c,h}
  (live policy, JIT-on-miss, persisted-shard cache, async worker glue,
  psx_sljit_call/cop2/memx host helpers — reachable only via the removed
  cpu->sljit_helpers table), and cpu_state.h (SLJIT_HLP_* enum + the
  sljit_helpers[] member; CPUState layout change reshards via the cg tag).
- Master-only entanglement resolved before deletion: 28165d6's Einhander WS
  player-X bound emit existed in three paths (gcc code_generator, sljit,
  dirty_ram_interp). The gcc shard link contract now forwards
  psx_ws_player_x_bound (ABI v13, previous commit), so deleting the sljit
  emit path loses no widescreen behavior; ffc6688's game-dispatch block
  inside the dead psx_sljit_call helper goes with it.
- Reword comments that described sljit as a live backend (gpu.{c,h},
  ws_*_detect.h, cosim_state.c, dirty_ram_interp.c, autocompile.h,
  boot_state.h, config_loader.h, compile_overlays.py, and the CPS marker
  comments the recompiler emits).

recompiler psxrecomp-game builds clean; game runtimes rebuilt + boot-verified
separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:22:38 -07:00
Matthew Stanley 411532bd87 Merge remote-tracking branch 'origin/master' into spike/load-time-zero 2026-07-14 23:09:27 -07:00
Matthew Stanley 2ceaf5abee feat: park projection and perspective correction
Adapted from https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
2026-07-14 23:06:38 -07:00
douglasjv 28165d6421 Expand Einhander player bounds for native widescreen 2026-07-14 23:06:05 -07:00
Matthew Stanley 64b084e2a7 load: add opt-in event horizon, warm routes, and audio sink 2026-07-14 22:44:31 -07:00
Matthew Stanley 12934ed2c5 data-shards spike: memoized pure-function replay (capture/verify/replay + cycle-faithful credit)
- runtime/src/data_shards.c + include: read-before-write recorder fed from
  the memory chokepoints; byte-verified replay (kernel-bless pattern);
  recorded-cycle credit in slices through psx_advance_cycles +
  psx_check_interrupts_at; purity poisons (MMIO/DMA/thread-switch/budget);
  .dss persistence under the unified cache dir; TCP data_shards cmd.
- emitter: [data_shards] funcs -> psx_datashard_enter at entry +
  psx_datashard_ret at jr-$ra sites (rolls cg-hash).
- obs: phase_hot set=static keyed on new g_psx_last_fn_entry entry stamp
  (current_func dispatch stamp survives post-IRQ resumption and pooled 55%
  of static samples on kernel 0xF40); TCP starv_ring query for the
  always-on PC-sample ring.
- docs/DATA_SHARDS.md: design, soundness model, gates, FMV exclusion.
2026-07-14 11:08:02 -07:00
Matthew Stanley b39387eb00 Fix exact native and static overlay dispatch 2026-07-13 05:23:16 -07:00
Matthew Stanley dbfc124856 Merge master into feat/mmx4-widescreen-fw
Master brought the MMX5 true-wide generalization, which evolved this
branch's earlier X4 work (unified psx_ws_bg2d_* helpers, copied-a1 cull,
sltiu count sites, nw_left_hud_packet arena filter). Resolution keeps
master's evolved implementations and grafts the X4-only pieces on top:

- bg2d count_site accepts signed slti alongside sltiu (master style
  ?1u:0u emission); startx_site accepts subu rd,zero,rt (X4's
  -(scrollX & 15)).
- cull a1 move sites accept both move encodings (addu rD,a1,zero and
  addu rD,zero,a1) with a distinct addu-but-not-move error.
- HUD arena filter: master's nw_left_hud_packet_lo/hi is the single
  mechanism (X4's parallel nw_hud_source_lo/hi dropped); X4's broader
  prim coverage kept — ws_nw_hud_shift_vertices re-anchors polygons,
  lines and dots as rigid composites alongside the rect/sprite sites.
- mono_rect gets both ws_expand_fullscreen_rect and the HUD re-anchor.

Also carried from this branch: binary-search dispatch table and
Release-gated GL frame_perf instrumentation (auto-merged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:52:04 -07:00
Matthew Stanley 97ea975580 widescreen: X4-generation 2D hooks — inline-count bg2d variants, copied-a1 cull, HUD source-range filter
Generalizes the Capcom 2D widescreen machinery for the MMX4 engine revision:

- [widescreen.bg2d] count_site accepts the inline slti/sltiu loop compare
  (X4/X5 style) alongside MMX6's li; startx_site accepts subu rd,zero,rt
  (X4's -(scrollX & 15)); inline-count sites route through the generic
  psx_ws_bg2d_* helpers that keep the emitted ring mask and skip MMX6's
  title-specific host-side refill state.
- [widescreen.cull] a1_sites now also accept a move rd,a1 (addu encoding):
  classifiers that copy the caller margin with no delay-slot nop widen the
  copied value instead.
- [widescreen] nw_hud_source_lo/hi: optional GP0 command-source range filter
  for nw_hud_corners, so a pure-2D game re-anchors only its dedicated HUD
  packet arena and never world sprites. 0/0 preserves legacy behavior.
  ws_nw_hud_shift_vertices re-anchors polygon/line prims as one rigid
  composite; mono rect/dot/8x8 sites shift too.

All helpers identity at 4:3; unset config keys are byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:32:40 -07:00
Matthew Stanley 40b476c4e4 widescreen: finish MMX5 edge handling 2026-07-10 10:07:40 -07:00
Matthew Stanley 57ce271990 Merge branch 'master' into feat/mmx5-widescreen-fw
# Conflicts:
#	recompiler/src/config_loader.cpp
#	recompiler/src/config_loader.h
#	recompiler/src/main_psx.cpp
#	runtime/src/gpu.c
#	runtime/src/main.cpp
2026-07-10 08:52:29 -07:00
Matthew Stanley 79cdb12eed feat: support MMX6 finite-map widescreen 2026-07-10 01:47:29 -07:00
Matthew Stanley d9dae48c8e widescreen: generalize Capcom 2D background hooks for MMX5 2026-07-10 00:57:03 -07:00
Matthew Stanley 850140835a codegen: PSX_EMIT_PROD default ON — collapse data-as-code funcs to loud stubs
The experiment flag is now the default. A "function" that is >=50%
untranslatable (TODO-opcode) words is a data region discovery mistook for
code (primary-opcode-only validity check); emit it as the compact,
fail-closed psx_unknown_dispatch data stub instead of ~hundreds of silent
/* TODO */ no-op lines. Strictly MORE faithful than the baseline (loud
abort-if-dispatched vs silent no-op), and slashes emission: Tomba
259->42MB, exe 59->24MB, -O2 ~25min->~2.5min.

Smoke-validated by full launch of Tomba (SCUS-94236), Tomba2 (SCUS-94454)
and Ape Escape (SCUS-94423) regenerated with the flag on — all boot and
play, no unknown-dispatch fatal (=> no reachable function misclassified).
Set PSX_EMIT_PROD=0 to restore the full-scan (oracle) emission. Proper
productionization moves the judgment upstream into function_analysis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:35:00 -07:00
Matthew Stanley 43820876e4 experiment(codegen): PSX_EMIT_PROD — collapse data-as-code funcs to fail-closed stubs
Discovery's validity check (function_analysis valid_primary_p3) only checks the
PRIMARY opcode, so data words with a valid primary opcode but a garbage
sub-field (e.g. fake-REGIMM 0x01 with rt=0x0E) slip through as "code". The
generator then can't translate the bogus (opcode, subfield) combo and emits a
silent /* TODO: opcode */ no-op. Nothing is currently emitted as is_data_section
for the games, so these ride as code — Tomba 1's 0x29000..0x88000 data region
becomes ~348K TODO lines / 259MB / ~25min -O2 compile.

This env-flagged (PSX_EMIT_PROD) downstream pass re-emits any function whose
body is >=50% untranslatable (TODO) words as the existing compact, fail-closed
psx_unknown_dispatch data stub — using the generator's OWN judgment of what it
can translate rather than duplicating the implemented-instruction set. Default
OFF => byte-identical to the full-scan baseline (the complete/oracle emission
stays intact).

Measured on Tomba 1 (SCUS-94454), validated by full playthrough (plays great,
no unknown-dispatch fatal => no reachable function misclassified):
  generated full.c   259.1 MB -> 39.7 MB   (348K TODO -> 1026)
  game full.c -O2     ~25 min  -> 156 s     (~10x)
  runtime exe          59.3 MB -> 26.7 MB

EXPERIMENT ONLY. Proper productionization moves this judgment UPSTREAM into
discovery (sub-field-aware validity in function_analysis), fixing it for every
title at discovery time without a post-hoc generate-then-discard pass, and turns
the env var into a real config option. Also apply/measure on Tsumu (261MB) and
MMX4 (215MB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:34:59 -07:00
Matthew Stanley ee09ab9bbb widescreen: generic Ape-Escape-class support + native-wide GL perf + cutscene flicker fix
Adds full 16:9/21:9 native-wide support for fully-3D titles (Ape Escape,
SCUS-94423) as a generic, opt-in, 4:3-byte-identical framework capability, plus
a native-wide GL compositor perf fix and a cutscene present-mode flicker fix.

Widescreen (generic, per-game opt-in via [widescreen*]):
- Shared screen-extent cull detector (runtime/include/ws_cull_detect.h) used by
  all three backends (recompiler, dirty-RAM interp, sljit). Per-game signature
  immediates ([widescreen.cull] screen_w_imms/screen_h_imms) — Ape's funnel
  tests 0x181/0xF1 on its 368px display, not Tomba's 0x140/0xE0.
- Three funnel idioms widened: unsigned per-vertex sltiu (psx_ws_cull_sltiu),
  signed min/max slti right edge + bare-bltz left edge (psx_ws_cull_slti/bltz,
  new helpers + SLJIT_HLP appends), and billboard center+/-halfwidth. Explicit
  [widescreen.cull] slti_sites for X-only funnels the auto-detector can't
  qualify.
- gte_game_mode: GTE-activity gameplay detector for 3D titles with no sprite-tag
  helper (gte.cpp notes every RTPS/RTPT). STICKY (>=3 verts, 45-frame hysteresis)
  so sparse cutscene frames never flip to a 4:3 pillarbox = fixes intro flicker.
- nw_hud_corners: push outer-third screen-space HUD sprites to the true wide
  corners in native-wide (else inset by the reveal).
- nw_backdrop: stretch a full-frame axis-aligned 2D backdrop quad to fill the
  wide frame (for titles with a flat sky plane).
- Pattern-scanned interp/sljit widen hooks now gated per-game (auto_screen_x /
  auto_backdrop): fixes an ungated auto_backdrop false-positive that rewrote a
  live GPR = the ~24s native-wide crash on Ape.

Native-wide GL perf (fixes 16:9 frame drops):
- The wide surface centre equals the canonical 4:3 framebuffer, so wide_blit_center
  copies canonical->wide-centre at present (both present paths) and the per-prim
  mirror SKIPS prims fully inside the 4:3 frame (mirror_x/geo/batch_center_only).
  Only reveal-margin prims re-rasterize. Peak frame time 38.8ms -> 17.5ms, no
  vsync misses. Toggle gl_wide_fast; 4:3 untouched (all gated on g_wide_cur).

Runtime-only except the recompiler cull emit (regen needed for cull widening).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:30:09 -07:00
Matthew Stanley 553d9933d1 fix mmx6 timing divergences with cosim oracle 2026-07-01 09:28:50 -07:00
Matthew Stanley 4ddccccaa9 checkpoint: exact-store-PC tooling fix (verified) + freeze/scheduler diagnostics + faithful-timing WIP
recompiler/code_generator.cpp: stamp g_debug_last_store_pc before EVERY game
store (SB/SH/SWL/SW/SWR, SWC2, + the ws-backdrop / game-option special paths)
and declare the extern in the game-C preamble. Previously only the BIOS emitter
(strict_translator.cpp) stamped it, so game stores inherited the last BIOS store
pc -> wtrace/readtrace mis-attributed game writes to the BIOS. VERIFIED LIVE:
MMX6's producer trace for the 0x800CD3F8 write now reports the real game store
pc 0x8001A708 (matches Beetle), not the stale 0xBFC11BF0.

runtime: g_pc0_reason slice-exit provenance, fail-closed exception-sentinel
asserts, named thread-event kinds, freeze-heartbeat RVA emission (diagnostics,
default-on, additive).

Carries the in-progress faithful-timing / MDEC work (cpu_state.h, memory.c,
dma.c, interrupts.c, dirty_ram_interp.c, full_function_emitter.cpp) as an
uncommitted-WIP checkpoint. tools/: regen_bios.sh, bios_emitter_fingerprint.sh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 14:20:17 -07:00
Matthew Stanley 0edb9355bb timing: I-cache fetch Stage 2 — compiled-path emit (both emitters) + default-on
Charge the faithful R3000A I-cache fetch cost in BOTH static emitters at each
cache-line leader, completing the model (interp-only since Stage 1). A leader is
a block leader / mid-block jump-table target (a possibly-cold line entry) OR a
16-byte-line start (addr&0xC==0); intra-line fall-through followers are provable
hits (+0) and emit nothing. Emitted BEFORE the per-instruction interlock/load so
a fetch miss clears the pending load give-back first (Beetle ReadInstruction
order).

- code_generator.cpp (game): emit_pre_icache at the 3 timing sites (normal,
  branch, delay slot); `addr` is already the KSEG0 runtime PC.
- full_function_emitter.cpp (BIOS): emit_icache_fetch at all 5 emit_insn_interlock
  sites (main loop + 4 orphaned delay slots), mapping the ROM/compile addr to the
  RUNTIME guest PC via relocate_ra (BIOS main stays KSEG1 0xBFC.. uncached;
  kernel Part 2 -> 0x500+, shell -> 0x80030000+). Same address value the interp
  passes for the same instruction, so the shared TV array stays consistent.
- psx_icache.c: flip psx_icache_enabled() DEFAULT ON (PSX_ICACHE=0 to disable).

Validated: ruler #2 compiled (4600) == Beetle (4382) EXACT on all 13 loops incl.
icache_miss +14 (was +0); ruler #1 steady delta 0 (56==56) with native cold-refill
spikes (77/84) now matching Beetle's range; Tomba 2 boots past the load wedge to
the intro FMV + PS BIOS logo, no freeze. Default-on path re-validated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:59:13 -07:00
Matthew Stanley d5977976e5 timing: GTE-read + MFC0 + muldiv-stall load-delay give-back (ruler#2 100%)
Closes the last steady-state cycle divergences; native now == Beetle on
the complete 12-loop ruler #2 suite.

- MFC2/CFC2 (GTE reg -> GPR read): new psx_gte_read stalls to gte_ts_done
  AND hands the stall amount to following instructions as a load-delay
  give-back (Beetle cpu.cpp:1332-1341: LDAbsorb=gte_ts_done-ts,
  LDWhich=rt). MTC2/CTC2 (writes) keep stall-only psx_gte_stall.
- MFC0: arms ld_absorb=0 / ld_which_t=rt (Beetle delayed load) — sets
  ReadFudge=rt so a load in the next slot gets no fudge.
- psx_muldiv_stall (MFLO/MFHI): now CONSUMES the pending load give-back
  while stalling to muldiv_ts_done, plus the muldiv_ts_done-1 off-by-one
  shortcut (Beetle cpu.cpp:1723-1736). No-load code is unaffected
  (read_absorb==0 -> plain advance).

Wired in both static emitters (code_generator, strict_translator) and
the dirty interp.

METHODOLOGY (Rule 15): Beetle cyc_watch must be sampled at STEADY STATE
— its warm-up window reports the no-give-back value (the source of the
prior "gte_rtps +15 EXACT" mis-record; true steady Beetle = +11).

Validated at steady state, native == Beetle on ALL 12 ruler #2 loops:
the 4 divergences closed (gte_rtps 18->14, gte_nclip 11->7,
gte_read_use 19->14, ld_div 45->49); the 8 CPU-load/alu/div/mult loops
held. Ruler #1 delta 0. Tomba 2 FMV plays (no regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:42:47 -07:00
Matthew Stanley d8c4a8ec9f timing: faithful R3000A load-delay interlock (ReadFudge/LDAbsorb)
Implements the last unmodeled load-path cycle component — the R3000A
load-delay pipeline interlock (Beetle ReadAbsorb/ReadFudge/LDAbsorb/
LDWhich) — as ONE shared per-instruction model across the dirty interp
and BOTH static emitters, replacing the flat per-instruction +1 base and
the flat +4 main-RAM read wait-state.

Model (transcribed from beetle-psx cpu.cpp + libretro.cpp, verified Δ):
- §1 base: a pending load's per-register give-back (read_absorb) is
  consumed INSTEAD of charging +1 (pipeline write-back overlap).
- GPR_DEPRES: each opcode's src/dst regs end the give-back on use.
- DO_LDS: commits the prior load's give-back + ReadFudge.
- load ReadMemory: +2 fudge iff the predecessor committed no load;
  main-RAM region +3 + completion +2 (LWC2 +1) become the LDAbsorb
  give-back; scratchpad +0.

New runtime/include/psx_cyc.h (inline §1/deps/DO_LDS + step) over new
CPUState timing fields; psx_cyc_load_*/psx_cyc_lwc2_read in memory.c
(region-aware, own the whole load interlock, value via uncharged
psx_read_*); psx_cyc_dep_res_mask in psx_instr_cost.h (per-opcode
GPR_DEP/RES, gen-time literal). cpu->read_* rewired to uncharged in
main.cpp; charge_main_ram_read removed. Wired before each instruction
body in code_generator.cpp (game) and full_function_emitter.cpp +
strict_translator.cpp (BIOS); loads route through psx_cyc_load_*.

Validated against the Beetle oracle:
- ruler #2 load2: native +10 -> +11 == Beetle (all other components
  still exact: alu+1/load+5/div+38/mult+15/gte_rtps+15/gte_nclip+8).
- ruler #1 [0x80001C5C->0x80001CA4]: 54 -> 56 == Beetle steady-state
  (residual 84/77 spikes = I-cache cold refill, the separate P2 axis).
- Tomba 2 boots to the intro FMV (pixels on screen, no regression).

Follow-up (separate commit): GTE-read (MFC2/CFC2) + MFC0 LDAbsorb
give-back, and muldiv-stall give-back consumption — needed for mixed
game/FMV faithfulness but do not affect the rulers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 14:14:47 -07:00
Matthew Stanley ec1fd76b56 timing: faithful GTE (COP2) per-command completion-stall
Models the GTE command latency + stall-on-COP2-access, the next Stage-2 cycle
component. Mirrors the mult/div completion-stall mechanism:

- cpu->gte_ts_done (new CPUState field, appended): a GTE command sets it to
  psx_cycle_count + (cost-1); back-to-back commands serialize (psx_gte_set
  stalls to the prior deadline first). Any COP2 register access
  (MFC2/CFC2/MTC2/CTC2/LWC2/SWC2) calls psx_gte_stall, advancing guest cycles
  to the deadline if the op is still in flight.
- Cost table (psx_cycles.c, psx_gte_cmd_latency): per-op cost-1, transcribed +
  verified from beetle-psx gte.cpp op returns (RTPS15 NCLIP8 MVMVA8 NCDS19
  NCDT44 RTPT23 NCCT39 ... AVSZ4=5 not 6). cost-1 because the COP2 instruction's
  own +1 base is charged separately (Beetle GTE_Instruction returns ret-1).
- Set armed in the shared gte_execute() (one site for both backends); stall
  emitted before every COP2 reg access in both emitters (code_generator,
  strict_translator) and the dirty interp. The compiled "+1 at top" vs interp
  "base after exec_one" offset cancels (same as muldiv).

Validated EXACT on ruler #2 (new gte_rtps + gte_nclip loops): native +15/+8 ==
Beetle +15/+8. All other components unchanged (no regression); Tomba2 boots to
FMV. Requires regen (emitters changed); runtime+BIOS+game regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:41:22 -07:00
Matthew Stanley a3e8f2818b feat(timing): faithful mult/div completion-stall + ruler #2 native half (validated EXACT)
Stage-2's biggest cost gap closed. MULT/MULTU/DIV/DIVU now set a completion
deadline; MFLO/MFHI stall guest cycles until it — exactly like Beetle's
muldiv_ts_done. Latencies transcribed from cpu.cpp: DIV/DIVU=37; MULT via
MULT_Tab24 (14/10/7) on the first-operand magnitude. Native previously charged
ZERO for this.

- Per-instruction cycle charging is now the DEFAULT on this audit branch
  (PSX_CODEGEN_CYCLE_PER_INSN), required so the stall absorbs correctly (the
  running cycle count must be accurate mid-block). Validated cycle-neutral for the
  identity base model; Tomba 2 still reaches the FMV.
- runtime: CPUState.muldiv_ts_done + psx_muldiv_set/stall + psx_mult_latency_s/u
  in psx_cycles.c. Emitted at the mult/div/mflo/mfhi sites in the GAME emitter
  (gated PSX_ENABLE_BLOCK_CYCLES). Stall fires even for mflo/mfhi to $zero.
- ruler #2 native half: tools/cycle_testrom/CMakeLists.txt builds psx-cyctest
  (links the recompiled test EXE), boots disc/cyctest.cue (serial CYCT-00101 so
  disc-identity matches, no warning dialog). measure.py --port 4600.

VALIDATION (native vs Beetle oracle, per-iteration component cost):
  div +38 vs +38 EXACT | div_spaced +38 vs +38 EXACT (absorb works) |
  mult +15 vs +15 EXACT | baseline/alu match.
Remaining: load native +7 vs Beetle +5 (memory.c flat +6 wait-state needs
calibration to ~4) — a smaller refinement. BIOS emitter still block-mode (ruler
#1 unchanged at 30) — applying per-insn + muldiv there is the next step to close
ruler #1's div-stall gap.

No FMV regression (i_stat 0x8D, screenshot verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:00:22 -07:00
Matthew Stanley 2b5ad88dc6 feat(timing): ruler #2 foundation — cycle micro-benchmark test ROM + universal block observe
Ruler #2 (the in-tree cycle-model integration-test ecosystem):
- tools/cycle_testrom/gen_testrom.py: hand-encoded MIPS PS-X EXE of cycle-
  isolation loops (baseline/alu/load/load2/div/div_spaced/mult). Each loop top is
  a block leader, so a cyc_watch single-anchor Δ = one iteration; baseline
  subtraction isolates each component (overhead + fetch cancel). Encoding
  validated by a clean recompile (divu -> gpr[10]/gpr[11], etc.).
- game.toml/seeds.txt/README.md; .gitignore for the regenerable EXE/json/generated.

Universal per-block-leader observe:
- Added debug_server_cyc_observe at every block leader in the GAME emitter
  (code_generator.cpp) to match the BIOS emitter, so ANY recompiled code (game,
  BIOS, overlay) is anchorable on both backends — not just function entries.
  #ifndef PSX_NO_DEBUG_TOOLS so prod = zero overhead.

Regression-checked: regen Tomba 2 + rebuild, FMV still streams (i_stat 0x8D), no
visual regression. Builds clean.

Remaining for ruler #2: native test-runtime delivery (boot->entry handoff) +
Beetle .exe sideload + the per-component compare harness (see README TODO).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:30:14 -07:00