diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index c9b8da59..439d2ee3 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -2220,7 +2220,7 @@ UserSettings load_user_settings(const fs::path& path) { }); if (v.contains("rewind_depth")) try_get([&]{ int d = toml::find(v, "rewind_depth"); - static const int opts[4] = {25, 50, 75, 100}; + static const int opts[4] = {50, 100, 150, 200}; int best = opts[0]; int best_d = d > best ? d - best : best - d; for (int i = 1; i < 4; ++i) { @@ -2230,6 +2230,18 @@ UserSettings load_user_settings(const fs::path& path) { s.rewind_depth = best; s.has_rewind_depth = true; }); + if (v.contains("rewind_interval")) try_get([&]{ + int d = toml::find(v, "rewind_interval"); + static const int opts[5] = {1, 4, 8, 12, 15}; + int best = opts[0]; + int best_d = d > best ? d - best : best - d; + for (int i = 1; i < 5; ++i) { + int dd = d > opts[i] ? d - opts[i] : opts[i] - d; + if (dd < best_d) { best_d = dd; best = opts[i]; } + } + s.rewind_interval = best; + s.has_rewind_interval = true; + }); } if (doc.contains("audio")) { const toml::value& a = toml::find(doc, "audio"); @@ -2455,6 +2467,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { f << "adaptive_view = " << (s.adaptive_view ? "true" : "false") << "\n"; if (s.has_rewind_depth) f << "rewind_depth = " << s.rewind_depth << "\n"; + if (s.has_rewind_interval) + f << "rewind_interval = " << s.rewind_interval << "\n"; f << "\n[audio]\n"; if (s.has_spu_hq) f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n"; diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 54cea435..e5c44b65 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -1104,8 +1104,11 @@ struct UserSettings { int aspect_den = 3; // (4:3 = native) bool has_adaptive_view = false; bool adaptive_view = false; // [video] rewind_depth: local rewind snap-ring capacity. UI offers - // 25 / 50 / 75 / 100 (default 50). Runtime clamps + snaps to those steps. + // 50 / 100 / 150 / 200 (default 50). Runtime clamps + snaps to those steps. bool has_rewind_depth = false; int rewind_depth = 50; + // [video] rewind_interval: frames between local rewind snaps. UI offers + // 1 / 4 / 8 / 12 / 15 (default 15). + bool has_rewind_interval = false; int rewind_interval = 15; // [audio] bool has_spu_hq = false; bool spu_hq = false; // [bios] / [disc] / [memcard] diff --git a/runtime/include/psx_rewind.h b/runtime/include/psx_rewind.h index 606f4438..72069b2c 100644 --- a/runtime/include/psx_rewind.h +++ b/runtime/include/psx_rewind.h @@ -10,10 +10,10 @@ * Keyboard: F8 (config.ini [KeyMap] Rewind). Controller: View/Back or L3. * While open: D-pad / left-stick Left/Right, Cross/A load, Circle/B/Back close. * - * Env: PSX_REWIND=0 disables; PSX_REWIND_INTERVAL (frames, default 15); + * Env: PSX_REWIND=0 disables; PSX_REWIND_INTERVAL (1/4/8/12/15, default 15); * PSX_REWIND_FMV_INTERVAL (frames while depth24/MDEC/XA, default 4); - * PSX_REWIND_DEPTH (snaps; UI offers 25/50/75/100, default 50, max 100). - * settings.toml [video] rewind_depth overrides the default (env still wins). + * PSX_REWIND_DEPTH (50/100/150/200, default 50, max 200). + * settings.toml [video] rewind_depth / rewind_interval (env still wins). */ #include @@ -25,6 +25,7 @@ extern "C" { /* Snap count kept in the local rewind ring. UI values: 25 / 50 / 75 / 100. */ void psx_rewind_set_depth(uint32_t depth); +void psx_rewind_set_interval(uint32_t interval); void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc); void psx_rewind_shutdown(void); diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 8a250966..3f400e5a 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -1001,8 +1001,10 @@ function(psxrecomp_add_runtime_target target) endif() # Per-game netplay/local pad ceiling. Default 2 (MotK / dual-shock path). - # Games that need multitap N-player (e.g. Bomberman Party Edition) pass - # MAX_PLAYERS 5. Clamped to the framework absolute max of 5. + # Single-player titles (Tomba, Ape Escape, …) pass MAX_PLAYERS 1 so rewind + # / rbengine still link without advertising multiplayer. Multitap N-player + # (Bomberman Party Edition) uses 5; dual SCPH-1070 uses 8. Range matches + # sio.h (1..8). if(NOT PSXRT_MAX_PLAYERS) if(DEFINED PSX_MAX_PLAYERS AND NOT PSX_MAX_PLAYERS STREQUAL "") set(PSXRT_MAX_PLAYERS "${PSX_MAX_PLAYERS}") @@ -1010,9 +1012,9 @@ function(psxrecomp_add_runtime_target target) set(PSXRT_MAX_PLAYERS 2) endif() endif() - if(PSXRT_MAX_PLAYERS LESS 2 OR PSXRT_MAX_PLAYERS GREATER 5) + if(PSXRT_MAX_PLAYERS LESS 1 OR PSXRT_MAX_PLAYERS GREATER 8) message(FATAL_ERROR - "MAX_PLAYERS must be in 2..5 (got ${PSXRT_MAX_PLAYERS})") + "MAX_PLAYERS must be in 1..8 (got ${PSXRT_MAX_PLAYERS})") endif() message(STATUS "psxrecomp ${target}: PSX_MAX_PLAYERS=${PSXRT_MAX_PLAYERS}") diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 509a683d..3cb12e98 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1059,7 +1059,8 @@ static int g_video_screen = 0; /* 0=raw,1=crt,2=composite,3=trinitr static int g_video_win_w = 1280; /* window width (height follows aspect) */ static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */ static int g_auto_skip_fmv = 0; /* skip FMVs the instant they're detected */ -static int g_rewind_depth = 50; /* local rewind snap count (25/50/75/100) */ +static int g_rewind_depth = 50; /* local rewind snap count (50/100/150/200) */ +static int g_rewind_interval = 15; /* frames between snaps (1/4/8/12/15) */ static int g_headless = 0; /* debug/CI frontend: no SDL window/audio */ /* FMV instant-skip via the game's OWN end-of-movie path. Tomba's MDEC player * (FUN_8001efe8) tears a movie down when the streamed frame number reaches that @@ -10256,6 +10257,7 @@ int main(int argc, char** argv) { } if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq; if (us.has_rewind_depth) g_rewind_depth = us.rewind_depth; + if (us.has_rewind_interval) g_rewind_interval = us.rewind_interval; if (us.has_bios_path && !bios_from_cli && !us.bios_path.empty()) { settings_bios_storage = us.bios_path.string(); bios_path = settings_bios_storage.c_str(); @@ -10663,6 +10665,7 @@ int main(int argc, char** argv) { seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true; seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true; seed.rewind_depth = g_rewind_depth; seed.has_rewind_depth = true; + seed.rewind_interval = g_rewind_interval; seed.has_rewind_interval = true; seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true; if (has_netplay_player_name) { seed.netplay_player_name = netplay_player_name; @@ -10816,6 +10819,7 @@ int main(int argc, char** argv) { ls.frame_interp_fps = seed.frame_interpolation_fps; ls.spu_hq = seed.spu_hq ? 1 : 0; ls.rewind_depth = seed.rewind_depth > 0 ? seed.rewind_depth : 50; + ls.rewind_interval = seed.rewind_interval > 0 ? seed.rewind_interval : 15; ls.auto_skip_fmv = seed.auto_skip_fmv ? 1 : 0; ls.turbo_loads = seed.turbo_loads ? 1 : 0; /* Localization: index of resolved_language within lang_menu_options @@ -11045,6 +11049,8 @@ int main(int argc, char** argv) { seed.spu_hq = ls.spu_hq != 0; seed.has_spu_hq = true; seed.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50; seed.has_rewind_depth = true; + seed.rewind_interval = ls.rewind_interval > 0 ? ls.rewind_interval : 15; + seed.has_rewind_interval = true; seed.auto_skip_fmv = ls.auto_skip_fmv != 0; seed.has_auto_skip_fmv = skip_fmv_offered; seed.turbo_loads = ls.turbo_loads != 0; @@ -11244,6 +11250,8 @@ int main(int argc, char** argv) { g_audio_spu_hq = seed.spu_hq; g_rewind_depth = seed.has_rewind_depth && seed.rewind_depth > 0 ? seed.rewind_depth : 50; + g_rewind_interval = seed.has_rewind_interval && seed.rewind_interval > 0 + ? seed.rewind_interval : 15; skip_launcher_setting = seed.skip_launcher; if (seed.has_bios_path) { settings_bios_storage = seed.bios_path.string(); @@ -12231,6 +12239,7 @@ session_reboot: memory_get_bios_checksum(), game_entry_pc, bios_token, openbios_ws); psx_rewind_set_depth((uint32_t)g_rewind_depth); + psx_rewind_set_interval((uint32_t)g_rewind_interval); psx_rewind_configure(memory_get_bios_checksum(), game_entry_pc); /* Headless / agent load: PSX_LOAD_SLOT=N stages F1..F12 load (0..11) * at the next safe block boundary after boot. */ @@ -12826,6 +12835,8 @@ soft_return_lobby: us.has_spu_hq = true; us.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50; us.has_rewind_depth = true; + us.rewind_interval = ls.rewind_interval > 0 ? ls.rewind_interval : 15; + us.has_rewind_interval = true; us.auto_skip_fmv = ls.auto_skip_fmv != 0; us.has_auto_skip_fmv = skip_fmv_offered; us.turbo_loads = ls.turbo_loads != 0; @@ -12875,6 +12886,10 @@ soft_return_lobby: g_rewind_depth = ls.rewind_depth; psx_rewind_set_depth((uint32_t)g_rewind_depth); } + if (ls.rewind_interval > 0) { + g_rewind_interval = ls.rewind_interval; + psx_rewind_set_interval((uint32_t)g_rewind_interval); + } switch (ls.aspect_index) { case 2: g_video_aspect_num = 21; g_video_aspect_den = 9; break; case 1: g_video_aspect_num = 16; g_video_aspect_den = 9; break; diff --git a/runtime/src/psx_rewind.c b/runtime/src/psx_rewind.c index da983702..bdb2c771 100644 --- a/runtime/src/psx_rewind.c +++ b/runtime/src/psx_rewind.c @@ -24,7 +24,7 @@ #define RW_THUMB_W 128 #define RW_THUMB_H 96 -#define RW_MAX_DEPTH 100 +#define RW_MAX_DEPTH 200 #define RW_DEF_DEPTH 50 #define RW_DEF_INTERVAL 15 /* Match netplay §96 FMV media snaps (default 4; MEDIA_KF uses 2). */ @@ -102,6 +102,7 @@ static const uint8_t FONT8[59][8] = { #if !defined(PSX_HAS_RBENGINE_SNAP) void psx_rewind_set_depth(uint32_t depth) { (void)depth; } +void psx_rewind_set_interval(uint32_t interval) { (void)interval; } void psx_rewind_configure(uint32_t bios_checksum, uint32_t entry_pc) { (void)bios_checksum; @@ -151,6 +152,7 @@ static uint32_t s_interval = RW_DEF_INTERVAL; static uint32_t s_fmv_interval = RW_DEF_FMV_INTERVAL; static uint32_t s_depth = RW_DEF_DEPTH; static int s_depth_pref = -1; /* -1 = unset; else from settings.toml / launcher */ +static int s_interval_pref = -1; static uint32_t s_frame; static uint32_t s_last_capture_frame = 0xffffffffu; static int s_capture_due; @@ -190,10 +192,10 @@ static uint32_t env_u32(const char *name, uint32_t def, uint32_t lo, uint32_t hi return v; } -/* UI offers 25/50/75/100; nearest match keeps hand-edited toml values sane. */ +/* UI offers 50/100/150/200; nearest match keeps hand-edited toml values sane. */ static uint32_t normalize_rewind_depth(uint32_t v) { - static const uint32_t opts[4] = {25u, 50u, 75u, 100u}; + static const uint32_t opts[4] = {50u, 100u, 150u, 200u}; uint32_t best = opts[0]; uint32_t best_d = v > best ? v - best : best - v; unsigned i; @@ -207,6 +209,23 @@ static uint32_t normalize_rewind_depth(uint32_t v) return best; } +/* UI offers 1/4/8/12/15. */ +static uint32_t normalize_rewind_interval(uint32_t v) +{ + static const uint32_t opts[5] = {1u, 4u, 8u, 12u, 15u}; + uint32_t best = opts[0]; + uint32_t best_d = v > best ? v - best : best - v; + unsigned i; + for (i = 1; i < 5; i++) { + uint32_t d = v > opts[i] ? v - opts[i] : opts[i] - v; + if (d < best_d) { + best_d = d; + best = opts[i]; + } + } + return best; +} + void psx_rewind_set_depth(uint32_t depth) { s_depth_pref = (int)normalize_rewind_depth(depth); @@ -215,6 +234,13 @@ void psx_rewind_set_depth(uint32_t depth) s_depth = (uint32_t)s_depth_pref; } +void psx_rewind_set_interval(uint32_t interval) +{ + s_interval_pref = (int)normalize_rewind_interval(interval); + if (s_enabled >= 0) + s_interval = (uint32_t)s_interval_pref; +} + static int rewind_wanted(void) { const char *e; @@ -224,7 +250,12 @@ static int rewind_wanted(void) s_enabled = 0; else s_enabled = 1; - s_interval = env_u32("PSX_REWIND_INTERVAL", RW_DEF_INTERVAL, 1u, 60u); + { + uint32_t iv_def = s_interval_pref > 0 ? (uint32_t)s_interval_pref + : RW_DEF_INTERVAL; + s_interval = normalize_rewind_interval( + env_u32("PSX_REWIND_INTERVAL", iv_def, 1u, 60u)); + } s_fmv_interval = env_u32("PSX_REWIND_FMV_INTERVAL", RW_DEF_FMV_INTERVAL, 1u, 16u); { @@ -251,7 +282,10 @@ static int rewind_fmv_media_active(void) static uint32_t rewind_capture_interval(void) { - return rewind_fmv_media_active() ? s_fmv_interval : s_interval; + if (!rewind_fmv_media_active()) + return s_interval; + /* FMV densifies toward s_fmv_interval but never sparser than the user pick. */ + return s_interval < s_fmv_interval ? s_interval : s_fmv_interval; } static int resume_pc_ok(uint32_t pc)