- Restart bits: a group waits only when the edit touches it (next differs
from the live value) and the result differs from boot. Dynamic
resolution moves the live internal resolution off boot, so an in-game
texture filter change relaunched the game. test_menu_video_snapshot
covers both cases.
- Return to the frontend from a local game opens the launcher as at
boot; only a lobby match start (the runtime's pending-launch exit, or
the provider's lobby view asking once its match started) keeps the
launch and resumes the room.
- Default provider: with no title choice, the UI library's provider is
used when it ships one (recomp_ui_psx_menu.h), so recomp-ui stays the
default; PsxMenuHost.default_selection tells it to keep the title's
default presentation.
- Debug: menu_host reports provider, open, pause, capture and snapshot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Register netplay_rb_verify_lost_post_test (#636). Each seat is built from
the production POST receive loop, Verify retransmit/wait/timeout block,
quorum publish and progress helpers cut from psx_netplay_rb.c, with
recomp-net's quorum, a controlled clock and a lossy/delaying POST
transport. Covers a Live seat past the target with its POST dropped,
genuine slow replay up to and at the target, first observation, multiple
owing seats, a disconnected or never-present seat, late and duplicate
POSTs, a later epoch and a rematch; each timeout must land within
RB_VERIFY_TIMEOUT_MS of the last in-episode progress, and the following
realignment episode must confirm one digest on every seat and advance the
confirmed frontier. The lost-POST case is also built against the pre-fix
wait (rb_owing_seats_progressed) and must hang there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up on #661 (#636):
- test_depth24_present_hold_vblank.py reads sources as UTF-8 (cp1252 on
native Windows Python raised UnicodeDecodeError).
- The present skip is now guest state: set at guest VBlank, read (not
consumed) by the present and packed into the GPU snap's hold word (bit 8;
older snaps derive it from the count). The executable test showed the
host-side latch made the present of a restored tick differ from live on
the last held VBlank (hold 0, skip 1), exposing stale trailing FMV
columns after a rollback load.
- Depth24 scanout staging and trailing-column blanking move from main.cpp
into gpu_depth24_stage_scanout (gpu.c), so the test renders through the
same code every CPU depth24 present runs.
- New depth24_present_hold_exec_test: drives real gpu.c through a 15-bit to
24-bit FMV, GP1(07h) retargets and mode transitions; compares hold, skip,
upload span and snapshot bytes across windowed/headless/deferred presents,
rollback replay from every tick and disk loads, and checks staged frames
never show stale movie pixels while the new movie stays visible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
restore_edge_irq_replay_test links the real scheduler (traps.c) and IRQ
delivery (interrupts.c) against a deterministic harness guest. Each case
runs live, captures state at a block edge with the edge's resume PC, then
restores from inside guest execution through interrupts_resync_after_restore
+ psx_scheduler_resume_at and requires every delivery (cycle, EPC, CAUSE,
SR, I_STAT, interrupted GPRs, TCB save) and every later block to match live.
Covers compiled-game, BIOS ROM, CPS-continuation and interpreted resumes,
no pending IRQ, I_MASK / SR.IEc / SR.IM2 gating, same-edge dedup against the
game dispatch-entry check, and null-PC recovery (RESUME_CURRENT without
resume_at) not inheriting the one-shot check. On master the ROM,
continuation and interpreted resumes take the IRQ one block late.
The source-guard test now reads traps.c as UTF-8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verify retransmits POST and aborts the episode (realign) after
RB_VERIFY_TIMEOUT_MS, but re-arms that timer whenever an owing seat's
tip moves, on the theory that it is still replaying. A seat that already
finished the episode and runs Live past the target also moves its tip,
so a lost POST from it kept the others in Verify until the liveness
timeout ended the match (3 seats across a Mac and a PC: two seats waited
for "peer POST" at tick 12099 while the third was Live at 12114; #636).
Only progress up to the episode target now re-arms the wait; past it the
timeout is honoured and the episode realigns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GP1(07h) arms s_d24_present_hold to skip a few Swaps after a depth24
display-band retarget. The hold is in the GPU snap, but it counted down
only at host present: a headless run (and any path that skips the present)
left it stuck, and a rollback snap load zeroed it. In R4's 4-seat rollback
self-test the replayed saves then differed from the live saves of the same
ticks in the GPU section (hold 3 live, 0 replay; #636).
The count now moves at guest VBlank (gpu_vblank_tick), which also latches
whether the following present skips its Swap; the present only consumes
that latch. A rollback snap load keeps the restored count. A disk
savestate load still clears it so the restored VRAM shows at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every state restore (savestate, rewind, selfcheck, rollback load) resumes
through psx_scheduler_resume_at at a block edge where a poll inside
psx_check_interrupts took the snapshot. The live run then evaluated IRQ
delivery at that same edge. The resumed run went straight into the block,
so an IRQ pending at the snapshot point was taken one block later, at a
different PC: a rollback replay delivered the tick-boundary VBlank 10-27
cycles after the live run did (live in BIOS ROM at 0xBFC07644, replay at
0xBFC0766C), and the replayed save differed from the live save of the same
tick in EPC and the TCB in RAM (#636).
resume_at now arms a one-shot check that the scheduler runs on the
RESUME_CURRENT path before psx_dispatch. It is the dispatch-entry check
that compiled game entries already run (deduped by cycle and PC), so a
compiled entry is not checked twice; BIOS ROM and continuation resumes get
the check they lacked. The recovery path that sets RESUME_CURRENT directly
does not arm it.
R4 4-seat local rollback self-test (PSX_RB_DET_SELFTEST=120): the boot
replay-vs-live EPC differences are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The admit barrier polls SDL every frame of a netplay match and treated
Esc as "leave the match" before the in-game overlay saw it, so opening
the menu online could end the session (netplay_barrier_escape).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#655 landed the same PSX_TEXTURE_FILTER_* enum and translation in
video_texture_filter.h/.c; drop this PR's duplicate (link error) and
include that header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#602): live_option_values() was process-static and never cleared, so
an override set in one session outlived mod_runtime_initialize and a new
committed plan (committed 'eight', API still answered the old 'four'). Every
plan change -- initialise, commit, the netplay plan (vanilla or negotiated)
-- now drops the overrides. test_mod_runtime: a second session answers its
own committed value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psxrecomp owns the contract between the runtime and any in-game menu
frontend. A provider (PsxMenuProvider: attach, event, is_open, set_open,
draw, detach) is registered by the title with psx_menu_set_provider();
none is registered by default, so a title's UI and pin are unchanged.
The runtime offers every provider the same host (PsxMenuHost): pause
(P1, refused in netplay), video get/apply on PsxHostVideoSettings (P4),
mod switch/option set (P5, via the new psx_mod_feature_enable /
psx_mod_feature_set_option shared with the launcher ABI provider),
reload bindings, exit (quit, back to the frontend loop in-process, or a
quick relaunch with the original arguments), disc/tray slots filled by
the tray policy (psx_menu_set_disc_ops), and optional helpers on the
shared launcher ABI both frontends already speak (game info with the
mod catalogue and lobby, settings get/apply with the handoff's filter
translations).
Policies live here, the same for every provider: the lobby is pumped
during play and a started match ends the local session and starts in
the frontend loop (pending launch, seats and signals kept, pumping
stopped so the match port stays free), the menu is closed and its pause
dropped first; the provider detaches before its GL context goes away at
session teardown and attaches to the next one. --launcher sets
RECOMP_LAUNCHER_PREGAME for frontends that boot into the game;
PSX_HIDDEN_WINDOW is a hidden-window test hook that keeps the frontend.
Nothing here includes or names a frontend's own headers or types.
Tests: psx_menu_host_test, host_overlay_test (relaunch quoting).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#601/#603): the live path and the launcher handoff both collapsed
the texture filter to a boolean (value ? 1 : 0), so neither could carry xBR
(#586) and they could drift from settings.toml. PSX_TEXTURE_FILTER_* is now
the one enum (equal to [video] texture_filtering / gr_set_texture_filter),
and psx_texture_filter_from_ui is the one translation used by the launcher
handoff (seed, UserSettings, live globals) and the in-game apply. Unit-
tested in host_overlay_test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#602): psx_mod_set_option_live stored the new value before any
callback ran, so a refusal ("nothing changes until next start") still
changed what psx_mod_option_value answered. The value is visible to the
callbacks while they run and is rolled back unless one accepts. New
test_mod_runtime case: a refusing callback leaves the running value as is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#601):
- psx_video_apply_live had no netplay guard. In a match only presentation
(fullscreen, scanlines, linear present) applies live; dynamic resolution,
frame generation and the filters are saved and reported with the new
PSX_VIDEO_RESTART_NETPLAY bit (next session).
- internal_resolution and present_thread are written to UserSettings like
the other restart-only fields, so the choice survives a restart.
- The live-apply code sat inside #ifndef PSX_NO_DEBUG_TOOLS but is used by
the launcher path, so Release + launcher did not compile. It now lives
outside the debug-tools block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_video_apply_live applies the [video] keys a running game can change
(fullscreen, texture and FMV filter, scanlines, linear present, dynamic
resolution, frame generation) through the existing live setters, saves
them to settings.toml, and reports renderer / internal resolution /
thread changes as restart-only. Nothing calls it without a provider.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_mod_set_option_live records a session override for a committed
feature's option and calls the plugin's new option-changed callback in
that plugin's context; psx_mod_option_value answers the override. It
returns 1 when applied live, 0 when the change waits for a restart, -1
during netplay (the plan is negotiated between peers). Nothing changes
unless an overlay calls it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#600): with low_latency_input on, the re-drain after the pacer
wait sampled the pad without host_pause_service, so ui_capture was a frame
stale and the overlay's opening chord (Start+Select) could reach the guest
for one frame. The re-drain now services the overlay before the sample.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_host_set_input_sink: an overlay provider sees host SDL events first,
in the event drain and the host pause loop, and may consume them.
psx_host_set_ui_capture: while on, local game input reads released and
centred (netplay included, so peers receive "nothing pressed") and the
external-input gate stays closed. Debug builds gain host_key, which pushes
a real SDL key event for scripted runs. Inert without a provider.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#599): who keeps the callback context alive until render-thread
calls are quiescent. Draw calls now run inside an in-flight count, and
set_draw_cb (replace or NULL) swaps the record and waits until no call
that read the old one is running, then frees it. When it returns, the
registrant may free ctx and its GL objects; it must unregister before
destroying them or the context. A change from inside the callback does
not wait. Documented in host_overlay.h. Tests: a reader thread drawing
while the owner unregisters and frees (300 rounds; fails without the
wait), and self-unregister from the callback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#599): fn and ctx were two separate stores read on the render
thread, so an unregister could pair a live fn with a NULL ctx. They are now
one immutable record behind an acquire/release atomic pointer. The GL
fixture runners compile a fixed source list; gpu_gl_renderer.c now calls
psx_host_overlay_draw, so every run_gl_*.py links host_overlay.c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_host_overlay_set_draw_cb: the OpenGL backend calls it after the game
frame and host OSD layers are composed, before present_shot capture and
the swap, on the thread that owns the GL context. Without a registered
callback nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#598): resuming left the frame pacer's deadline in the past, so it
paid a pause back as up to ~12 unpaced frames. Resume now runs
present_session_reset, an audio cycle resync and a dynamic-resolution hold,
as a savestate load does. host_pause on=1 headless was a no-op that answered
ok; the headless frame path now enters host_pause_loop too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_host_pause_push/pop: a nesting, host-owned pause. While it is held the
runtime freezes the guest at the vblank present (as the save-state menu
does), keeps presenting the last frame and pauses audio. Push refuses
during netplay. Nothing pushes unless an overlay provider (or the debug
server's new host_pause command) asks, so behaviour is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>