The session reset clears g_bezel_path, which only stops the next load.
gl_renderer_shutdown() deleted the context but kept s_bezel_tex, and
present_bezel() draws whenever that name is nonzero, so a lobby rematch
would bind the previous session's texture name in its new context.
Shutdown now drops it through gl_renderer_set_bezel(NULL, 0, 0) while
the context is still current. Only a soft return to the lobby and
process exit shut the renderer down.
The source guard checks the shutdown call and set_bezel's NULL path.
MOD_PACKAGES.md says what each half of the bezel reset does and gives
the exact reset values for the adaptive cap and the native pacing rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lobby rematch is the only in-process second session: an offline
session ends the process, and only a netplay match launched from the
lobby soft-returns (netplay_soft_exit -> psx_request_return_to_lobby).
The rematch path re-enters via goto session_reboot, below the first-boot
session block, so it reset no mod-owned state at all. The reachable leak:
a match with [netplay] local_viewport_aspect = "adaptive" leaves Fit
(g_ws_adaptive_view) on, and an offline rematch then runs adaptive
widescreen. The launcher also round-trips the match's fixed aspect
(16:9/21:9 from the local viewport) through ls.aspect_index.
reset_mod_owned_presentation() runs immediately before activation in the
first-boot session block (absorbing the existing blend reset) and in the
rematch path after its commit or netplay clear. Every call clears Fit, the
world and retained scene predicates, the adaptive backdrop preload, bezel
artwork, the blend mode and native VBlank pacing. Later calls also restore
vsync, frame interpolation and its rate, and automatic FMV skipping to
their first-session values, and clear the 8 MiB RAM request (memory_init()
re-latches it at session_reboot) and the texture-bank resolver and
batching flag. Netplay clears the plan, so no plugin has activated before
a rematch; everything past Fit is defensive. The renderer and fixed aspect
are launcher controls and process-lifetime allocations are left alone
(documented in MOD_PACKAGES.md).
The first-call capture lives in mod_session_baseline.h: its first call
records the pre-activation values (settings.toml, PSX_VSYNC, launcher) and
writes nothing back, so the first session, and every run that never
soft-returns, is unchanged. A static_assert keeps the restore honest: it
is only valid while interpolation and Skip FMVs have no launcher control.
Function-entry hooks need nothing here: the commit or netplay clear
empties the hook table and only activation rebuilds it.
The rematch path also re-applies the Settings aspect clamp (4:3 while
widescreen is mod-owned) before apply_netplay_local_viewport_aspect, so a
netplay rematch still gets its local viewport. The first-boot path keeps
the launcher/lobby aspect as before.
Not changed here: the rematch path still skips activation and the
controller, load and disc-speed resets of the first-boot block, so an
offline rematch with mods enabled applies the plan's patches and runs its
VBlank callbacks without activation.
Tests: mod_session_baseline_test (new, CTest) drives the first-call
capture and later restore with vsync on and off; it fails if the first
call writes anything. test_mod_owned_display_controls.py guards the
reset list, both call sites, reset-before-activation on the rematch path
and the rematch 4:3 re-clamp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Winsock sockets are inheritable by default. Any child process the runtime
spawns (e.g. the overlay autocompile's `cmd.exe /C python compile_overlays.py`)
inherits the debug server's listening socket; if the game exits while the
child is still running, the child keeps 127.0.0.1:<debug_port> bound and the
next launch cannot bind it (clients get connection refused / talk to a dead
listener).
Observed: after closing the game mid-autocompile, `netstat -ano` showed :4370
LISTENING under the exited game's PID, held by the surviving python child;
killing the compile freed it. Fix: SetHandleInformation(HANDLE_FLAG_INHERIT, 0)
right after socket(). No behavior change on non-Windows.
This change was made with AI assistance (Claude Opus 5.5, Anthropic) and
reviewed/tested by the submitter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found with the Khronos validation layer on AMD RX 7900 XT (Windows, mingw),
where D24S8 is unavailable and choose_ds_format() picks the combined
D32_SFLOAT_S8_UINT:
- VUID-VkImageMemoryBarrier-image-03320: every view/barrier/clear on the
canonical and wide depth-stencil images named only the STENCIL aspect.
Without separateDepthStencilLayouts, a combined depth/stencil format must
be transitioned with both aspects. Added DS_ASPECT and used it for the
image views, barriers and vkCmdClearDepthStencilImage ranges.
(vkCmdClearAttachments keeps the stencil-only mask - that is legal.)
- VUID-vkCmdClearDepthStencilImage-pRanges-02659: the canonical DS image is
cleared with vkCmdClearDepthStencilImage but lacked
VK_IMAGE_USAGE_TRANSFER_DST_BIT (the wide DS images already had it).
- VUID-vkCmdCopyImageToBuffer-dstBuffer-00191: staging buffers are used for
readbacks (vkCmdCopyImageToBuffer) but were created with TRANSFER_SRC only.
Validation messages over the same 20 s boot (SmackDown 2, Vulkan):
53 -> 3 (the remaining VUID-vkQueueSubmit-pSignalSemaphores-00067 swapchain
semaphore reuse is untouched). The fix also cleared the downstream
VkImageMemoryBarrier-oldLayout-01213 and vkCmdDraw-None-09600 reports.
This change was made with AI assistance (Claude Opus 5.5, Anthropic) and
reviewed/tested by the submitter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adapt the 8 MB RAM work from the rbengine branch into a master-targeted change. The builtin package is disabled by default, activation requests expanded RAM before memory init, and runtime/state/digest/dispatch paths respect the live RAM map.
The setup host located itself with realpath("/proc/self/exe"), which only
exists on Linux. On macOS it failed, so a host launched from Finder (cwd =
$HOME) could not find its project tree: Generate & rebuild never appeared
and forward-to-built-game never fired. Take the path from dyld's
_NSGetExecutablePath there (as recomp-ui's launcher_model.c already does);
Linux and Windows are unchanged.
The netplay rollback "binary path" diagnostic had the same /proc read and
logged "(unknown)" on macOS; it uses the same source now.
No emulation or runtime behaviour changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
recomp-net now carries the peer-facing rollback modules that used to live in
retcomm-rbengine (RetroPortingToolKit/recomp-net#14). Switch the MotK shims and
psx_netplay_sched.c from rbe_sched_* / rbe_ih_* / rbe_hc_* /
rbe_rb_peer_post_tip_ok to their rnet_* names and recomp_net/ headers, and bump
lib/recomp-net to c2338c6.
The netplay_* / np_sched_* surface the rest of the runtime calls is unchanged.
The snap ring and rbe_mono_ms still come from retcomm-rbengine, which stays at
a7b9850 for now; the three moved-module unit tests link recomp_net directly
instead of reaching it through rbengine. RBE_RB_* env knobs keep their names.
Checked with PSX_NETPLAY=ON: psx-runtime links and the netplay tests pass
against both the current rbengine and the split one (rbengine
refactor/launcher-core), and the binary's 33 scheduler / history /
hash-confirm functions now resolve to rnet_* where they were rbe_*.
lobby_chat_ring_test does not compile on master either (chat_push arity) and
is untouched here.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Default = largest multiple of 240 lines within 2/3 of the usable height;
an explicit window_width is still honoured.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gaps the dump does not store were dropped, so every later track (and GetTD)
sat 150 sectors early; games that seek via GetTD landed in the previous track.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reinsert hotkey needs a focused window, so no test could exercise
the lid. This runs the same tray cycle on the same image from the
debug server.
It was added to isolate a wedge seen after a savestate save, and it
established that the wedge predates the disc-change work: a save
followed by this command alone reproduces it, with no disc change.
The index derivation is guarded by !disc_override_path, so an explicit
--disc left selected_disc_index at its default of 1. Every savestate
then carried the _disc1 token whatever was mounted.
Measured on a two-disc set: booting disc 2 with --disc wrote
state_801255AC_disc1_slot08.pst. With this change the same run writes
state_801255AC_disc2_slot08.pst.
This is independent of the disc-change feature. It is in this branch
because the roster registration already computes the mounted index.
main() sets savestate_set_disc_scope() once, from the boot disc. A
mid-session disc change left it behind, so a state saved after a swap
was filed under the disc the game booted from. That is the exact
mix-up the scoping exists to prevent, and nothing in the slot list
would have shown it, because every disc of a set shares one entry_pc.
cdrom_disc_select() now updates the scope, rather than the one caller
doing it, so any future caller stays correct.
Two smaller fixes in the same function. A failed iso_open() left
s_disc_selected naming a disc that is no longer mounted; it now
reports none. A roster path longer than the buffer was silently
truncated and would open the wrong file or nothing; it is now dropped.
The runtime mounts one disc image at startup. No code path changes the
mounted image after that. Some multi-disc games prompt for a disc
change during play and then poll the drive. That poll never succeeds.
The CD-reinsert path reopens the same image path, so it cannot mount
the other disc.
cdrom_disc_select() closes the current image and opens another roster
entry, inside the existing lid window. cdrom_lid_media_ready() returns
0 for the whole window, so the guest cannot read the image until the
window ends. The existing timed close then ends the window.
cdrom_init() is not called. It also resets warm-route, trace and timing
state, which would discard a running session. cdrom_disc_select()
resets the sector buffers, the seek position, and the warm-route
arming. Registered warm routes are configuration and are not reset.
begin_tray_open_cycle() now holds the tray-open code, moved out of
debug_force_cd_reinsert(). Both paths call it.
Add a disc_select debug command. It returns the roster and mounts entry
n when n is given. It also returns the sector count and track count of
the mounted image, which confirms that the reader opened another image.
Tested on Ace Combat 3: Electrosphere (SLPS-02020 / SLPS-02021). Boot
disc 2 with no disc-2 save. The game prompts for disc 1. At that prompt
the CD read count is static at 2955 and frames run at 60.5 Hz, so the
drive is idle. disc_select n=1 mounts disc 1. The game resumes and
plays its first cutscene. Reads restart within two seconds and pass
19971. The read addresses change to disc-1 values. The frame rate does
not drop.
Co-Authored-By: Claude <noreply@anthropic.com>
During a boot FMV the guest parks in BIOS MDEC code with the dirty-RAM counters
legitimately still while frames advance, which is exactly the spin signature.
Every boot FMV wrote a 47-76 MB dump, 1.8 GB per kit in one session, inside
timed windows. MDEC activity within the window now rules the classification
out. Verified on NCII: heartbeat live through the FMV, zero dumps.
(cherry picked from commit bf60c128218949e3e3af7ea867d50cc7a1e8a774)
dirty_ram_text_guard_resync_after_restore() cleared text_modified_bitmap,
and the page-clean fast path in dirty_ram_text_native_ok_ranges_from()
then trusted a clear bit as "bytes still equal the reference" and skipped
the compare. Restored RAM may carry self-modified text (the CMR2 class)
that no live write will ever re-flag: the bitmap is not part of the state
image, and the writes happened in the process that produced it. After a
save-state load into a fresh process, a stale native body could be
admitted for those pages.
Recompute the bitmap from the bytes: one pass over the reference range (at
most 2 MB, once per restore), setting the bit for every page that differs,
so those pages stay under the exact compare.
(cherry picked from commit ab186edf6, memory.c part)
No setup package ever staged one, so overlay_cache = true alone left every
streamed overlay on the interpreter. rebuild now stages the pinned Python,
tcc, this pin's recompiler, headers and the build's codegen hash beside the
normal and diagnostic products, and writes compiler.txt naming the compiler
the wizard already has; the runtime treats that as a gcc-tier compiler, so a
player with nothing on PATH gets -O2 shards. Verified on the NCII kit: gcc
tier reported and four shards built with no compiler on PATH.
(cherry picked from commit 03865e10f6ee198d58c47c74526df5db46bb35a9)