Commit Graph

884 Commits

Author SHA1 Message Date
Alex Vanderveen 9482615b29 Pass lobby occupied_mask into recomp-net for sparse seats.
Bumps recomp-net so empty lobby seats no longer block READY/admit; launch
builds the mask from seated members (online + LAN).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 22:37:08 -04:00
Alex Vanderveen ae9e6a4e2b hotfix build error on ci 2026-08-12 22:00:46 -04:00
Alex Vanderveen d5b5a029fe hotfix ci template 2026-08-12 21:53:26 -04:00
Alex Vanderveen c4f412be17 project studio, harden prefetch sdl3 2026-08-12 16:49:03 -04:00
Alex Vanderveen a42dd63b61 overlay rewind 2026-08-12 04:34:41 -04:00
Alex Vanderveen add8ea46e0 update overlay ui 2026-08-12 04:07:18 -04:00
Alex Vanderveen 554476457f rewind support 2026-08-12 03:51:50 -04:00
Alex Vanderveen 31e16f4e40 chore: update submodule 2026-08-12 03:32:42 -04:00
Alex Vanderveen 1c070dfe40 chore: bump recomp-net + retcomm-rbengine 2026-08-12 03:25:31 -04:00
Alex Vanderveen db24878132 chore: update submodule 2026-08-12 02:54:48 -04:00
Alex Vanderveen a73a5c1093 rewind feature 2026-08-12 02:42:30 -04:00
Alex Vanderveen 3acb8f6d59 Integrate retcomm-rbengine as shared rollback host policy.
Wire lib/retcomm-rbengine beside recomp-net and replace MotK in-tree sched,
hash_confirm, input hist, snap ring, and rb_post with thin PSX shims so the
ecosystem can share invent/pacing policy outside each title facade.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 23:52:31 -04:00
Alex Vanderveen 2207cd9a09 auto versioning CI releases 2026-08-11 22:05:20 -04:00
Alex Vanderveen 48a2a4dd7a hotfix 2026-08-11 20:34:19 -04:00
Alex Vanderveen 581d4e927c Merge origin/master into feat/legendofmanaetc
Resolve main.cpp by keeping ae_fill/rematch netplay bring-up and taking
master's mod-safe turbo_loads/skip_fmv apply (no pre-activation clobber).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 19:28:20 -04:00
Alex Vanderveen 12f4e3281e lobby rematch & bios hotswap stability 2026-08-11 18:59:19 -04:00
Matthew Stanley fe22c12f7b tests: make unregistered tests impossible, and repair the ones that rotted
PR #134 fixed test_netplay_snap_ring.c's harness. This addresses why it broke:
its only build instructions were a gcc command line in a header comment, and a
comment cannot fail. 16 of the 41 C/C++ tests in runtime/tests/ were in that
state, plus 3 python guards. Four had already rotted against the runtime API.

Worse, two tests that WERE registered did not link on origin/master --
sio_dualshock_rumble_test (psx_read_word / psx_get_cycle_count / i_mask /
g_debug_last_store_pc) and spu_fidelity_test (crc32_update) -- so `ctest` in a
runtime tree did not build at all.

Registering them fixes the instance. check_test_registration.cmake fixes the
class: it reads both CMakeLists.txt files as text and fails configure on any
test file that is neither registered nor listed with a reason. Reading them as
text (rather than adding a top-level CMakeLists.txt) keeps the BIOS-free
recompiler tree configurable, which is the only tree a newcomer can configure;
both projects call the guard, so each catches orphans in the other.
CONFIGURE_DEPENDS matters: without it, ADDING a test would not re-run configure.

Also incorporates TechnicallyComputers' review of #134. -Werror turns a
pre-existing Linux warning into a hard failure: clock_gettime/CLOCK_MONOTONIC
need _POSIX_C_SOURCE under a strict -std=cNN. Both halves are needed -- the
define in netplay_snap_ring.c fixes a direct compile of that TU, but not the
harness, which includes <stdio.h> first and so has already latched glibc's
<features.h>. Verified on Ubuntu 24.04 / GCC 13.3.

Test-file changes:
  * repaired 4 rotted tests (stubs / a changed parameter list), no logic edits
  * build recipes replaced with `ctest -R <name>`, so exactly one description
    of how a test builds exists and the build executes it
  * known-failing tests registered DISABLED, not de-registered -- de-registering
    is the mechanism that hid these in the first place; DISABLED keeps them in
    `ctest -N` and countable without turning the run red
  * sio_card_protocol_test runs in a build-tree scratch dir; it writes
    test_dir/card1.mcd, and that is a TRACKED fixture
  * removed three committed test binaries and widened the .gitignore rule that
    missed them (it only covered *.exe, and these were Linux builds)

Disabled, each with a bead: sio_card_protocol_test (147/309),
sio_dualshock_rumble_test (27), spu_fidelity_test (4 ADSR), aot_overlay_discovery
(cwd), and the 3 pre-existing perf guards.

Verified:
  * adding an unregistered test file fails the build with an actionable message;
    removing it passes
  * runtime ctest 32/32 pass, 3 disabled
  * recompiler ctest: same 5 failures as a pristine origin/master baseline
  * Tomba, Tomba 2 and MMX6 build against this framework and were each driven
    240s of attract + randomised pad fuzz (~195 injections, 47+ combos):
    59.6 fps sustained, zero stalls, all three reached real gameplay

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 14:34:12 -07:00
Alexandros Mandravillis 6c641db0ea tests: repair standalone snap ring harness 2026-08-11 21:44:53 +03:00
Alex Vanderveen 599d326dd0 default multitap and multitap hack, and savestate optimizations 2026-08-11 14:20:26 -04:00
Alex Vanderveen 49f7a7f668 hardening connectivity 2026-08-11 11:57:44 -04:00
Alex Vanderveen 4e8900e6fe bios netplay patch 2026-08-10 23:20:30 -04:00
Alex Vanderveen 91d683d87d bios hotfix 2026-08-10 22:53:17 -04:00
Alex Vanderveen 71adcfa2c0 BIOS pathing 2026-08-10 22:17:30 -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
Alex Vanderveen 61279a9d01 bios select and rematch pathing fix 2026-08-10 20:44:18 -04:00
Matthew Stanley 73a8b614b6 Bound overlay captures and compact legacy vaults 2026-08-10 00:06:29 -07:00
Matt Stanley 52d6a5440d Merge pull request #127 from mstan/hotfix/windows-installations
Fix Windows setup installs: require_generated cmdline + BIOS picker.
2026-08-09 21:22:51 -07:00
Matthew Stanley 4f8f584ac6 fix(msvc): close issue 122 portability gaps 2026-08-09 21:06:23 -07:00
Matthew Stanley 41658ce997 launcher: ignore hidden stale BIOS paths 2026-08-09 20:44:26 -07:00
Matthew Stanley 4af7025f3d launcher: gate disc netplay diagnostics by capability 2026-08-09 20:03:10 -07:00
Alex Vanderveen 6c0d343077 Fix Windows setup installs: require_generated cmdline + BIOS picker.
Pass generated shard paths via a SOURCES_FILE so cmake -P is not killed by
CreateProcess's ~8191-char limit, and keep has_bios on for OpenBIOS-only
setup SDKs so the first-run wizard still offers SCPH1001.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 22:57:18 -04:00
Matthew Stanley e102688227 launcher: make PSX display enhancements mod-only 2026-08-09 19:41:44 -07:00
Alex Vanderveen 2918a4ef0c Merge branch 'master' of https://github.com/mstan/psxrecomp 2026-08-09 22:25:24 -04:00
Alex Vanderveen 47d1bf147b stomp mods in netplay 2026-08-09 22:24:48 -04:00
Matthew Stanley e76c53d63a tests: update Hybrid integration guards 2026-08-09 19:17:59 -07:00
Matthew Stanley 89cf7c2053 keybinds: use the project SDL shim, not a raw <SDL.h>
The mouse-button commit added #include <SDL.h> to psx_keybinds.c, which does
not compile: that translation unit is not given SDL's include directory, and
the codebase reaches SDL through runtime/include/psx_sdl.h (the SDL2/SDL3
shim) rather than the vendor header directly. psx_keybinds.h already includes
the shim, so the raw include is both wrong and unnecessary.

Corrected in-port rather than bounced back, since the rest of the commit is
sound.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:07:55 -07:00
Matthew Stanley 0179d941ef input: keep keyboard/mouse binds live alongside a routed gamepad
Routing a player to a controller silently discarded every keybinds.ini and
mouse bind, which defeats the pad-in-hand plus mouse-for-aiming combination
those binds exist for.

This is the same behaviour change as the original commit in PR #107, but
applied through pad_sources_for() rather than to the button merge alone. That
distinction is the point: every consumer of the source set - the button merge,
the HYBRID auto-switch detectors and the analog stick fold - now learns about
the keyboard in the same instant.

Widening only the button merge (the original shape) let a keyboard D-pad press
assert the D-pad bits while hybrid_dpad_active never saw them, so a mod-driven
hybrid pad would report D-pad input while still presenting ANALOG - the exact
failure the hybrid logic exists to prevent, and silent. Commit 3ba40b02 had
previously removed a keyboard-always-P1 behaviour for related reasons.

The PSX pad word is active-low and the merge is an AND, so an unpressed source
is a no-op and pad-only players are unaffected.

NEEDS HANDS-ON VALIDATION: Tomba's hybrid controller mod is the only consumer
of HYBRID mode. It should be exercised with a controller routed AND keyboard
binds live, confirming the pad still auto-switches. If the two cannot coexist,
the agreed fallback is for the mod to disable keyboard/mouse dual binding while
active, documented in the mod.

Co-Authored-By: Alexandros Mandravillis <Alexbeav@live.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:07:55 -07:00
Alexandros Mandravillis 63db8f430d keybinds: dual bindings per control (primary + alt, comma ini syntax) 2026-08-09 19:07:55 -07:00
Alexandros Mandravillis e4658ee680 keybinds: allow mouse buttons as bindable inputs (Mouse1-5, LMB/MMB/RMB) 2026-08-09 19:06:13 -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
Matthew Stanley fc5b07f677 input: derive every pad source from one predicate (pad_sources_for)
The runtime decided which sources may drive a pad slot in three independent
places - the button merge in capture_pad_slot, the HYBRID analog/digital
auto-switch (hybrid_stick_active / hybrid_dpad_active), and the analog stick
fold. Nothing kept them in step.

That is a live hazard rather than a tidiness issue. If the button merge is
widened without widening the hybrid detectors, a source can assert the D-pad
while the hybrid state machine never sees it, so the pad reports D-pad presses
while still presenting ANALOG and never falls back to digital - the exact
failure the hybrid logic exists to prevent, and silent when it happens.
Commit 3ba40b02 previously removed a keyboard-always-P1 behaviour for related
reasons; the hybrid gates were written assuming it stayed removed.

pad_sources_for(p, dev_here) now returns the source set once (device,
keybinds, all_pads) and all three consumers read it. Input-routing POLICY
becomes a change to that one function instead of three conditions that must
be edited together.

Pure refactor - behaviour is unchanged:
  device   == (p.kind != 0), as before
  keybinds == (p.kind == 1 || dev_here), the same expression the hybrid
              D-pad detector already used
  all_pads == dev_here, as before
dev_here implies slot 0, so the previously hardcoded player 1 in
pad_from_keyboard(1) and psx_keybinds_sticks(keys, 1, ...) is the same value
as `player`. Where a consumer is now reached that was not before (kind==1 in
analog mode re-entering psx_keybinds_sticks), the call is idempotent: it only
sets fixed extremes on held binds, and pad_sticks_for already applied it with
the same player.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:04:12 -07:00
Matthew Stanley 6042e82613 fix(cdrom): restore the "disc could not be mounted" check PR #110 dropped
cdrom_init() is deliberately non-fatal -- BIOS-only targets run with an empty
drive on purpose -- so when a disc IS requested and nothing mounts, the game
boots into an empty drive and renders nothing. That is the "black screen on a
.cue that works as a .bin" symptom.

The earlier validate_disc_for_launch() pass cannot catch it: identify_disc()
reads the data-track FILE directly, so a cue whose sheet the mounting reader
rejects still earns a green "Disc verified" badge and the player is left
staring at black with no explanation.

Master reports the actual failure and names the likely cause; PR #110 dropped
the check while keeping every part it depends on -- cdrom_has_disc() in
cdrom.h, resolve_disc_path()/DiscPathResolution in disc_path.h, and
launcher_warning() -- so only the call site and the disc_path.h include were
missing. Block restored verbatim from origin/master.

Verified no false positive: Tomba boots past cdrom_init into gameplay with a
good disc (PSX_NO_LAUNCHER=1, reaches the in-game window). The negative case (a
cue that verifies but will not mount) was not synthesised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 16:49:43 -07:00
Matthew Stanley e8441c6a3d fix(bios_hle): decide call-HLE and boot-skip via psx_bios_hle_plan again
PR #110 replaced master's plan() call with an inline
"(bios_hle && !keep_intro) || fast_boot" boot-skip decision that passes
bios_hle straight through as the call-HLE axis. That conflates two axes with
DIFFERENT per-image requirements, which is exactly the bug bios_hle_plan.c was
written to fix: call-HLE needs a DeliverEvent anchor (deliver_event_ret), the
boot-skip needs only shell_entry_phys.

On an image exporting no DeliverEvent anchor -- OpenBIOS, which is bundled and
default for several titles, and bios_hle defaults ON -- the inline form turns
call-HLE on anyway, servicing B0 events with mismatched semantics. That is the
OpenBIOS bring-up wedge: the shell menu hangs in a CD-event wait under HLE and
boots fine under LLE.

Restores the plan() call and both denial notices, so an unavailable axis is
reported rather than silently taken (or silently cancelling the other).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:51:57 -07:00
Matthew Stanley b98633734f fix(bios,input): restore master call sites PR #110 dropped from main.cpp
BIOS
  - resolve_bios_path() now consults PSXRecompV4::resolve_bios_rom() again, so
    either filename convention resolves: a dump folder holding
    "US-PSX-SCPH1001.BIN" satisfies a request for "SCPH1001.BIN". Restored at
    both rungs (direct hit and the exe-dir upward walk).
  - gi.has_bios = psx_bios_has_selectable() restores the launcher BIOS row.
    It asks the registry rather than psx_bios_image, which is not populated
    until a backend activates.

Input
  - anti_deadzone was gone entirely: PR #110 inlined the stick maths into
    axes_to_pad_pair() and implemented only the deadzone half, so [controller]
    anti_deadzone in game.toml was parsed and silently ignored. axes_to_pad_pair
    now calls the shared psx_stick_to_dualshock() (runtime/src/psx_stick.c),
    keeping PR's per-player deadzone and restoring master's anti-deadzone.
  - Launcher device<->source mapping used raw string compares, so a hand-edited
    "Keyboard" or " none " was misclassified as a gamepad. Both read sites and
    both writeback sites now use launcher_source_from_device(), which trims and
    lowercases first. PR's N-player loop and GUID preservation are kept -- they
    supersede master's 2-player launcher_device_from_source pair.

Shutdown
  - autocompile_shutdown() restored ahead of capture teardown, so closing the
    window no longer leaves cmd/python/gcc running against cache and capture
    files while the main thread does synchronous shutdown work (the window
    appears frozen until that process tree exits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:50:50 -07:00
Matthew Stanley 32047212b7 fix(widescreen): restore the five gpu_ws_set_* call sites PR #110 lost
PR #110 keeps the widescreen engine and most of its configuration (full_2d,
bg2d, clear_reveal, cull_guard_pixels, explicit/slti/negsub/vxrange/depth/
plane_nx/xclip cull sites, dome sites) but drops five setters that master
calls from the same [widescreen] config block:

  - gpu_ws_set_auto_ui_squash
  - gpu_ws_set_activation_guard_pixels
  - gpu_ws_set_cull_keep_sites
  - gpu_ws_set_angle_sites
  - gpu_ws_set_aspect_cone

All five still have live config fields in recompiler/src/config_loader.h and
live implementations in runtime/src/gpu.c -- only the main.cpp call sites were
missing, so every game.toml that configures them was silently ignored and the
funnel-cull / aspect-cone quality work never engaged.

Blocks are taken verbatim from origin/master and inserted at the same points
in the config block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:28:37 -07:00
Matthew Stanley 7e4c01d529 fix(mods): restore the four mod_runtime hooks PR #110 lost from main.cpp
PR #110 keeps almost the whole mods integration - mod_plugins.h, every
psx_mod_set_* callback, mod_runtime_commit, mod_runtime_activate_plugins -
but four call sites present on master are absent, which leaves the entire
feature inert:

  - mod_runtime_initialize          scan <exe_dir>/mods + load persisted state
  - mod_runtime_launcher_provider   gi.mods, the launcher Mods tab
  - mod_runtime_effective_disc_path mount a mod's private patched image
  - mod_runtime_enable_disc_patches apply exe/overlay patches after the guard

Without initialize() nothing else can fire, so a build stages all of a
title's mod packages next to the exe and then never reads them: Tomba showed
an empty Mods tab and fell back to the legacy Display "View mode" row instead
of the widescreen mod.

gi.mods is set at both launcher GameInfo sites so the tab appears on either
path. Blocks are taken verbatim from origin/master.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:26:39 -07:00
Matthew Stanley e5cb95b787 fix(build): link nd_intro_ot.c — PR #110 added the TU but never the source entry
083959df added runtime/src/nd_intro_ot.c + nd_intro_ot.h, and 02b08dae /
later commits call psx_nd_note_wood_batch_ot(), psx_nd_wood_batch_ot(),
psx_nd_note_sibling_ot_hint() and psx_nd_sibling_ot_hint() from gte.cpp and
debug_server.c. runtime.cmake keeps an explicit source list (no globbing), so
the TU was never compiled and every link failed with undefined references.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:04:00 -07:00
Alex Vanderveen c42809a7fe bump 2026-08-09 14:31:58 -04:00
Alex Vanderveen 1bbda282a2 ungate ape escape patch, doesn't break determinism 2026-08-09 14:27:24 -04:00
Alex Vanderveen 4338ed9207 document ape-escape memcard patches 2026-08-09 00:39:32 -04:00