Commit Graph

164 Commits

Author SHA1 Message Date
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
Matthew Stanley 2163035e9a netplay local viewport: projection renderer, hi-res half present, state gate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 16:02:08 -07:00
Matthew Stanley 85c88d269c widescreen: auto_ui_anchor = "in_place" squashes each HUD run about its own centre
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:13:30 -07:00
Matthew Stanley 7c7133fbb4 Integrate MMX6 adaptive renderer with current framework master
Preserve current segmented dispatch, active-plan mod hooks and software scale limits. Extend function filters through the active plan, retain the last-store pointer with overlay ABI 25, and keep default config fingerprints stable. Refresh BIOS emitter stamps after byte-identical regeneration and reconcile regression fixtures.
2026-09-30 20:23:16 -07:00
Alexandros Mandravillis 5ef2bf04fd launcher: save paths inside the game folder relative to it
settings.toml [bios]/[disc]/[memcard] and the disc.cfg / bios.cfg
sidecars were written as absolute paths. A portable game folder therefore
broke once it was moved or copied to another PC: the launcher asked for
the disc and BIOS again, and saves were not found.

Paths inside the folder that holds settings.toml (the exe directory) are
now written relative to it. Paths outside it (another drive, a network
share) stay absolute. Readers already resolve relative paths against the
exe directory (previous commit), so the folder works wherever it lands.

portable_settings_paths_test checks that a path inside the settings folder
is written relative, a path outside stays absolute, and the relative form
reads back unchanged.
2026-09-30 22:26:00 +03:00
Cyanoxide 4aa72128fa Make the depth24 trailing margin configurable per title 2026-09-29 20:00:35 +01:00
Matthew Stanley 2bf4739f44 Merge master into feat/internal-resolution-settings: regenerate TCP_COMMANDS.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:22:53 -07:00
Matt Stanley f8a6f4f185 Merge pull request #425 from RetroPortingToolKit/feat/internal-resolution-gl
Raise the GL internal-resolution ceiling to the driver's limits
2026-09-29 11:22:28 -07:00
Shokunin f34f91cc25 Settings: Internal resolution presets (Native ... 8K, Match display)
Add a player-facing internal resolution on top of the raised GL ceiling.
A preset is a target height; the runtime renders at the integer scale
S = ceil(target / reference_lines), reference 240 by default: 720p 3x,
1080p 5x (area-resolved to 1080), 1440p 6x, 4K 9x, 5K 12x, 8K 18x. The
backend then clamps S to what it can allocate.

- runtime/include/internal_resolution.h: the preset table, stable ids,
  the value encoding shared with recomp-ui (0 unset, 1 native, -1 match
  display, N target lines) and the pure resolver.
- config: [video] internal_resolution (a preset id or a number of lines)
  and resolution_reference_lines in game.toml; internal_resolution in
  settings.toml, written as a stable id beside supersampling = min(S, 4)
  for older runtimes. Precedence: game default < player's legacy factor <
  player's preset. settings.toml window_width now accepts up to 7680.
- main.cpp: resolve the preset before gpu_init; Match display re-resolves
  from the game window's monitor (SDL3 mode height x pixel density) before
  the GL context allocates. The launcher gets the vocabulary (with a
  synthesized "Nx (L lines)" entry for a legacy factor no preset matches)
  behind RECOMP_LAUNCHER_HAS_INTERNAL_RESOLUTION, on first boot and on the
  netplay soft-return.
- The OpenGL game window gets SDL_WINDOW_HIGH_PIXEL_DENSITY (SDL2:
  ALLOW_HIGHDPI) only when the player chose more than native, so the
  drawable is in pixels on Retina; native keeps the window as it was.
- debug server: video_info (preset, requested and effective scale, GL
  limit and clamp, hr surface, window points vs pixels), screenshot_hires
  reads the GL hr FBO at full size, screenshot_wide_hires captures the
  native-wide band at internal resolution.
- docs: config_schema (keys, precedence), NETPLAY (a per-peer presentation
  setting), TCP_COMMANDS (index regenerated), ENHANCEMENTS IR1.

Default-off: with no internal_resolution anywhere the scale, the window
flags and the present path are unchanged.

Tests: internal_resolution_test (presets x reference 240/480 x backend
maxima, legacy mapping round trip), video_enhancement_settings_test (game
and settings keys, stable-id round trip, legacy key still written),
gl_internal_scale_guards (HiDPI only when opted in, launcher fields behind
the capability macro).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:52:23 -07:00
Shokunin eee07fe910 GL: raise the internal-resolution ceiling to the driver's limits
The OpenGL backend capped supersampling at 4, the software mirror's limit,
although its hr surface lives on the GPU. Raise the compile-time ceiling to
32 (gpu_render.h) and decide the real scale at context init:

- Query GL_MAX_TEXTURE_SIZE, GL_MAX_RENDERBUFFER_SIZE and
  GL_MAX_VIEWPORT_DIMS and clamp S so the 1024*S x 512*S surface fits them
  and a 2 GiB memory budget (PSX_GL_VRAM_BUDGET_MB overrides it). Apple's
  GL on Metal reports 16384, so 16x is the most a full-VRAM surface can be.
  The clamp is logged with the limit that applied.
- If the surface still fails to allocate, retry one scale lower inside GL.
  Only a 1x failure drops the backend to software, as before.
- Draw lines above 1x as quads one native pixel thick covering both
  endpoints. glLineWidth(S) is capped at 1 on core profiles, so every line
  was one hr pixel thick (1/9 of native at 9x).
- Present a supersampled source with an area resolve when it is more than
  1.25x the output (1200 lines into a 1080 window, 480i menus), and inset the
  UV rect by half a texel instead of half a native pixel, which cropped and
  zoomed the image by (S-1)/2 texels per edge.
- Size the copy scratch to the largest copy (copies stage at its origin) and
  rebuild the mask stencil in tiles, only over the primitive bbox union, so a
  large S does not cost another full-size surface.
- Refuse a native-wide surface wider than the driver limit (uncapped Fit at
  a high scale) with one log line; the frame presents 4:3.
- main.cpp: the offline and dual-raster clamps no longer cap GL at the
  software 4x. The CPU present path presents GL frames at 1x (its readout is
  native) and its staging buffer stays sized for at most 4x.
- [video] supersampling accepts 1..32 in game.toml and settings.toml; the
  runtime clamps per backend.

Every change is gated on S > 1: at 1x the renderer is byte-identical (same
native VRAM and presented framebuffer digests as the previous source on a
fixed GP0 scene).

Tests: gl_scale_limits_test (clamp arithmetic), gl_internal_scale_guards
(source guards + runner parsing), and gl_scale_invariance_test, a hidden
real-GL run at 1,2,3,5,9x that requires identical guest-visible VRAM at
every scale, one-native-pixel lines at internal resolution, and a 32x
request that stays on GL at the driver's clamp (16x on Apple).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:52:22 -07:00
Shokunin 78bec7b55e Add [widescreen.cull] bgez_sites and clip_edge_x_load_sites
Two generic explicit cull-site kinds for native-wide titles whose screen-X
rejects the existing keys cannot express exactly:

- bgez_sites: `bgez SX, keep` in a signed per-vertex left-edge chain
  (`bgez x0,keep; ...; bltz xN,reject`). Emitted through psx_ws_cull_bgez
  (keep while SX >= -margin). bltz_sites alone still rejects a primitive
  whose first vertices sit in the revealed band; with both kinds the chain
  rejects exactly when every vertex is left of -margin.
- clip_edge_x_load_sites (+ clip_edge_width, default screen_w_imms[0]):
  lh/lhu/lw of a clip-rectangle X bound. psx_ws_clip_edge_x moves a loaded 0
  to -margin and a loaded width to width+margin and leaves interior bounds
  (mirrors, split-screen halves) unchanged. xclip_load_sites is lw-only and
  upper-bound-only, so it cannot express this.

Both are identity at 4:3, emit nothing when empty, and fail main-EXE
generation on an opcode mismatch (overlay variants stay vanilla). They enter
the overlay-cache hash only when non-empty, so every existing title keeps
its hash (pinned in the test against the previous layout).

Interpreter parity: the dirty-RAM interpreter now also honours explicit
bltz_sites, bgez_sites, branch_keep_sites and clip_edge_x_load_sites. Its
explicit site lists are stored sorted (binary-search lookup), the cap rises
from 64 to 256 per key, and an over-long list is logged instead of being
silently truncated.

Shared math lives in runtime/include/ws_cull_edge.h. New ctest
ws_cull_edge_codegen_test covers parsing, hash identity, emitted code,
the opcode guards and the helper math.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:39:26 -07:00
Matthew Stanley 78534deff4 mods: [runtime] hide_hidden_mod_features opts the launcher into never showing hidden features
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:41:47 -07:00
Matthew Stanley ec4c91cd94 runtime: GP0 source key and overlay_region_floor follow the 8 MiB window
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 08:03:58 -07:00
Matthew Stanley 41e92d8a32 Merge current framework master preserving accepted MMX6 widescreen and performance work 2026-09-19 17:49:10 -07:00
Pegasus 324690cb97 Preserve fully qualified Windows network asset paths
(cherry picked from commit 51d5d56a27)
2026-09-14 17:26:32 +03:00
Matthew Stanley 33e3ed78eb Expand lower activation bounds with matching native and runtime semantics 2026-09-12 14:47:47 -07:00
Kevin 3cbe6c1cde kernel-bless: publish install-slot ranges so patched BIOS bodies run native
The BIOS exception handler ran interpreted for the life of every process,
1.22 billion instructions and 44.6% of all interpreted work in Breath of
Fire III, even though it is compiled and even though the stub that
unblessed it sat at exactly the address bios/SCPH1001.toml already
declared as an install slot. Two mechanisms existed and did not compose:
the emitter hooked the declared slot PC, while the runtime's bless
verifier memcmp'd the whole body including that slot, so the body was
MISMATCH forever and native code never reached the hook.

Three parts, none of which pays off alone:

1. Publish the declared ranges to the runtime. The emitter emits a
   PsxKernelPatchRange table next to the kernel body table and routes it
   through the backend descriptor; the bless verifier compares a body in
   segments that skip them. CLEAN now means "every byte outside every
   declared range still matches ROM", which is the claim the native code
   actually depends on.

2. Generalise the slot model to what the patches are. The old model
   assumed a 4-word lui/addiu/jalr/nop stub written over ROM zeros and
   fired on `word != 0`. Measured against both images, only one of five
   observed shapes fits: patchers also plant a `jr` over real ROM
   instructions, rewrite an 11-word prologue and insert an `mfc0 Cause`,
   and NOP out a driver routine. A slot is now a range with a length and
   an explicit `resume` (jalr / fallthrough / none), detected by
   comparing every word against its ROM-baked value.

3. Hand back from the interpreter at the range end. Kernel page 0 is
   permanently dirty, because the handler saves registers there on every
   exception, so straight-line interpretation would run the whole
   function after the patch. The emitter registers the resume PC as a
   continuation key and dirty_ram_interp surfaces there.

A PC inside a declared range must never be a native dispatch key: the
compiled bytes there are not what executes. Seven pre-existing
jal-return continuations violated that and wedged the boot at frame 0 in
an infinite dispatch loop (OpenBIOS RAM 0x357C). The emitter now drops
continuation keys inside a range so dispatch misses through to the
interpreter, and refuses to emit if a range covers a function entry,
which would silently remove a callable function instead.

Both profiles declare their measured ranges, and SCPH1001.toml finally
pins [program.image] sha256 to v2.2 (CRC32 37157331) so a v2.0 dump
refuses to generate rather than producing phantom discovery gaps.

Measured on Breath of Fire III (SLPS-00990, Psy-Q SDK 3.70), headless,
matched frames, PSX_KERNEL_PATCH_RANGES=0 for the OFF side:

                      OpenBIOS            SCPH-1001 v2.2
  bless mismatch      21 -> 0             74 -> 0
  kernel bodies       -87.7%/frame        -97.8%/frame
  all interp insns    -42.6%/frame        -94.2%/frame

The residual kernel-body work is exactly declared-words x entries on
every range, so nothing leaks past the hook. The A0/B0/C0 vector
trampolines are unchanged (+0.2%, noise): the profile excludes them by
design and they are a separate job. Card read traces are byte-identical
across the A/B on both images, all 32 entries, which exercises the two
ranges that replace the kernel's card handler.

Rule 18 holds throughout: the guest's patched instructions still execute
as written on the dirty-RAM interpreter. Only the code around them
changes backend. Nothing is synthesised.

Tests: runtime/tests/test_kernel_patch_ranges.c (new, the verifier's
accept/reject behaviour and the hand-back predicate) and the install-slot
range invariants in recompiler/tests/bios_address_model_test.cpp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:38:34 -04:00
Matthew Stanley 912c44c75e Support native-wide HUD and tiled backgrounds with guarded culling 2026-09-07 22:40:23 -07:00
Kevin 3b8b98fa29 runtime: fast-forward toggle shortcut + document the implicit Select chord
Turbo (Tab / [hotkeys] fast_forward_pad) is hold-to-run only. Add a
press-to-latch twin that feeds the same manual fast-forward block:

- host_keymap: HOST_KEYMAP_TURBO_TOGGLE, [KeyMap] TurboToggle, default F9.
- main.cpp: g_manual_turbo_latched flipped by the key edge or by a fourth
  host shortcut, PSX_ASSIST_BIND_FAST_FORWARD_TOGGLE ("Fast-forward
  toggle", [hotkeys] fast_forward_toggle_pad, unbound by default), polled
  next to Rewind / Save states through hotkey_pad_binding_down(). OSD shows
  "Fast forward: Nx (locked)" / "Fast forward: off". Threaded through every
  settings hop (settings.toml -> globals -> launcher seed -> LauncherSettings
  -> back), mirroring fast_forward_pad.
- config_loader: [hotkeys] fast_forward_toggle_pad read/write.
- README: describe the toggle and state that a single-button / trigger host
  shortcut is an implicit chord with Select (hotkey_pad_binding_down), which
  the launcher now labels as "select + ..." (recomp-ui companion change).
- test_rewind_toggle_combo.py guards the new sites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 21:00:59 -04:00
Kevin 2ae781095a runtime: controller host shortcut for fast-forward ([hotkeys] fast_forward_pad)
Turbo was keyboard-only ([KeyMap] Turbo, Tab). Add a third host shortcut the
launcher can bind to a pad button or chord, next to Rewind and Save states:

- main.cpp: PSX_ASSIST_BIND_FAST_FORWARD + "Fast-forward" label; default
  chord select+L1 (PSX_HOTKEY_PAD_SELECT_L1 = 1528); the manual fast-forward
  block now runs while either the keyboard key or the pad binding is held,
  through the same hotkey_pad_binding_down() matcher the other two use.
  Threaded through every settings hop (settings.toml -> globals -> launcher
  seed -> LauncherSettings -> back), mirroring save_state_menu_pad.
- config_loader: [hotkeys] fast_forward_pad read/write, same pad_bind_value_ok
  bound as the siblings.
- test_rewind_toggle_combo.py guards the new sites; README documents the chord.

Launcher side: recomp-ui draws every advertised host shortcut instead of the
first two (companion PR).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 20:12:21 -04:00
Kevin 92e5f4b2e2 runtime: wire scanlines through the launcher + settings persistence
Threads Settings.scanlines / scanline_strength_pct from the recomp-ui launcher
Display card through the same paths as screen_kind: seed the launcher from the
current state, apply the player's choice back to the video globals (pushing it
to the GL renderer immediately), and persist it to settings.toml. All launcher
struct accesses are gated on RECOMP_LAUNCHER_HAS_SCANLINES so the runtime still
builds against an older recomp-ui.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 09:53:22 -04:00
Kevin 97f58e19d7 runtime: present-time scanline post-process (host display enhancement)
Adds an optional scanline effect to the GL present path: a soft sinusoidal
beam pitched to the PS1 display-line grid (one dark line per native scanline),
applied in both the present and interpolation shaders and gated by output
scale so it never shimmers below ~2x. Orthogonal to the crt_filter colour LUT
(the two compose).

Controls: [video] scanlines / scanline_strength (game.toml + settings.toml),
PSX_SCANLINES / PSX_SCANLINE_STRENGTH env overrides, an F6 hotkey
(HOST_KEYMAP_SCANLINES, rebindable via [KeyMap] Scanlines), and a `scanline`
TCP command for live A/B. Launcher Display-card wiring follows separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 09:53:22 -04:00
Alex Vanderveen 2ac6d181d9 Merge pull request #262 from mstan/master
upstream
2026-08-29 16:44:57 -04:00
Alex Vanderveen d14ca6917c bump 2026-08-29 01:50:12 -04:00
Alex Vanderveen 77095c589c runtime: make local rewind opt-in, and give it a settings key
Rewind was on for everyone and could only be turned off with PSX_REWIND=0 in
the environment. It now defaults off and is enabled from settings.toml
([video] rewind) or the launcher's Display card.

The cost it was charging silently: each snapshot is a whole machine — 2 MB main
RAM + 1 MB VRAM + 512 KB SPU RAM — serialized uncompressed into a buffer
malloc'd at a fixed 5 MiB cap that is never shrunk to the actual length, and one
is captured every rewind_interval frames (15 by default, tightening toward 4
while an FMV runs), each with a 128x96 thumbnail built pixel by pixel. At the
default depth of 50 a full ring is roughly 250 MB resident; at the maximum 200
it approaches 1 GB. That is a lot to hand a low-end host for a feature a session
may never open.

Turning it off genuinely avoids all of it: psx_rewind_configure() returns before
any allocation when rewind is not wanted, and both capture paths are guarded by
psx_rewind_enabled(). The only residue is a 450 KB BSS overlay panel whose pages
stay untouched.

The toggle applies live rather than at next launch — freeing the ring is the
whole point, so waiting for a restart would miss it. psx_rewind_shutdown()
already resets the overlay and drops a pending load, so tearing down mid-session
is safe.

PSX_REWIND still outranks the setting in both directions and now accepts 1 as
well as 0. Netplay rollback is a separate subsystem with its own ring and is
unaffected; rewind was already suppressed during netplay.

Because off is now the common case, psx_rewind_toggle() says so on the OSD
instead of leaving F8 / Select+R3 looking broken. It stays silent only in a
-DPSX_REWIND=OFF build, where there is nothing to enable.

Adds rewind_settings_test (default off, explicit off distinguishable from
absent, save/load round trip, tuning keys independent of the enable) and the
first documentation the feature has had, in config_schema.md.

Verified: recompiler tests build and pass; main.cpp type-checks against the new
recomp-ui ABI field; psx_rewind.c compiles both with and without
PSX_HAS_RBENGINE_SNAP; the existing test_rewind_toggle_combo.py still passes.
Not run end to end — this checkout has no BIOS dump to boot.

Needs recomp-ui a31ecbc for Settings.rewind_enabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017AwqKPQFnK3x7xQbK4PQKj
2026-08-28 17:33:52 -04:00
tetrisgm 5afe95b158 runtime: add title-scoped exact NCLIP branches
(cherry picked from commit 81ebee1346)
2026-08-27 20:44:13 -07:00
Alexandros Mandravillis 699022e7d9 config: validate overlay_region_floor integers at parse time
Raw TOML integers were cast straight to uint32_t and left to the runtime
clamp/mask, so a negative or >32-bit value could wrap or be masked into a
different physical address silently. Reject those at parse time, and require
the (KSEG-stripped) value to name main RAM above the kernel window
(0x10000..0x1FFFFF), matching the fail-loud style of the nearby fields. The
hex-string path already goes through parse_hex and now shares the range check.
2026-08-27 16:25:47 +03:00
Alexandros Mandravillis d596b0ec46 runtime: [runtime] overlay_region_floor config key; bundled toolchain drives gcc when available
Two shipping gaps left Gran Turismo's gameplay and FMV code on the
interpreter (0.55-0.64x real time) even though the recompiled boot EXE was
fine:

1. The overlay region floor defaults to the boot EXE text end. GT's secondary
   EXEs (GTOS/GTMENU/GTMAIN/GTEND) all load at 0x80010000, inside that range,
   so they were never overlay-cache eligible. The only way to lower the floor
   was the PSX_OVERLAY_REGION_FLOOR env var, which a shipped package cannot
   carry. Add `[runtime] overlay_region_floor` (hex string or integer), applied
   before the env override with the same kernel-window clamp.

2. The bundled overlay_toolchain/ was only used to build the tcc command.
   A dev/production box with gcc on PATH but no game.toml autocompile command
   got no compiler at all. Treat the bundle as a gcc toolchain provider too:
   gcc counts as available when the bundle is present and a gcc/cc/clang is
   on PATH, and the same bundle then drives compile_overlays.py with
   --compiler gcc. One package yields gcc shards on a dev box and tcc shards
   on a toolchain-less player box.

Measured on GT1 (SCUS-94194), headless boot -> intro FMV -> attract, with the
floor at 0x10000 and gcc autocompile: 1.27-1.38x real time vs 0.55-0.64x;
SPU production 27 kHz -> 59 kHz (the FMV audio distortion was starvation).

(cherry picked from commit 0d226e5069bdf1395c819110beec40367a44a699)
2026-08-26 21:43:30 +03:00
Matthew Stanley 432a44f64a input: add plugin controller presentation policies
Supersedes the global HYBRID controller-mode shape from PR #233 with a trusted plugin callback that can choose analog or digital presentation per input sample.

Keeps analog mode hardware-faithful by no longer folding physical D-pad input onto the left stick, while leaving debug injection able to steer stick-only paths.

Includes the pad/cursor probe tooling from #233 for diagnosing input doubling.
2026-08-25 20:37:49 -07:00
Codex f64ca0ce3c config: honor title window width 2026-08-25 01:45:55 -07:00
Matthew Stanley 15099cc5b6 presentation: add opt-in FMV filter setting 2026-08-24 20:28:43 -07:00
Alex Vanderveen b8b82b753c Fix the pad-hotkey range check rejecting every chord
[hotkeys] rewind_pad / save_state_menu_pad accept the recomp-ui pad encoding,
where 1000 + a button bitmask is a CHORD. The parse guarded on `n < 256`,
which admits single buttons and rejects every chord there is — including the
two values this same file writes back out (1272 = select+r3, 2040 =
select+r1). A saved chord was silently dropped on load and the defaults
quietly reinstated, so the pad hotkeys were never actually configurable.

The bound predates the chord encoding; the runtime's own
normalize_hotkey_pad_binding has handled combos correctly all along.

Replaced with pad_bind_value_ok(), sharing one documented bound with the
encoding it validates. Verified against a live boot with rewind_pad = 1017
(select+cross): parsed has=0 value=1272 before, has=1 value=1017 after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 165fae0aa1)
2026-08-23 13:07:39 -07:00
Matthew Stanley bdd7ade6e8 Merge remote-tracking branch 'origin/master' into codex/netplay-local-viewport 2026-08-15 21:40:56 -07:00
Matthew Stanley b2eeb0f866 Add opt-in netplay local viewport extraction 2026-08-15 20:51:23 -07:00
Matthew Stanley 7993f412c0 Add opt-in controller device defaults 2026-08-15 20:09:37 -07:00
Matthew Stanley 200b5bf003 Add opt-in widescreen culling keep hooks 2026-08-15 19:42:43 -07:00
Matthew Stanley 2ba74f3b4b pgxp: land clean-room value-propagation engine (phases 0+1, issue #92)
The G1.2/G1.3 mechanism: per-word RAM/scratchpad + per-GPR + per-GTE-reg
shadows carrying sub-pixel projections BY PROVENANCE, validated against
the actual guest word on every read (never guessed from rounded position
- the measured 93.1%-ambiguous G1.4 dead end). Clean-room from psx-spx +
public design docs; vendored duckstation (CC BY-NC-ND) and beetle-psx
(GPL) trees remain black-box oracles only - no code ported.

- runtime/src/pgxp.cpp + pgxp.h: engine, per-vertex GetPreciseVertex
  (dataflow -> ambiguity-gated position cache -> native) with truncation
  agreement + tolerance safeguards; stats; test accessors.
- pgxp_hooks.h: 5-entry hook funnel; PGXP_*() macros no-op unless
  -DPSX_PGXP=1 (codegen-time gate over ONE generated tree); appended
  PGXPHooks* table member in OverlayCallbacks (ABI bump ships with the
  Phase-2 emitter change).
- Both interpreters wired: loads/stores/COP2 + precision-carrying
  arithmetic (moves, add/sub, or-merge, 16-bit shifts, LUI, HI/LO).
- gte.cpp: RTPS feeds the GTE reg shadows; retired s_precise_sxy FIFO,
  hashed s_precision_store, and plain-store invalidation (redundant
  under validate-on-read; store hot path gets 3 calls back).
- gpu.c: prepare_precise_triangle rewritten per-vertex; perspective UVs
  share the same provenance and compose per triangle.
- [video] pgxp_cpu_mode / pgxp_tolerance keys; TCP pgxp verb live-
  toggles geometry/texture/cpu_mode/tolerance; geom_correction census
  grew the pgxp dataflow counters (G1.9 gate metric).
- tests: new pgxp_test (registered); gte_register_access_test updated
  to precise invalidation contract. 33/33 enabled tests green.

Also fixed while landing (both broke a fresh master tree):
- runtime.cmake: once-only psx_game_version.txt stamp (multi-target
  file(GENERATE) collision broke every fresh single-config configure).
- test_overlay_capture_retry: SDL3 rot (SDL_SetMainReady gone, inverted
  SDL_Init check) -> psx_sdl_init.

Tracked as beads-eio.3.48; postmortem + this landing: ENHANCEMENTS.md
G1-G1.10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 18:34:47 -07:00
Matthew Stanley cab7d00f9a Fix PSX launcher hotkeys and audio persistence 2026-08-14 15:22:29 -07:00
Matthew Stanley cee9b97239 Add PSX rewind and save-state controls 2026-08-12 11:12:24 -07:00
Alex Vanderveen 554476457f rewind support 2026-08-12 03:51:50 -04:00
Alex Vanderveen 1c070dfe40 chore: bump recomp-net + retcomm-rbengine 2026-08-12 03:25:31 -04:00
Matthew Stanley f0cfa92afa config: retire the legacy generic turbo_loads switch — load acceleration is mod-only
Load acceleration moved to the Mods catalog in 125d84d
(psx.enhancement.fast-loading "Fast Loading (host pacing)" and
psx.enhancement.cd-speed, both `game_id = "*"` so they ship with every
title, both default-off), and recomp-ui deliberately stopped drawing a
generic Turbo loads row. But the legacy switch was left live here with
`offer_turbo_loads` defaulting TRUE, so any title that had not explicitly
migrated still got turbo forced on — with no UI to turn it back off.

That shipped to players. MegaManX6Recomp v1.0.4 and v1.0.5 both carry
`[runtime] turbo_loads = true` in the game.toml inside the release zip
(verified by extracting the artifacts). Two paths forced it on:

  * game.toml [runtime] turbo_loads set g_turbo_loads_enabled directly;
  * a stale [video] turbo_loads in settings.toml was restored, and the
    seed write persisted it — so one run latched the value permanently
    and no later game.toml change could undo it.

Reported as MegaManX6Recomp#14: the WARNING screen self-advances and load
spinners run fast, with Turbo confirmed off in Mods. Tomba 2 hit the same
defect (fixed per-title), and MMX5 is also unmigrated. Third occurrence of
one config default, so fix the class instead of the titles.

Both keys are still PARSED, so existing configs load without error, but
neither is honoured:

  * offer_turbo_loads defaults false and turbo_loads_offered becomes a
    constexpr false, matching how ws_offered / skip_fmv_offered /
    frame_interpolation_offered already model mod-owned features. No title
    can offer the generic switch, so forgetting to migrate is no longer
    punished.
  * game.toml turbo_loads logs a deprecation line naming the Fast Loading
    mod and enables nothing.
  * settings.toml [video] turbo_loads is neither restored nor written back
    out, so the stale row is dropped on the first save — same write-only
    latch reasoning already documented for fast_boot, which bit harder here
    because there was no UI control at all.

Also fixes a related hole found while auditing: the in-game Settings apply
path wrote both g_turbo_loads_enabled and g_auto_skip_fmv from a launcher
snapshot taken BEFORE mod_runtime_activate_plugins() ran, so the first
in-game Apply silently turned a player's enabled Fast Loading or Tweaks FMV
feature back off. Both are now gated on their offered flags.

The supported surfaces are unchanged: the Fast Loading / CD Speed mod
plugins still enable acceleration (verified: "mod selected 8x load
acceleration"), and the `turbo_loads` TCP debug command still toggles it
for development.

Verified against MMX6 built on this framework: with game.toml
`turbo_loads = true` AND `offer_turbo_loads = true` AND a stale
settings.toml `turbo_loads = true`, both deprecation lines fire and the
debug server reports enabled=0, turbo_frames=0. With the keys removed and
no mods enabled, it boots silently with enabled=0.

test_mod_load_acceleration.py is updated to pin the new contract; it also
had a stale assertion on the old 2..16 multiplier range, which is now
pinned to the symbolic PSX_MOD_LOAD_ACCEL_MAX bound. The guard-test suite
goes from 9 pre-existing failures to 8 (no new failures; this one fixed).

Refs: beads-eio.3.26, MegaManX6Recomp#14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 18:52:16 -07:00
Matthew Stanley 20a8f4ad08 input: make Hybrid a mod-only mode, not a selectable one
Hybrid kept reappearing in titles that never meant to ship it, because it was
the ZERO value of PadMode and therefore the default everywhere: PlayerInput,
p1_mode/p2_mode, ctrl_locked_*, and default_p1/p2_mode all initialised to it.
A game then had to opt OUT with [controller] allow_hybrid = false, and every
shipping title did exactly that or pinned a mode - so no title actually ran it.

Hybrid now has exactly one route: a trusted game plugin calling
psx_mod_set_controller_mode_override(), which is how Tomba's hybrid controller
mod already requests it. Everything that made it reachable by accident is gone:

  - defaults are ANALOG (PlayerInput, p1/p2, ctrl_locked_*, default_p1/p2)
  - [controller] allow_hybrid is removed, along with the startup clamp that
    existed only to undo the bad default
  - game.toml default_mode/p1_mode/p2_mode = hybrid now THROWS, naming the
    mod API as the only route, rather than being silently coerced
  - a stale settings.toml hybrid migrates to analog via
    pad_mode_from_settings_string(); a user should not fail to launch over a
    value they never typed
  - pad_mode_to_string() never writes hybrid back

Deliberately KEPT, because the mod runs on it: PAD_MODE_HYBRID,
PSX_MOD_CONTROLLER_HYBRID, psx_mod_set_controller_mode_override(),
hybrid_stick_active/hybrid_dpad_active, PlayerInput::hybrid_analog and
psx_keybinds_dpad_active.

Paired with the recomp-ui change that drops the Hybrid segment and the
allow_hybrid field from RecompLauncherCGameInfo - the two must land together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:06:13 -07:00
Alex Vanderveen 4cdccf58c1 multitap enhancements 2026-08-07 10:19:12 -04:00
Alex Vanderveen 94cb3807a9 Merge origin/master into integrate/game-codegen-hooks-into-rollback
Keep rollback/netplay multiplayer routing, dual-raster GL present, and TOC
fingerprints while taking master's CHD support, mod load acceleration,
adaptive widescreen, and geometry/perspective APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 22:50:38 -04:00
Alex Vanderveen 4073e2311c rollback + fmv + connectivity 2026-08-06 20:34:50 -04:00
Alex Vanderveen 652be7ba02 rollback FMV patches and overlay 2026-08-06 00:52:14 -04:00
Matthew Stanley c8b692eda2 feat(video): opt-in sub-pixel vertex precision + perspective-correct textures
Makes the parked projection/perspective work reachable and gets it into the
renderers people actually use. Addresses issue #92 (PGXP-style geometry and
texture correction).

The GTE side cache, SWC2 RAM provenance, GP0 triangle preparation and the
software-renderer consumption path already landed in 2ceaf5a ("park projection
and perspective correction", from kem0x PR #14), but were unreachable: the
generic setters gte_geometry_correction_set() / gpu_texture_correction_set()
had no production caller, and the corrected vertices went straight to
sw_set_precise_triangle(), bypassing the renderer vtable — while the default
renderer is OpenGL.

This adds:

- [video] geometry_correction and [video] perspective_texturing, in game.toml
  and settings.toml, both defaulting false. Independent knobs; the launcher
  seed marks them present so a save round-trips a hand-edited key instead of
  dropping it. Startup names the active pair, and notes when geometry
  correction is on below supersampling 2 (where it cannot be seen).
- gr_set_precise_triangle() / gr_set_perspective_triangle() on the
  GpuRenderBackend vtable, so gpu.c hands the correction to whichever backend
  is live rather than to the software rasterizer directly. NULL entries no-op.
- OpenGL and Vulkan consumption. Positions are already float end-to-end, so
  sub-pixel vertices drop in; the readback dirty rect widens by one pixel to
  cover them. Perspective UVs use a per-vertex a_q weight and clip coordinates
  pre-multiplied by w = 1/q, letting the hardware's own perspective divide
  interpolate a smooth UV varying beside the affine noperspective one. a_q == 0
  — the default, and what every prim gets with the feature off — makes w
  exactly 1.0 and selects the affine varying, so the faithful path is the
  pre-feature pipeline unchanged.

The PS1-visible GTE SXY FIFO stays integer either way: guest-side screen-bounds
culling and SXY readback are untouched, and this remains visual-only.

Also fixes a pre-existing concurrent-build race found while validating: every
runtime target runs embed_spirv.py over the same shaders, and each wrote
"<shader>.spv" next to the source, so under -j N one process deleted the file
another had just written (FileNotFoundError on a shader that compiled fine).
The intermediates now go to a per-invocation temp dir.

Parked-work provenance retained; see ENHANCEMENTS.md G1.

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:02:58 -07:00
Matthew Stanley 67757efac4 feat(runtime): add CHD discs and mod-owned loading modes 2026-08-03 22:32:17 -07:00
Matthew Stanley b2af4e89c9 feat(mods): add trusted game enhancement hooks 2026-08-03 15:17:39 -07:00