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>
psx_mod_set_pad_transform(player, &PSXModPadTransform) registers stage 2b
of pad_external_input: after the physical pad and any controller source,
before the mouse policy. The transform sees the resolved pad plus the
port's host extras (gamepad present, LT/RT 0..255 from SDL, unthresholded)
and may rewrite buttons, sticks and the presented controller type, e.g. a
NeGcon whose I/II come from the triggers. Output is pre-filled as a
pass-through, validated (struct size, ranges, type within allowed_types)
and replaced by neutral when invalid; detaching or a mod/session reset
owes one neutral release frame. initial_type sets the boot/hotplug type.
The resolver stays pure: host extras arrive through a new capture hook and
the transform through another, both covered in pad_external_input_test.
Under the input guard the transform runs on zero triggers and its output
is neutralized. A NeGcon result resets the mouse policy rather than letting
it overwrite the I/II bytes. Offline only: netplay, resim, selfcheck replay
and plain debug overrides never reach it.
With no transform registered every pad is bit-identical to before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trusted game code can register entry hooks and consuming filters that run
only in a netplay session (psx_game_register_netplay_function_entry /
_filter). They live outside the mod package plan, which every online
match clears, so a title's netplay support survives it. Offline they are
inert unless PSX_GAME_FILTER_OFFLINE_PROBE=1. Reimplemented on master's
entry-filter path from the R4 Link Battle branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mouse policy now folds onto the resolved pad (source included) instead of a discarded capture; no-policy path does no mouse polling.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
psx_mod_read_local_pad_sticks(player, out[4]) returns the mapped local
stick axes (lx, ly, rx, ry; 0..255, centre 128) without touching the
SIO sample, so a presentation plugin (a look-around camera) can read the
right stick even when the game selected a digital pad. It returns 0 and
centred axes for an invalid or disconnected player, in netplay, and
during rollback resimulation; peer input is never presentation input.
The debug-server axis override feeds it for deterministic tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>