107 Commits

Author SHA1 Message Date
Matthew Stanley 4734f69306 Internal resolution becomes a built-in host-only mod; drop the sample-rate setting; host-only mod options editable in netplay
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 20:29:37 -07:00
Matthew Stanley 7a786f8e84 Move PSX enhancement settings to built-in host-only mods; remove Graphics presets
CRT Filter, Image Filtering, Enhanced Audio and Rewind become default-off mods (netplay = host_only,
outside the plan fingerprint); Internal resolution gains Auto in place of dynamic resolution.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 19:23:40 -07:00
Matthew Stanley 4fca2d27d8 Allow explicit world axes for widescreen visibility cones
(cherry picked from commit 2c6e0a3a20)
2026-10-24 15:24:20 -07:00
Matthew Stanley 42ddce61b5 Preserve wide backdrop composites and guard GTE flag rejection
(cherry picked from commit 6b6402e7f0)
2026-10-24 15:15:46 -07:00
Matthew Stanley 6f37a0edee Widen guarded packed screen-X predicates in native and interpreted code
(cherry picked from commit b0f7b2be8e)
2026-10-24 15:15:42 -07:00
Aliet Exposito Garcia 1c4731bb14 feat: add configurable widescreen sxy culling support
Some PlayStation 1 titles implement trivial rejection by folding the sign bits of projected vertex screen coordinates across triangle or quad primitives with a chain of AND instructions. Without adjusting these checks during widescreen presentation, geometry in the newly revealed horizontal area is still rejected by the original 4:3 bounds. Rather than embedding title specific instruction patterns directly into the recompiler, this introduces a generalized `[[widescreen.cull.sxy]]` configuration schema within `game.toml`.

Game configurations can now declare multi vertex trivial rejection sequences by their exact AND instruction words, vertex registers and the branch that consumes the result. The loader derives the expected chain from those words and rejects any other shape. Each AND keeps its original value except for the horizontal sign bit, which survives only while that vertex lies beyond the revealed left edge, so vertical and right side rejection are preserved and 4:3 output is bit identical. Shifted coordinate lower edge comparisons reuse the existing `screen_x_sites` handling through a new `slti rt, x, 0` form instead of adding a separate site family.

Full parity is maintained between recompiled code and the dirty RAM interpreter by sharing the same runtime helper and validating every fold and consumer instruction word on both paths, leaving mismatching overlay variants untouched. This eliminates hardcoded game logic from the recompiler while giving titles clean geometry retention in expanded aspect ratios.
2026-10-24 12:28:38 -07:00
Shokunin 74c7cbf94a [video] supersample_disable_at_lines: drop SSAA on 4K/8K displays (opt-in) 2026-10-22 15:26:47 -07:00
Shokunin 44a51191d9 [video] match_display_max_lines: opt-in cap on Match display height (#722) 2026-10-21 14:45:27 -07:00
Matt Stanley 13b7f81d29 Add guarded instruction variants fixed during offline mod activation
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.
2026-10-19 16:37:32 -07: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
Matthew Stanley d110d0a423 Integrate current master for reviewed PR597 and retain canonical BIOS stamp 2026-10-15 00:36:07 -07:00
Matthew Stanley dfd4555d09 Include fixed scaled-charge batching profile in netplay compatibility 2026-10-14 23:51:49 -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 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
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 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 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
Matthew Stanley 1dd2afec07 Merge master and enable PS1 blend correction by default 2026-10-09 23:55:29 -07:00
Matt Stanley eb4c76fc47 Merge pull request #586 from RetroPortingToolKit/feat/gl-xbr-filter
GL: opt-in xBR edge-directed texture filter (texture_filtering = "xbr")
2026-10-09 23:19:31 -07:00
Shokunin 70c735944b GL accurate blending: unorm8-exact blend constants (NVIDIA), regen OpenBIOS stamp
NVIDIA quantises the constant blend colour to the 8-bit target (0.5 ->
127/255), putting B/2+F/2 and B+F/4 one step under a k*8 boundary, so
gl_blend_accuracy_test failed there (2016 mismatches). With accurate
blending on, use 128/255 and 64/255: exact in unorm8, and the small upward
bias stays inside the PS1 truncation bucket for every 5-bit operand, so
fixed-point and float blenders agree. Passes on Apple M-series GL and an
RTX 4080 SUPER (NVIDIA 610.88). Default path unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:18:36 -07:00
Shokunin 0e3c53720b GL: opt-in accurate blending + PS1 blend/mask accuracy test
Verifies the four PS1 semi-transparency modes, set-mask, mask check and
texel STP at 4x internal resolution with PGXP depth on and off, directly
and on the render thread, against the PS1 formulas and the software
rasterizer (new gl_blend_accuracy_test).

Finding: with the historical surface bases (fills and texels stored as
k*255/31, everything else as k*8) single-layer blends are off by one 5-bit
step in places (1312 of the test's checked pixels, e.g. B+F/4 over a fill).
[video] accurate_blending = true stores every VRAM write as k*8 so GL's
8-bit blend truncated by the readback is exactly the PS1 arithmetic; whites
of fills/texels then present as 248 like uploads already do. Default off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:18:16 -07:00
Shokunin b55d92124d GL: opt-in xBR edge-directed texture filter (texture_filtering = "xbr")
Filter mode 3 magnifies textures with an edge-directed rule after Hyllian's
xBR (written from the published description, no code reused): for the
texel corner nearest a sample, weighted YUV colour distances over a 5x5
neighbourhood decide whether an edge runs along the corner's diagonal; if
so the corner is cut with an anti-aliased boundary and takes the closer
side neighbour. Flat areas, straight edges, gradients, cutouts and 1:1 draws
stay nearest. Selectable from [video] texture_filtering, settings.toml,
PSX_TEXTURE_FILTER and the new texture_filter TCP command. Default unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:17:27 -07:00
Shokunin ee56f79087 Mod API: title-supplied host rumble (psx_mod_set_host_rumble)
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>
2026-10-08 23:25:25 -07:00
Matthew Stanley 50747ae16d Merge PR #585: GL opt-in texture LOD + anisotropic filtering
# Conflicts:
#	docs/TCP_COMMANDS.md
#	docs/config_schema.md
#	generated/OpenBIOS.emitter.sha
#	recompiler/src/config_loader.cpp
#	recompiler/src/config_loader.h
#	runtime/CMakeLists.txt
#	runtime/src/debug_server.c
#	runtime/src/main.cpp
2026-10-08 23:04:04 -07:00
Shokunin ab13278bdf GL: opt-in FMV chroma smoothing for 24-bit MDEC frames
[video] fmv_chroma_smoothing = true makes the OpenGL present of a 24-bit
(MDEC) frame keep each sample's luma and take its chroma from a [1 2 1]
tent over the 3x3 source pixels, hiding the 2x2 colour blocks of 4:2:0
video without softening luma. Pairs with the existing fmv_filter upscale.
Guest VRAM/MDEC output untouched; 15-bit presents and the default unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:37:27 -07:00
Shokunin cf756f0b2d GL: opt-in texture LOD + anisotropic filtering for world textures
[video] texture_lod = "mipmap" emulates mip levels for PS1 texture pages,
which are decoded per fetch from VRAM and so have no GL mip chain: the
screen-space UV footprint of a minified, proven-3D primitive is box-averaged
(opaque texels of the fragment's STP class only), and anisotropic_filtering
(1..16) spreads that many taps along the footprint's major axis. Every tap
clamps to the primitive's UV bounds or texture window, so packed atlases
never bleed. The mode rides in the textured batch's filter key; off (the
default), sprites/HUD and magnified textures are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:01:07 -07:00
Shokunin 7d06942eb0 Dynamic resolution floor "display": the output's own lines
dynamic_resolution_min = "display" (game.toml, settings.toml,
PSX_DYNRES_MIN) floors the controller at the selected internal resolution
without the supersample factor, so with supersample > 1 it only trades
supersampling and never renders below the output. psx_dynres_floor_scale
in internal_resolution.h, tested in test_internal_resolution. OpenBIOS
emitter stamp regenerated (config_loader changed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:40:17 -07:00
Shokunin c061ea23ea docs: measured FXAA cost, HUD note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:40:10 -07:00
Shokunin a090a4b83f docs: antialiasing_mode, supersample and the post_aa command
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:40:10 -07:00
Shokunin 91cd6db705 guest_cycle_scale: one CPU charge for every tier, declarative gate, rollback
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>
2026-10-07 21:41:39 -07:00
Shokunin 1e6e43639a Title timing constant: [timing] guest_cycle_scale (and _gated)
No CPU is emulated: recompiled code charges the guest clock a fixed cost
per instruction. guest_cycle_scale = N charges 1/N of it (instruction,
load, mul/div, GTE latencies, overlay DLL batches) while VBlank, timers,
CD, SPU and DMA keep hardware time, so the game never runs out of frame
time. A title constant in game.toml (1 = faithful, 1..64), never a player
setting; guest_cycle_scale_gated applies it only while the title's mod
opens the gate (psx_guest_cycle_scale_gate_open). PSX_GUEST_CYCLE_SCALE
overrides for testing. A scale other than 1 is folded into the netplay
content fingerprint as a safety net.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:41:34 -07:00
Shokunin 46f2abebcf Docs: render thread design, sync points, interoperation, tests
docs/RENDER_THREAD.md: the record/replay model, why guest-visible VRAM
stays gpu.c's array (private upload source on the render thread), the
guest state captured at record time, every sync point with file:line,
the frame bound and present skipping, interoperation with savestates,
netplay, render passes, interpolation, widescreen, resolution changes,
screenshots, headless and Vulkan, what is out of scope, and the tests.
TCP_COMMANDS.md documents render_thread (index regenerated);
config_schema.md documents [video] render_thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:53 -07:00
Shokunin 812465df58 Let a title block local Rewind (psx_mod_set_rewind_blocked)
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>
2026-10-05 23:44:18 -07:00
Matthew Stanley 6674024285 Merge master into feat/medievil-native-wide-guards
Reconciled with #470 (PGXP): gpu.c precise vertices take PGXP only under geometry correction and count it, then native-wide edge recovery; the PGXP triangle stat stays PGXP-only. Stubs and tests from both kept; stamps recomputed.
2026-10-02 20:00:23 -07:00
Matthew Stanley 30a4072c25 Merge master into feat/max-detail
Stamps recomputed with tools/bios_emitter_fingerprint.cmake after #480's restamp.
2026-10-02 19:22:24 -07:00
Matt Stanley 921d0b021d Merge pull request #467 from RetroPortingToolKit/feat/vs-split
GL: texture window per vertex; opt-in batching across texture windows
2026-10-02 19:20:52 -07:00
Matt Stanley 0347edd577 Merge pull request #455 from Alexbeav/up/settings-paths-relative
runtime: keep a portable game folder portable (relative settings paths)
2026-10-02 19:20:44 -07:00