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>
PsxNetPad type 3 is NeGcon, packed lx = twist, rx = I, ry = II, ly = L
(the rollback row's stick view carries twist and L). Every decoder accepts
up to PSX_NETPAD_TYPE_MAX instead of clamping at JogCon, and normalization
keeps NeGcon pressures exact (only twist gets the centre deadzone).
psx_pad_apply_to_sio() is now the one PsxNetPad -> SIO path for offline
sampling, netplay delay/rollback apply and selfcheck replay, so a type
added once reaches the guest the same way everywhere. Selfcheck replay now
matches offline sampling on multitap seats with the analog-on-tap hack.
psx_netplay_normalize_pad moves to a pure TU so tests link the real one
instead of a mirror. Pins recomp-net to the branch that keeps type 3.
Other titles: pads never carry type 3, so SIO bytes are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SIO_PAD_NEGCON = 3 presents a Namco NeGcon (NPC-101): poll ID 0x23 with
buttons, twist (left X), then analog I, II and L (Mednafen negcon.cpp).
sio_set_pad_negcon() supplies I/II/L per frame like the sticks. The device
has no config mode: any command but 0x42 gets the ID byte with no ACK, and
game bytes in a poll drive no motors. Multitap seats stay digital unless the
analog-on-tap hack is armed; the bulk status block carries the NeGcon frame.
Entering or leaving NeGcon is a device swap, not the DualShock analog
button: a deferred request applies even while the game holds the 0x44 mode
lock, and the swap clears the lock, config latch and motor map. Requests
raised mid-config still wait for config exit.
0x44 now selects the analog mode of the device in the port: a JogCon the
game set to digital returns as a JogCon instead of becoming a DualShock.
The mode lock and that device are guest state, so they join the snapshot
as an appended section (older snapshots still load, unlocked).
pad_status reports negcon and mode_locked per slot.
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>
Non-Windows only reconciled while focused AND captured; focus loss drops capture, so a release outside the window left activation_down_ stuck. Decision moved to LocalMousePolicy::sync_left (tested).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f012cf1693)
pgxp.cpp now calls gte_geometry_correction_lookup_probe for pre-draw
probes; the session harness links pgxp.cpp without gte.cpp, so give it
the same no-op stub it already has for gte_geometry_correction_lookup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runtime/tests/test_openxr_color_gl.c is compiled and run by
run_openxr_color_gl.py (a real-GL fixture, like test_gl_readback_region.c),
not a ctest binary. Without the allowlist entry the recompiler project fails
its test-registration check, which blocks configuring that tree.
The shared GL fixture (test_gl_scale_invariance.c) includes
gpu_gl_renderer.c directly. It now reads psx_cycle_count and
g_psx_vblank_cycles and calls the psx_openxr_* core, none of which the GL
filter/scale runners provided, so gl_texture_filter_test failed to link.
Define the same guest-clock stand-ins as test_gl_readback_region.c and
compile psx_openxr.c (its no-op shims without PSX_OPENXR) into the two
runners, as run_gl_readback_region.py already does.
The shared GL fixture (test_gl_scale_invariance.c) includes
gpu_gl_renderer.c directly. The stereo-pair freshness check added for
render passes reads psx_cycle_count and g_psx_vblank_cycles, which the
fixture never provided, so gl_texture_filter_test failed to link on
Windows (and the non-Windows scale test would too).
Define the same test stand-ins as test_gl_readback_region.c.
Rebase a shadow onto GP0's 11-bit field only when flag bits sit above it;
a plain overflowed number keeps master's exact-agreement check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Apply committed sector mods during plugin activation, resolve paths through
patched directory records, and bound file size by CD capacity.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>