Keep selector zero stock and seal all choices until the next session restart. Refuse nonzero online activation, scope non-stock save states by frozen choices, and preserve original instruction guards when combined with effective-address adapters. Use overlay ABI34 on current master without the old menu dependency stack.
Extract the audited adapters and card transport onto master without the held selector/menu stack. Keep adapters and codecs tied to mod activation, use overlay ABI 33, retain rejected-load protection on codec registration, and refuse extended-card codecs in netplay before frame zero. Preserve default atomic save publication and guest-visible write failures.
* feat(widescreen): continue framebuffer-feedback sprites into native-wide bands
Titles tag a 15-bit sprite that copies a framebuffer 1:1 (gpu_ws_tag_feedback_rect / psx_mod_tag_feedback_rect). Where it touches the draw area's edge, gpu.c emits band spans and the new optional backend op wide_feedback_rect (GL incl. render thread, software) samples the source buffer's wide surface. Band texels count as bit 15 set, as the centre does once the trail blends.
The software rasterizer now writes the texel's bit 15 into the framebuffer, and wide_full can dump the surface alpha ("alpha":1).
* fix(widescreen): quantise band feedback like packed VRAM
The centre samples 5-bit packed VRAM each frame; the band copy read the 8-bit wide surface and modulated with /128, so the bands drifted ~3 grey levels brighter and left a step at the 4:3 edge. Truncate like PACK_FS and modulate like TEX_FS.
* Reject fully clipped sprites before extending feedback margins
* Complete isolated resident and PGXP fixture collaborators
* Preserve existing line endings in the review fixes
---------
Co-authored-by: Matthew Stanley <1379tech@gmail.com>
Keep temporary keyboard profiles separate from saved bindings. Add optional persistent mouse capture with ordered release/focus/reset handling, and let offline sources represent an unplugged port without falling back to physical input. Existing capture and source defaults remain intact. Beads: beads-9wg8 and beads-eio.19.18.
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>
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>
In a netplay match psx_mod_set_host_rumble now takes only this peer's own
guest port (psx_netplay_local_port) and drives this peer's input pad with
it; other ports and calls made while rolling back are ignored, so a
netplay = "host_output" plugin can report every port each tick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some titles compute vibration for every pad but only send it over SIO to a
DualShock (R4 with a presented NeGcon). A trusted mod can now hand the runtime
DualShock motor values per seat; the host pad gets the louder of that and the
guest's SIO motors. Values lapse 8 VBlanks after the last call and are cleared
at every session reset, so nothing can stick on. Opt-in: no title that does
not call it changes. PSX_RUMBLE_TRACE now also logs headless.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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.
Load modern texpage/texupload PNG replacements independently of DuckStation source. Integrate pack folders and options with mods, preserve native VRAM, and validate GL rendering, format matching, dump safety, and upload invalidation.
A [[plugin]] may declare netplay = "local_view": it only changes what one
player sees. A netplay session now keeps the player's own features whose every
contribution is such a plugin (no writes, overlays or derived discs) instead of
clearing them, without saving state.toml. They activate as usual, but their
hooks run only inside psx_mod_render_local_view (psx_mod_local_view_scope) and
their vblank/savestate callbacks not at all. With such a plan the widescreen
cull margin is the stock 0 in the shared game and this peer's own margin only
inside the sandboxed own-view draw; squash is not used. The canonical frame,
snapshots and digests stay identical on every peer whatever each player chose,
so nothing is negotiated, and each keeps their own aspect.
Tests: netplay_view_mods_test (flag, plan filter, parked hooks, no vblank,
state.toml untouched), ws_local_view_margin_test (margin scope), render-pass
sandbox asserts the scope around a local view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_mod_render_local_view() runs a title's draw code in the render-pass
sandbox on a forward netplay frame and keeps the drawn display rect in the
OpenGL presenter's surface only. The authoritative CPU VRAM, guest RAM,
rollback snapshots and digests keep the canonical frame; the presenter shows
this peer's own image. A committed image cancels any local-view crop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title calls psx_mod_set_rewind_blocked(1) while it is in a mode that must
not be rewound, such as local split-screen multiplayer, and 0 when it ends.
While blocked, opening Rewind is refused with an OSD note, no history is
captured (the ring keeps what it has; a capture already due is dropped and an
open panel closes unloaded), and a title-allowed direct Rewind shortcut is not
in force: its button is not claimed and reaches the guest pad / title
transform as if Rewind were off. Netplay already refuses Rewind on its own.
The block clears at every mod/session start, so a title that never calls it
is unchanged.
The direct-allowance decision (allowed, and for Rewind: enabled and not
blocked) is now one pure helper, psx_hotkey_direct_active, used for both the
claim and the shortcut press, so an unclaimed Rewind button is never a
one-button shortcut (with Rewind off it falls back to Select + button rather
than popping the "Rewind is off" note on every press).
Tests: psx_hotkey_pad_test covers the allowance and a block taking effect
mid-press; rewind_toggle_combo_test guards the capture/open gates and reset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
psx_mod_allow_direct_shortcut(PSX_MOD_SHORTCUT_*) lets a host shortcut
bound to one controller button act on that button alone. Without it a
one-button binding means Select + button, like the legacy encoding, so a
title can default to a one-button binding safely whether or not its mod is
enabled. While allowed (Rewind: while Rewind is enabled) the runtime claims
the host button: it never reaches P1's guest pad, and a claim made while it
is held lasts until release. Allowances clear at every mod session start.
Multi-button bindings behave as before.
game.toml [controller] direct_shortcut / direct_shortcut_button lets the
title default that shortcut to one button and tells the launcher to capture
it as one button (RECOMP_LAUNCHER_HAS_DIRECT_ASSIST_BIND). No title id is
checked anywhere.
Debug: set_input {"layer":"host", buttons, lx..ry, lt, rt} arms a
virtual P1 gamepad that feeds stage 1 of the offline input path (so sources,
title transforms and trigger values see it) and shortcut polling. A plain
set_input override still wins. Headless runs with the layer armed resolve
P1 through the same path and poll Rewind and the fast-forward latch; the
Rewind pause loop keeps the debug endpoint live and runs without a window.
Binding semantics and the claim latch are pure (psx_hotkey_pad.h) and
covered by psx_hotkey_pad_test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A digital presentation folds the sticks onto the D-pad and centres them,
so a transform on a digital player (the default for many titles) never
saw the stick. The transform decides what the guest sees, so for a
digital stage-1 pad with no controller source its frame is now the host
pad: real sticks and buttons without the stick->D-pad fold, type still
DIGITAL (new PadExtHooks.host_pad, capture_pad_slot_view). The output is
pre-filled with the stock pad and a decline delivers it, so pass-through
stays the stock folded digital bytes (mod_pad_transform_run takes the
stock pad). Analog pads, sources and guarded frames are unchanged.
Tests: pad_external_input_test (digital player: frame has the real lx and
unfolded buttons, decline and pass-through deliver the stock digital
bytes, no host-pad query for analog / guarded / source ports),
mod_pad_transform_test (decline returns the stock pad, output pre-filled
from it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>