Commit Graph

835 Commits

Author SHA1 Message Date
Alexandros Mandravillis b1fd460350 interrupts: retain handler k0 on qualified same-thread RFE 2026-10-17 18:01:31 +03:00
Matthew Stanley 50958497c4 Integrate merged experimental wheel support before preset validation 2026-10-15 13:49:46 -07:00
Matthew Stanley a6ee3aa062 Integrate current master and canonical BIOS stamp for PR609 validation 2026-10-15 13:40:27 -07:00
Shokunin 8be777f1f1 quality presets: a supported key set, host-side Custom classification
- [quality.*] accepts only the supported keys (quality_key_supported):
  twelve player-editable ones (masked, copied, classified, applied) and
  nine preset-only ones (no settings.toml form; applied on select). Any
  other key, e.g. vsync or renderer, is a load error.
- Picking a preset now also applies bloom, texture LOD, anisotropy, FMV
  chroma smoothing and dithering (quality_apply_video_globals).
- The frontends flag Custom on any tracked row; the host counts only rows
  of keys the title governs (psxq::launcher_custom_edit), so Antialiasing
  no longer turns Ultra into Custom when no preset sets it.
Tests cover the rejection, every player key's masking/copy, and the
classification cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:57:27 -07:00
Matthew Stanley 6d3c5308a7 Integrate current master and canonical BIOS stamp for preset re-review 2026-10-15 08:34:17 -07:00
Shokunin dc53ebe7d9 quality presets: document the preset/key-ownership transaction
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:07:32 -07:00
Matt Stanley b6835b6773 Merge pull request #667 from RetroPortingToolKit/work/prepared-resource-overlays
Support verified prepared resources as disc overlays
2026-10-15 00:51:10 -07:00
Matthew Stanley d110d0a423 Integrate current master for reviewed PR597 and retain canonical BIOS stamp 2026-10-15 00:36:07 -07:00
Matt Stanley d550ddf2e9 Merge pull request #590 from RetroPortingToolKit/feat/idle-skip-store-counters
idle_skip: elide store-counter wait loops (PsyQ v_wait), opt-in
2026-10-15 00:34:00 -07:00
Matt Stanley 9521343200 Merge pull request #664 from RetroPortingToolKit/fix/menu-host-followups
Menu host follow-ups: restart bits for touched groups, local return, recomp-ui default provider
2026-10-15 00:33:44 -07:00
Matthew Stanley fd6b89ccc7 Resolve verified resource slices as disc overlays 2026-10-15 00:18:30 -07:00
Matthew Stanley d946aec09a Integrate current master for reviewed PR597 and retain canonical BIOS stamp 2026-10-15 00:00:06 -07:00
Matthew Stanley 80674e4d9f Integrate current master for reviewed PR593 and retain canonical BIOS stamp 2026-10-14 23:59:50 -07:00
Matthew Stanley 2d2c1849f3 Integrate current master for reviewed PR591 and retain canonical BIOS stamp 2026-10-14 23:59:13 -07:00
Matthew Stanley e0800a28e0 Integrate current master for reviewed PR590 and retain canonical BIOS stamp 2026-10-14 23:58:57 -07:00
Matthew Stanley dfd4555d09 Include fixed scaled-charge batching profile in netplay compatibility 2026-10-14 23:51:49 -07:00
Matthew Stanley 2947916215 Detect active gamepad sessions without hijacking Deck Desktop Mode 2026-10-14 23:49:26 -07:00
Shokunin c3a2f2c068 Menu host follow-ups: restart bits for touched groups, local return, default provider
- 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>
2026-10-14 12:17:18 -07:00
Shokunin 73bb197a03 Docs: regenerate TCP_COMMANDS.md for the new debug commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:33 -07:00
Shokunin bff226bea7 Docs: regenerate TCP_COMMANDS.md for the new debug commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:20 -07:00
Shokunin 09ee810fbd quality presets: an unrecognised GPU gets Medium, not Ultra; honest docs
Nothing is measured about an unknown GPU, so the fallback no longer assumes
Ultra's extra work (supersampling above the display, title Ultra extras);
Low would drop Smooth motion and the PGXP extras on what is usually capable
new hardware. Medium keeps the display's resolution with dynamic
resolution under it. Covers unnamed renderers, Linux with only an AMD PCI
id, and unnamed nouveau chips. docs/QUALITY_PRESETS.md states plainly that
autodetect is a static heuristic over name, threads and memory with no
benchmark, and that the dynamic systems keep the frame rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin b92b96ea6b Quality presets docs: autodetect is a static Low/Ultra heuristic; unknown GPUs get Ultra
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 43afb3bb16 timing: opt-in batching of the scaled CPU charge
With [timing] guest_cycle_scale active, every one-cycle psx_cyc_charge went
straight to psx_cpu_charge and psx_advance_cycles, bypassing the batch the
scale-1 path uses. [timing] guest_cycle_scale_batch (PSX_GCS_BATCH=0/1)
scales the charge in place, with the same fractional carry and order as
psx_cpu_charge, then batches it like the scale-1 path. It flushes at the same
observation points. Default off.

R4 exactness, frame_fingerprint off vs on, 9000 frames each: headless boot
through intro, title and attract; a 1P Grand Prix race; and a 2P VS race.
cyc, mmio, mc and qc are identical on every frame. sp, wr, pc and ws are
identical at the end. The only differences are single-frame VBlank straddles
of wc/sc (never two frames in a row). 0 dispatch misses.

Speed, release builds, races at 60 Hz:
- M4 (macOS sample): psx_cpu_charge 4.3 % -> 0 % of emulation-thread samples;
  busy 46.0 -> 44.4 % (2 pairs).
- 9800X3D: no measurable change (30.0 vs 30.5 %; one core 44.6 vs 44.2 %).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin f99a318999 quality presets: review fixes (headless, persistence, Linux, renderer table)
1. Detection never runs under --headless / --hidden-window (CI, scripted
   runs): a saved preset applies, else the title's [video]; nothing is
   written. A detection is saved only when the GL probe succeeded; a result
   from the OS name alone is used for that run and the next launch probes
   again (a failed probe could otherwise pin "unknown GPU" -> Ultra).
2. Linux without a GL probe: the classifier now reads the sysfs PCI id
   (Steam Deck parts, Intel integrated vs Arc, NVIDIA).
3. AMD/Intel rules rewritten against real renderer strings: "Radeon RX
   Vega 11 Graphics" and other Vega/"Radeon Graphics"/R5-R7/680M-890M APUs
   are integrated (Low); RX/Pro/W cards and Strix Halo are Ultra; the Intel
   Arc iGPU is Low, Arc A/B cards Ultra. quality_presets_test holds a
   44-row table of Windows, Mesa (incl. Steam Deck LCD/OLED, zink) and macOS
   GL_RENDERER strings, plus probe-failure and headless cases.
5. generated/OpenBIOS.emitter.sha re-stamped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 59189e2f67 quality presets: autodetect picks Low or Ultra only
Owner correction: low-end machines (software GL, Steam Deck, mobile GPUs,
integrated GPUs up to the 890M/Iris Xe class, Apple M1/M2 base, GeForce
MX/GT, fewer than 5 threads, under 6 GB) start on Low; every other machine
starts on Ultra with the dynamic systems as its safety net. Medium and High
are manual choices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 35e55efb08 quality presets: Ultra by default; step down only classes that cannot hold 60 Hz at the dynamic floor
Owner decision: Ultra on first launch, with dynamic resolution, Smooth
motion from surplus and the adaptive aspect as the live safety net. Apple
silicon, discrete GPUs, unknown GPUs and 8 GB machines now detect as Ultra;
software GL, Steam Deck, mobile, Intel HD/UHD and small AMD iGPUs stay Low,
mid iGPUs Medium; fewer than 4 threads or under 6 GB still caps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 6b71927fda quality presets: test PGXP extras in a preset; doc example
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin d5fc43d802 Graphics presets with hardware detection (opt-in per title)
A title may declare [quality.low|medium|high|ultra] tables of [video] keys.
On first launch, and when the hardware fingerprint changes while a preset is
in force, psxrecomp detects the CPU, threads, RAM and GPU (OS facts; a hidden
1x1 GL window reads GL_RENDERER only when a detection is due and the OS did
not name the GPU), picks the matching preset and saves quality_preset,
quality_base, quality_hardware and quality_detected to settings.toml.

- Each preset is the whole game.toml re-parsed with its keys over [video], so
  every [video] key works in a preset with its usual validation.
- A preset in force owns its keys: saved settings.toml values for them are
  ignored. Custom (the player changed a governed setting in the launcher)
  applies the player's values over quality_base and is never overridden;
  new hardware under Custom is only reported.
- Launcher: GameInfo.quality_* (offered mask, detected preset, hardware
  summary, quality_apply / quality_redetect callbacks) and
  Settings.quality_preset/quality_base, behind RECOMP_LAUNCHER_HAS_QUALITY_PRESETS.
- PSX_QUALITY=<preset> for one run, PSX_QUALITY_REDETECT=1.
- Titles without [quality.*] are unchanged.

Rules (quality_tier.c) and the decision are unit-tested in
quality_presets_test; docs/QUALITY_PRESETS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 7a4a1a004a idle_skip store counters: only audited sites (idle_skip_store_counter_sites)
The detector cannot prove a loop has no later counter-dependent exit, so
the extension now skips only at block-edge PCs the title lists after an
audit. Unlisted sites are never skipped; an empty list disables it. Docs
record R4's v_wait audit as the reference; tests cover unlisted sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Shokunin d3047ede27 docs: developer contract for idle_skip_store_counters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Shokunin f2570a8f76 idle_skip store counters: review fixes, exactness from boot, pinning test
- Last-store tracking: memory.c owns the record and writes it only while
  the extension is on (g_psx_track_last_store); main.cpp hands it and the
  host writer (psx_host_write_word) to psx_cycles.c through
  psx_idle_skip_set_store_tracker / _set_host_writer, so psx_cycles.c no
  longer references memory.c (fixes the tests that link one without the
  other).
- Exact from boot: the device distance is now measured from the current
  cycle (devices report it from their last service, up to a few hundred
  cycles behind), and several edges per pass are accepted only together
  with a counter store. Before this, R4's boot diverged at frame 962 (an
  OpenBIOS multi-edge poll and a late VBlank IRQ after a v_wait skip).
- tests/test_idle_skip_store_counter.c: a v_wait trace skips (whole
  passes, counter lowered by the host writer, never to the timeout, lands
  before the event); an extra store, an MMIO read, a decrement of 2, a
  store to MMIO, the option off and no host writer do not.
- generated/OpenBIOS.emitter.sha re-stamped.

R4 frame_fingerprint off vs on, 9000 frames each: boot through intro and
attract, a 1P race and a 2P VS race: cyc, mmio, mc, sp, sc and qc
identical on every frame, 0 dispatch misses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Shokunin 9515cb7dfe idle_skip: elide store-counter wait loops (PsyQ v_wait)
idle_skip only skipped loops with zero stores and a single interrupt-check
edge, so it never fired on PsyQ libetc's v_wait -- the VSync(0)/VSync(n)
spin most PsyQ titles run each frame -- which decrements a stack timeout
word every pass and crosses two block edges.

New opt-in on top of idle_skip, [runtime] idle_skip_store_counters
(PSX_IDLE_SKIP_EXT=0/1 for one run):
- an iteration may span up to 8 block edges; the first edge is the anchor
  and the edge count must be stable;
- one word store per iteration to the same RAM/scratchpad address whose
  value drops by exactly 1 (a register holding it may drop with it).
Skips lower the counter by k (never to <= 1) and, for multi-edge loops, land
on the anchor strictly before the next device event so an IRQ is taken at
the same edge as without the skip. memory.c records the last word store's
address and value for the detector.

R4 race, 3600 frames from one savestate, held input: cyc, mmio, mc, sp, sc
and qc identical every frame; wc differs only by the elided counter stores.
Release build on an Apple M4: emulation-thread busy 47.5% -> 34.9% at 60 Hz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Matthew Stanley 1473c66772 Merge validated renderer fixes into generic post-effect pipeline 2026-10-13 10:18:03 -07:00
Matt Stanley 6eaf078699 Merge pull request #584 from RetroPortingToolKit/feat/gl-dithering
GL: PS1 dithering by default (native grid), elective fine/off modes
2026-10-13 10:10:53 -07:00
Shokunin 8dbe2165ff bloom: cost of real and generated frames
Smooth motion lost most of its generated frames with bloom on (R4 race,
120 Hz: ~30% of bloom-off). Measured and fixed in order of cost:

- Depth-stencil blits (CPU milliseconds each on macOS) are gone:
  generation surfaces no longer copy the marks and are not marked while
  drawn (generated frames reuse the newer real frame's mask); the
  native-wide centre copy is colour only again and the mask takes the
  centre's marks from the hr surface; the mask itself is resolved in the
  presented surface's own pixels through an R8 target that shares the
  surface's depth-stencil renderbuffer, then mapped to the window.
- Marking needs one pass per batch, not two: 3D batches only set, 2D
  opaque ones only clear, semi-transparent ones only set; only batches of
  unknown make (queued replays, lines) still do both.
- No full-size copy of the image: `source_down` (a blit straight to the
  pass's size) feeds the bright pass, and the composite screen-blends onto
  the window (`blend screen`; unlit pixels are discarded, so they keep their
  exact value). The debug split/bloom-only views go with the copy.

M4, R4 race, render thread + reprojection, PSX_FRAME_GEN_FORCE, a host
loaded by other work (absolute rates low): bloom pass on real / generated
frames 1.2 / 0.7 ms at 1080p, 1.3 / 0.9 ms at 1440p, 2.6 / 1.6 ms at 4K
(glFinish-bracketed); generated frames with bloom 79-80% of bloom-off at
all three (was ~30%). A 1/8-resolution blur chain was tried and kept no
more frames, so the 1/4 chain (the look) stays.

Tests: parser (blend, source_down, bloom preset reads no full copy), GL
fixture (source_down + blend screen chain exact; HUD and mask checks
unchanged), FXAA unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:03:35 -07:00
Shokunin de8d286180 Dithering: native-grid pattern is the default, internal-pixel is elective
Review of #584: the default must not make guest-visible VRAM depend on the
internal scale. Mode 1 ("on", default; "scaled" kept as an alias) now
puts the PS1 pattern on the native VRAM grid; mode 2 ("fine") is the
elective per-internal-pixel pattern. gl_dithering_test checks the default
mode's native VRAM is identical at 4x and 1x (equal-colour Gouraud now
averages 15.50 in the default at every scale). Docs keep the
semi-transparent approximation explicit. OpenBIOS stamp regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:18:29 -07:00
Shokunin abbae48947 Dithering on by default with explicit per-primitive eligibility
Maintainer review of #584: PS1 dithering is hardware behaviour, so it is
now on by default whenever the guest sets GP0(E1h) bit 9 ([video]
dithering = "on"; "scaled" stays opt-in, "off" is the old true-colour
output).

Eligibility is decided per primitive from its command and carried in the
vertex instead of being inferred from the colour:
- Gouraud polygons and shaded lines: geometry vertex a_col.a + 2*131072
  (decoded in GEO_VS, v_dith);
- texture-modulated polygons: a_semi + 8 (v_dith in TEX_VS);
- flat polygons/lines, raw-texture polygons and every rectangle: never.
This fixes the two reproduced bugs (Gouraud with 5-bit-exact colours was
skipped; textured rectangles were dithered). Bit changes no longer flush:
eligibility is fixed when the primitive is queued; mode changes still drain.

gl_dithering_test covers each case (Gouraud incl. equal colours, shaded
line, modulated and neutral-modulated textured polys dithered; flat tri,
flat rect, flat line, raw poly, modulated and raw textured rects never;
bit clear and off unchanged), plus scaled 4x == 1x, render thread, PGXP
depth, native-wide and the accurate-blending basis. OpenBIOS stamp
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:18:29 -07:00
Shokunin 023af6d9d4 GL: opt-in PS1 dithering ([video] dithering = off|on|scaled)
The OpenGL renderer has always drawn shaded and texture-modulated pixels
at 8 bits per channel with no dither pattern (true colour). That stays the
default. 'on' applies the PS1 4x4 offset matrix and 5-bit truncation per
internal-resolution pixel; 'scaled' puts the pattern on the native grid,
so it reads like the console at any scale and the native VRAM is the same
at every internal resolution.

GP0(E1h) bit 9 reaches the backend through a new optional facade hook
(gr_set_dither) recorded for the render thread and Smooth motion redraws.
With the mode off nothing flushes and no uniform changes. Flat, raw and
other 5-bit-exact pixels are never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:18:29 -07:00
Shokunin 8eccea1612 post effects: no per-frame GL queries; CRLF presets tested
- Uniform locations are looked up once per program (each lookup can be a
  driver round trip on a threaded GL; the executor runs per present,
  generated frames included).
- State save queries only capability flags and the viewport. The mask pass
  no longer clears (it draws 0 then 1), so the clear colour is never
  touched; the present's window target and texture unit 0 are left bound.
  The GL fixture checks exactly that contract.
- postfx_chain_test: a preset with CRLF line ends parses the same, and the
  import check writes its shader in binary mode (it failed on Windows).
- docs/POST_EFFECTS.md: `generated reuse`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin 74dadc6004 GL: one post-effect pipeline; bloom and FXAA as built-in chains, user chains via [video] post_shader
Post effects now run as chains of full-screen fragment passes on one
executor (gl_postfx.inc), as asked in review. A chain is a small .pfx preset
(postfx_chain.c, GL-free): passes with a shader (a file next to the preset or
builtin:<name>), inputs (source, the optional primitive-derived mask,
previous or a named earlier pass), a scale, format, filter and uniforms.
The executor owns target allocation and lifetime, program build/cache,
uniform binding (u_rcp, u_size, u_org) and GL state save/restore.

Stages, in order, each from a fresh copy of the image: the built-in "bloom"
chain ([video] bloom), a user chain ([video] post_shader / PSX_POST_SHADER,
elective, off by default), and the built-in "fxaa"/"fxaa_hq" chain
([video] antialiasing_mode). FXAA folds in as a one-pass chain and its
output is byte-identical to the former fixed pass; it still runs on real
frames only. Generated (Smooth motion) frames run bloom and the user chain.

The "may glow" mask is an optional pipeline input: draws keep stencil bit 1
only while an active chain reads `mask`, and the window-sized mask texture is
built once per present for the chains that use it.

Tests: postfx_chain_test (format, built-ins, errors, file import, no GL);
gl_bloom_test gains an imported two-pass chain (byte-exact invert), the mask
input (HUD unlit, 3D lit), a bad preset, a pass that does not compile, and GL
state restored with bloom + a mask chain + FXAA on, all on the caller thread
and the render thread. docs/POST_EFFECTS.md documents the format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin e730e03a8a GL: opt-in bloom ([video] bloom), sourced from 3D and additive primitives only
[video] bloom = 0.0 (off, default) .. 2.0 adds a glow around lights on the
OpenGL backend. PSX_BLOOM overrides it for one run; {"cmd":"bloom"} reads or
changes strength, threshold and a debug view live. It is a plain [video] key,
so [quality.*] presets can switch it off on low-end machines.

The HUD never glows: with bloom on every textured and untextured draw is
followed by two stencil-only passes of the same vertices that keep a
"may glow" mark in stencil bit 1 (bit 0 stays the mask-bit mirror). Opaque
primitives clear it under their coverage, then PGXP-proven 3D primitives and
additive semi-transparent texels/primitives (modes 1 and 3) set it, painter
ordered like the colour; 3D and non-3D primitives never share a batch while
bloom is on. Semi-transparent primitives leave the mark alone and the PGXP
depth-clear pass (colour writes off) marks nothing. Without PGXP geometry only
additive primitives glow.

The pass runs on the composed game image before AA and the OSD: the presented
surface's marks are blitted into a quarter-resolution stencil, a soft-knee
bright pass runs under it, three separable Gaussian iterations spread it and a
screen blend adds it back. The marks follow the picture: native-wide centre
copies and Smooth motion surfaces (redraw and reprojection) copy stencil with
colour, and generated frames get the same pass. Off: nothing runs, no state
changes, and the composed frame is byte-identical to master.

test_gl_bloom (gl_bloom_test): off is byte-identical before/after bloom and
with --dump against master; on, a bright 2D panel and a panel over a lit 3D
polygon are byte-identical to off, an additive and a 3D polygon glow; guest
VRAM is unchanged; on this thread and on the render thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin d0156d7416 Game Mode docs: SteamDeck=1 also covers Steam games in the Deck's Desktop Mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:51:59 -07:00
Shokunin f6c86b01cd Game Mode: keep only the gamescope signals and SteamDeck=1
SteamGamepadUI=1 is also set when Steam launches from Big Picture on an
ordinary desktop, which is broader than Steam Deck / gamescope. Detection is
now XDG_CURRENT_DESKTOP=gamescope, GAMESCOPE_WAYLAND_DISPLAY or SteamDeck=1
(plus --game-mode / PSX_GAME_MODE); game_mode_test and docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:51:59 -07:00
Shokunin 477bb188b4 Game Mode: Steam Deck / gamescope boots straight into the game, fullscreen
Detected from a gamescope session (XDG_CURRENT_DESKTOP, GAMESCOPE_WAYLAND_DISPLAY)
or Steam's gamepad UI (SteamGamepadUI=1); --game-mode / --no-game-mode and
PSX_GAME_MODE override. When on and a disc is set up, the launcher is
skipped and the game opens borderless fullscreen for that run (the saved
Fullscreen choice is untouched); the first run still shows the launcher so
the disc can be picked. Desktop sessions, --headless and --hidden-window are
unchanged. game_mode_test covers detection and precedence; docs/GAME_MODE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:51:59 -07:00
Shokunin 2d3e3f69b5 JogCon FFB: provider-neutral setting plumbing
The settings field lives in recomp-launcher's header (R4's provider); the
runtime reads it only behind RECOMP_LAUNCHER_HAS_JOGCON_FORCE_FEEDBACK, so a
recomp-ui build compiles unchanged. PSX_HAS_JOGCON_FFB_PLAYER_SETTING lets
the in-game overlay's apply_settings call the live hook without depending on
this PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:07:11 -07:00
Shokunin 2d47dabfa2 JogCon FFB: player setting, off by default, live-applicable
Owner decision: wheel force feedback is an optional player setting, off by
default. settings.toml [controller] jogcon_force_feedback (UserSettings,
load + save) overrides the title's game.toml
experimental_jogcon_force_feedback default. psx_jogcon_ffb_set_player_enabled
applies it live (off -> every wheel neutral now); the launcher's boot and
lobby soft-return exits seed and read RecompLauncherCSettings
.jogcon_force_feedback (RECOMP_LAUNCHER_HAS_JOGCON_FORCE_FEEDBACK,
RetroPortingToolKit/recomp-ui#95) and persist it. Tests:
jogcon_ffb_settings_test (default off, on, explicit off, round-trip);
jogcon_ffb_test (setting off: no haptic calls; toggled on: the hold drives;
off live: neutral).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:02:17 -07:00
Shokunin 05931590a3 JogCon FFB: experimental opt-in, neutral on pause / disconnect / end
Review (mstan): the title key is now experimental_jogcon_force_feedback(
_invert) and the runtime logs it as EXPERIMENTAL. Force feedback goes through
a pure driver (psx_jogcon_ffb.h) and an SDL haptic backend
(psx_jogcon_ffb_sdl.cpp): every force has a 120 ms lifetime renewed each
VBlank, so a pause, stall or crash goes slack by itself, and the Rewind and
save-state pause loops, disconnect (close_player) and session end
(close_controller) stop it explicitly. Tests: jogcon_ffb_test drives the
driver against a recording backend (lifetime, renew, stop once, neutral,
idempotence, disable mid-force); jogcon_ffb_sdl_test runs the SDL backend
against an SDL3 virtual wheel. SDL has no virtual haptic device, so it checks
that such a device is not driven, never falls back to rumble and closes
cleanly. Also fixes the SDL3 haptic subsystem init check (psx_sdl.h returns
0 on success).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:44:05 -07:00
Shokunin 1bc74b8190 host_output hooks observe a CPU copy; guest-mutating services refuse
Review (mstan): a host-output callback could change the shared CPU (GPR2 set
to 0x12345678 survived an online psx_mod_function_entry). Each host-output
callback now gets a private CPUState copy whose changes are discarded, and
while one runs (psx_mod_host_output_scope) every guest-mutating mod service
refuses: memory writes, host_write_ram, call_guest*, DMA / SPU / VRAM
uploads, guest and GPU-DMA allocations, texture packet memory, the
cycle-scale gate and finish_function. netplay_view_mods_test reproduces the
review case (GPR2 + PC changed in the callback) and checks the shared CPU is
byte-identical after the hook; it fails without the copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:40:27 -07:00
Shokunin 13d59b7a39 Mod plugins: netplay = "host_output" (read-only hooks stay on online)
A plugin that only reads guest state to drive this player's host output
(rumble, haptics) can now declare netplay = "host_output". Online it stays
on per player: its function-entry hooks run in the shared simulation while
the rest of the own-view plan is parked, a completion request is ignored and
every psx_mod_write_* it attempts is refused, so it can never diverge peers.
Offline it is an ordinary plugin. netplay_view_mods_test covers both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:38:35 -07:00
Matthew Stanley 7dfef44598 Reconcile current master with validated X4 co-op support for PR23 playtest 2026-10-10 20:49:35 -07:00