Re-add watchdog/PC-sample fire wrappers and overlay icache_fetch_fn;
stop shadowing the inlined psx_advance_cycles with a missing extern.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-add watchdog/PC-sample fire wrappers and overlay icache_fetch_fn;
pull inlined psx_advance_cycles via psx_cycles.h.
Co-authored-by: Cursor <cursoragent@cursor.com>
Export service_to_now, rename advance to *_slow for the header inline,
and give g_audio_host_rate C linkage.
Co-authored-by: Cursor <cursoragent@cursor.com>
Rename out-of-line load helpers to *_slow to match psx_cyc.h inlines,
declare psx_icache_fetch/reset, and wire overlay callbacks to slow paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
Jackie Chan Stuntmaster (SLUS-00684) hard-blackscreens after the intro
logos: the game's exception dispatcher is re-entrant by design. Its
stage-1 handler saves SR/EPC to a single save slot, then executes
mtc0 CAUSE,0x100 + mtc0 SR,0x101 and relies on the resulting COP0
SOFTWARE interrupt (CAUSE.IP0 & SR.IM0 & IEc) to vector into its
stage-2, which acks the CD INT at the controller, delivers events, and
restores the saved SR. The runtime only ever considered the INTC line
(i_stat & i_mask) gated by SR.IM2, so the sw-int was never taken:
stage-2 never ran, CD INT3 stayed unacked (process_pending freezes the
completion countdown while irq_flag != 0), and the machine deadlocked
with the display disabled and SR parked at 0x101 (IM2 stripped -> all
hardware interrupts dead).
Three changes, all architectural (no per-game logic):
1. interrupts.c psx_check_interrupts(): a pending software interrupt
(CAUSE & SR & 0x0300, plus IEc) is now a deliverable source. The
fast path, the pending check, and the SR gate each consider it; the
INTC path still requires IM2, the sw path only its own IM bit.
Exception entry sets CAUSE.IP2 only when the hardware line is what
is being delivered, so a pure sw-int presents the guest-written
IP0/IP1 bits unmodified (the dispatcher discriminates its stages by
exactly these bits).
2. interrupts.c: save/restore exception_jmpbuf across delivery. The
sw-int nests inside the hardware-interrupt handler window; the
nested setjmp used to overwrite the single global jmpbuf, so the
OUTER handler's later RFE longjmp'd into the exited inner frame
(host stack corruption, SIGSEGV).
3. dirty_ram_interp.c: deliver immediately after an interpreted MTC0
to CAUSE or SR that makes a sw-int deliverable, with EPC = the
committed next pc. Hardware vectors within the next instruction;
waiting for the next block boundary opened a window in which a
second hardware IRQ re-entered the (legitimately non-reentrant)
guest dispatcher and destroyed its saved SR. precise_irq_deliverable
gains the same sw-pending condition.
Verified on SLUS-00684: boots through BIOS/logos/FMV into rendered
3D gameplay at 60 fps, SR observed at the canonical 0x40000401, CD
irq_flag acked, MDEC decoding. BIOS-only boot unaffected.
Known scope limit: compiled (static) MTC0 sites do not yet trigger the
immediate check; the raising writes observed in the wild live in the
BIOS-installed, dirty-RAM-interpreted trampoline, which this covers.
Remove the in-tree launcher implementation and its submodules now that game projects use recomp-ui for launcher support.
Keep Vulkan hidden from the launcher unless a game explicitly sets [video] offer_vulkan = true, and clamp stale Vulkan settings back to OpenGL when it is not offered.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Cycle the renderer selector through Software, OpenGL, and Vulkan while retaining OpenGL-only option gating.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Record queue-wait, acquire, and present durations plus presentation timestamps in the existing vk_perf ring.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Let the frontend remain the sole frame pacer by requesting MAILBOX for tear-free delivery, with FIFO retained as the required fallback.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Keep a shared work command buffer open across logical GPU operations and submit it at the existing synchronization boundary.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Add same-layout color attachment barriers before geometry and wide-surface render passes so prior writes are visible to LOAD and blend operations.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Include the transfer stage in the swapchain acquire wait mask because the present command buffer clears and blits the acquired image with transfer operations.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Pad each packed R16 upload region to an even texel count so subsequent VkBufferImageCopy offsets retain the required four-byte alignment.
Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Add a small generic frame-hook surface for statically linked mod code. Mods can
register C callbacks with mod_register_frame_hook(); the runtime invokes them
after normal input sampling and before presentation/widescreen frame work.
Expose the SDL window symbol with C linkage so linked mod code can integrate
with SDL APIs such as window grab control.
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.
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.
Reconstruct the shareable pieces from PR #58 onto current master: add contiguous MDEC DMA input bursts, reduce per-pixel/per-byte MDEC output overhead, add sparse IDCT fast paths, and keep the MDEC activity stamp sane across rematches.
Also quiet the heavy debug trace recorders while MDEC video is active so debug builds can query state without starving FMV playback.
Regression testing was done with non-debug OpenGL builds across multiple FMV-heavy titles. Debug-tool FMV playback was also exercised with the quiet gate enabled.
Co-authored-by: Alex Vanderveen <alex@technicallycomputers.ca>
Co-authored-by: Cursor <cursoragent@cursor.com>
Restore user savestates to their saved CPU PC instead of the game entry PC, and clear non-serialized CPU/GTE completion deadlines after loading a state so stale absolute guest-cycle deadlines cannot stall the first post-restore unit read.
Debounce load requests for 60 frames after a successful restore. This prevents repeated load requests from stacking multiple restore/recovery cycles into a long apparent softlock while keeping normal save/load behavior unchanged.
Regression testing was done. Caveat: restores can visibly pause, especially near CD/load transitions, and worst-case recovery may take several seconds; observed long recovery cases eventually resumed rather than crashing or permanently hanging.
Co-authored-by: Henrique Guedes <henrique.guedes@porttus.com>
The shared recomp-ui launcher already exposes fullscreen as a universal
tri-state (RecompLauncherCSettings.fullscreen: 0 off / 1 borderless /
2 exclusive), but the PSX runtime collapsed it to a bool everywhere and
only ever used SDL_WINDOW_FULLSCREEN_DESKTOP. Widen the runtime to carry
the full tri-state end to end, matching the SNES/Genesis engines:
- config_loader: UserSettings::fullscreen is now int (0/1/2) instead of
bool. settings.toml persists it as a bare int; loading still accepts
the old bool form (true -> 1) for back-compat with settings.toml files
written before this change, then clamps to [0,2].
- main.cpp: removed every `!= 0` / `? 1 : 0` collapse between
UserSettings, the seed struct, and RecompLauncherCSettings, so 0/1/2
round-trips unchanged through the launcher and back into settings.toml.
- main.cpp: win_flags now maps 2 -> SDL_WINDOW_FULLSCREEN (exclusive
mode change) and 1 -> SDL_WINDOW_FULLSCREEN_DESKTOP (borderless), via
a small psx_fullscreen_flag_for_mode() helper shared with the hotkey.
- main.cpp: the Alt+Enter / Cmd+Ctrl+F hotkey now detects "currently
fullscreen" by testing the SDL_WINDOW_FULLSCREEN bit (set in both
SDL_WINDOW_FULLSCREEN and SDL_WINDOW_FULLSCREEN_DESKTOP) instead of
only the DESKTOP flag, and toggles between windowed and the
CONFIGURED tri-state mode rather than hardcoding DESKTOP.
- launcher.cpp/launcher.rml (the legacy RmlUi launcher, still built
whenever PSX_LAUNCHER is on): fullscreen is now a 3-way cycle button
(Off/Borderless/Exclusive) following the same cycle_*/*_label pattern
as the existing CRT/texture-filter controls, instead of an On/Off
toggle.
Validated with g++ -fsyntax-only (mingw64) against config_loader.cpp
standalone, and main.cpp with no launcher flags, PSX_LAUNCHER only,
RECOMP_LAUNCHER only (against the real recomp-ui RecompLauncherCSettings
header), and both together - all clean. launcher.cpp (RmlUi) was
reviewed by exact pattern-match against the file's existing cycle_crt/
crt_label code (lib/RmlUi isn't checked out in this worktree, so it
could not be compiled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Register configured range_sites with the dirty-RAM interpreter so overlay-resident classifiers use the same two-margin widening already emitted by native codegen.
Co-authored-by: Opok <novelasevilliousesp@gmail.com>
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>
Two independent failures made every live reshard fail SILENTLY (runs
counted, shard_ok stayed 0, the game just never got shards):
- autocompile.c: the child line is executed via 'cmd.exe /C <line>'.
When the line BEGINS with a quoted path and contains further quotes,
cmd.exe strips the first and last quote characters and mangles the
command ('The filename, directory name, or volume label syntax is
incorrect'). Wrap the whole line in one extra pair of quotes -- cmd
then strips exactly those and executes the inner command verbatim.
- launch_tomba2_interp_perf.ps1: the autocompile command used an
unqualified 'python'; with an MSYS python first on the inherited
PATH, it cannot open the Windows-style script path ('can't open
file'). Use the canonical Windows Python312 explicitly and fail the
launch loudly if it is absent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Landed while root-causing the Tomba 2 warm-cache boot death; each of these
was a hole that blocked attribution on the production binary:
- psx_fatal_halt: halt-and-serve is now keyed on the TCP listener being
live (debug_server_get_status) instead of the build flavor. A production
run with PSX_DEBUG_SERVER=1 + PSX_EXIT_HALT=1 now stays inspectable at
the fatal instead of exit(1)ing the evidence away.
- debug_server_get_status: was declared in debug_server.h but never
implemented; implemented (listener state, port, saved socket error).
- event_ring: EVENT_RING_ENABLED=1 unconditionally. The ring records
edges only; its production disable predated the production listener and
contradicted its own ALWAYS-ON design note.
- sp_ring (new): stack-domain (64 KB) transition ring at fntrace_record --
one entry per dispatch whose guest SP crossed a domain, i.e. the
provenance record for 'who installed this stack pointer'.
- disp_ring (new): 128-entry unconditional dispatch tail (target/ra/sp/
cycle) so the final control-flow sequence before a death is always
reconstructable post-mortem.
- overlay_loader: the native-call ring (overlay_native_ring recent[]) is
now recorded in production too -- each entry names the requested PC and
the claiming candidate CRC, which is what finally attributed the
wrong-variant native run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
The debug server is compiled into every build (recorders no-op under
PSX_NO_DEBUG_TOOLS; emu-thread pump sites early-out on a null check), so
starting the listener observes the REAL production hot path instead of the
instrumented build whose shifted IRQ-delivery timing diverges from it.
Loud init banners (opt-in, listening, socket/bind failure). Also adds
PSX_EVENT_STEP_CONSERVATIVE=1 env opt-in so the conservative device-step
diagnostic is reachable without TCP. Releases set neither: no port opens.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three-layer faithful-core fix for the exception-return contract, root-caused
live over TCP on a deterministic dbg-build repro (vblank authority vetoed
forever while the guest spun in the kernel idle loop, in_exception stuck 1):
1. Interpreter RFE backend parity: dirty-RAM RFE now calls
psx_rfe_mark_escape() exactly like the recompiled opcode and every
overlay shard (ABI v12), and dirty_ram_dispatch_inner takes a pending
escape at the committed transfer before local dirty flow can keep
interpreting the resumed code inside psx_check_interrupts' window.
2. Legacy-sentinel deliveries arm the escape too: the old blanket decline
assumed a sentinel delivery always resumes AT the sentinel, but guest
TCBs never hold the sentinel (Fix B anti-leak), so a kernel
ReturnFromException restore resumes at an approximate real PC with no
exit path armed. The take site preserves the legacy contract: a resume
landing ON the sentinel still exits through the sentinel detector.
3. Same-thread completion of a sentinel delivery via explicit RFE /
ReturnFromException converts to the legacy exit (restore interrupted
GPRs, pc=0, continue the live host chain) keyed on the structural
entry_tcb == exit_tcb test - otherwise the approximate TCB PC surfaces
up the trampoline, severs the live chain, and the run dies one hop
later on an un-re-enterable mid-function PC ("execution completed").
Validated: production build passes the previously-fatal splash transition
(frames 4717+ at 60fps, was deterministic death at 1385 x3 runs); dbg build
advances past the wedge (was frozen at exactly 1385); all runtime tests +
structural guards pass (guards extended to pin the interp RFE contract).
Known follow-up: the PSX_DEBUG_TOOLS build still loses one kernel CD event
through a third timing path at the same site - tracked as an open
foundation item; production config unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit fixes for the async shard-publication pipeline (45a2dda):
- autocompile_shutdown(): stops the pipeline, kills the compiler tree via a
kill-on-close job object (assigned while the child is SUSPENDED so cmd.exe
cannot spawn grandchildren outside it), and JOINS the watcher + preparer —
never abandoning a thread inside the Windows loader lock at ExitProcess.
Wired first into shutdown_runtime(). Crash exits reap the tree via the job.
- Failed off-thread prepares retry (3 attempts, 100 ms) on the worker before
the artifact silently reverts to a synchronous emu-thread first-map; retry
and giveup counters exposed in autocompile_status_json.
- Capacity caps fail closed and LOUD: cand_register shouts once at CAND_CAP;
load_one_dll_prepared refuses at MAX_LOADED_DLLS before registering, so
dll_already_loaded can never lose track and double-register on rescan.
- Publication confinement is filesystem-resolved: published_path_valid
canonicalizes the cache root and the pipe path (GetFinalPathNameByHandle /
realpath), prefix-checks canonical forms, and the worker maps the canonical
string while holding a no-delete-share guard handle (TOCTOU). `..`/junction
spellings that passed the lexical check are now rejected — proven by the
new overlay_path_canon_test boundary test.
- test_autocompile_publication gains a shutdown-mid-run scenario asserting
prepared == committed + discarded with nothing queued; perf guards updated
to require validate -> guard-handle -> canonical first-map ordering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>