diff --git a/README.md b/README.md index e0ce57d7..5f47f471 100644 --- a/README.md +++ b/README.md @@ -735,9 +735,15 @@ seek delays as real hardware. On top of that faithful baseline, load-time acceleration is **opt-in**, per game, so the accurate path is never compromised: - **Turbo** — a hold-to-fast-forward key that compresses loads on demand. -- **`[runtime] turbo_loads` / `idle_skip`** — automatic acceleration during load - waits, with `turbo_audio_sink` keeping the SPU timeline coherent through the - burst. +- **The "Fast Loading (host pacing)" and "CD Speed" mods** — automatic + acceleration during load waits, shipped with every title and **off by + default**, with `turbo_audio_sink` keeping the SPU timeline coherent through + the burst. Host pacing only changes how fast real time is fed to a load, so + the guest cannot desync; CD speed changes when the game receives CD + interrupts. The former `[runtime] turbo_loads` config key is deprecated and + ignored — see `docs/config_schema.md`. +- **`[runtime] idle_skip`** — proof-gated fast-forward through idle polling + loops, with guest time and device events still advancing exactly. - **Warm CD routes (`[[runtime.warm_cd_routes]]`)** — narrowly-scoped fast read cadence armed on a specific `SetLoc`, restoring authentic timing the moment the read pattern diverges. diff --git a/docs/config_schema.md b/docs/config_schema.md index 965c4ecd..d8412797 100644 --- a/docs/config_schema.md +++ b/docs/config_schema.md @@ -328,17 +328,36 @@ The other load-time accelerators are likewise opt-in: ```toml [runtime] -turbo_loads = true -offer_turbo_loads = true idle_skip = true turbo_audio_sink = true ``` -`offer_turbo_loads` defaults to true. A game that moves load acceleration into -its mod catalog sets it false; the generic Settings switch is hidden and stale -persisted values are ignored before the selected mod activation callback runs. +### `turbo_loads` / `offer_turbo_loads` — deprecated and ignored -`turbo_audio_sink` is meaningful only while `turbo_loads` is active. It keeps +**Do not use these keys.** Load acceleration is owned by the Mods catalog: +`psx.enhancement.fast-loading` ("Fast Loading (host pacing)") and +`psx.enhancement.cd-speed`. Both target `game_id = "*"`, so they ship with every +title, both default to off, and both expose the multiplier and instant-scheduler +detail that a single opaque boolean never could. recomp-ui correspondingly draws +no generic Turbo loads row. + +Both keys are still parsed so existing configs load without error, but neither is +honoured — the runtime logs a deprecation line naming the Fast Loading mod and +leaves acceleration off. Remove them from `game.toml`. + +The same applies to `[video] turbo_loads` in a user's `settings.toml`: it is no +longer restored at startup, and it is no longer written back out, so the stale +row disappears on the first save after updating. This is deliberate. Because the +launcher stopped drawing a control for it, a persisted `true` was simultaneously +authoritative and unreachable: one run of a build whose `game.toml` said `true` +latched turbo on permanently, and no later config change could undo it. That +shipped to players in MegaManX6Recomp v1.0.4/v1.0.5 (MegaManX6Recomp#14). Never +restore this row without also restoring a UI control for it. + +For development, the `turbo_loads` TCP debug command still toggles acceleration +at runtime. + +`turbo_audio_sink` is meaningful only while load acceleration is active. It keeps the guest SPU timeline advancing but discards accelerated samples before host playback, then fades normal output back in. diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index bcd94d37..af904e7d 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -471,12 +471,17 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& validate_project_relative(rt.overlay_capture_persist_dir, "runtime.overlay_capture_persist_dir"); } + // turbo_loads / offer_turbo_loads are deprecated and ignored (see + // config_loader.h). Still parsed so old configs load, and the presence + // flags let the runtime name the dead key in its deprecation notice. if (runtime.contains("turbo_loads")) { rt.turbo_loads = toml::find(runtime, "turbo_loads"); + rt.has_turbo_loads = true; } if (runtime.contains("offer_turbo_loads")) { rt.offer_turbo_loads = toml::find(runtime, "offer_turbo_loads"); + rt.has_offer_turbo_loads = true; } if (runtime.contains("turbo_audio_sink")) { rt.turbo_audio_sink = toml::find(runtime, "turbo_audio_sink"); @@ -2159,6 +2164,9 @@ UserSettings load_user_settings(const fs::path& path) { if (v.contains("auto_skip_fmv")) try_get([&]{ s.auto_skip_fmv = toml::find(v, "auto_skip_fmv"); s.has_auto_skip_fmv = true; }); + // Deprecated and ignored: read only so the runtime can report that a + // stale value was found (and so the next save drops it). Never applied + // — see UserSettings::turbo_loads in config_loader.h. if (v.contains("turbo_loads")) try_get([&]{ s.turbo_loads = toml::find(v, "turbo_loads"); s.has_turbo_loads = true; }); @@ -2411,8 +2419,10 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { } if (s.has_auto_skip_fmv) f << "auto_skip_fmv = " << (s.auto_skip_fmv ? "true" : "false") << "\n"; - if (s.has_turbo_loads) - f << "turbo_loads = " << (s.turbo_loads ? "true" : "false") << "\n"; + /* turbo_loads is deliberately NOT written back: it is deprecated and no + * longer restored, so re-emitting it would preserve a dead row that looks + * authoritative. Omitting it lets an existing settings.toml self-clean on + * the first save after the update. */ if (s.has_fast_boot) f << "fast_boot = " << (s.fast_boot ? "true" : "false") << "\n"; if (s.has_bios_hle) diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 6590f71c..2755fe45 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -251,18 +251,30 @@ struct RuntimeConfig { // beside the executable. std::string overlay_capture_persist_dir; - // turbo_loads: OPT-IN per game. While the game is loading (CD data - // stream active, XA/FMV excluded, post-BIOS-handoff only) the frontend - // skips wall-clock pacing so the guest runs at host speed — compressing - // load wall-time. Streaming titles (e.g. Crash) must leave this off. + // turbo_loads: DEPRECATED AND IGNORED. Load acceleration is owned by the + // Mods catalog — 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 both exposing the + // multiplier / instant-scheduler detail a single opaque bool never could. + // recomp-ui correspondingly draws no generic Turbo loads row. + // + // The key is still parsed so old configs load without error, but the + // runtime NO LONGER honours it: it logs one deprecation line naming the + // Fast Loading mod and leaves acceleration off. Retired because leaving + // the legacy switch live forced turbo on in any title that had not + // explicitly migrated, with no UI to turn it back off (MegaManX6Recomp#14 + // shipped that way in v1.0.4/v1.0.5). Development toggling still works + // through the `turbo_loads` TCP debug command. bool turbo_loads = false; + bool has_turbo_loads = false; // key present in game.toml - // offer_turbo_loads: expose the generic Turbo loads switch through - // recomp-ui Settings. Defaults true for compatibility. A game migrating - // load acceleration into its mod catalog sets this false; stale persisted - // Settings values are then ignored and a trusted activation plugin owns - // the launch policy. - bool offer_turbo_loads = true; + // offer_turbo_loads: DEPRECATED AND IGNORED, now that the generic switch + // it gated no longer exists. Defaults false and is never consulted; the + // migrated titles that set it false stay correct, and the titles that + // never set it are no longer punished for it. Parsed only so old configs + // load, and so the runtime can tell a developer the key is now a no-op. + bool offer_turbo_loads = false; + bool has_offer_turbo_loads = false; // turbo_audio_sink: while turbo_loads is actively running unpaced, keep // rendering the exact guest-time SPU sample budget (so voice/CD state @@ -1052,12 +1064,16 @@ struct UserSettings { bool has_perspective_texturing = false; bool perspective_texturing = false; bool has_screen_kind = false; int screen_kind = 0; // 0..3 (ScreenKind) bool has_auto_skip_fmv = false; bool auto_skip_fmv = false; // skip FMVs - // Turbo through in-game load screens: while the CD data stream is active, run - // the guest unpaced (host speed) to compress load wall-time. All guest timing - // (VBlanks/callbacks/sectors) is preserved and audio plays through. Default ON - // (the per-game game.toml value seeds the launcher toggle; user choice in - // settings.toml overrides it). - bool has_turbo_loads = false; bool turbo_loads = true; + // [video] turbo_loads: DEPRECATED AND IGNORED — the legacy home of the + // generic Turbo loads switch, back when the launcher drew a row for it. + // Load acceleration now lives in the Mods catalog (see + // RuntimeConfig::turbo_loads), so this row is neither restored at startup + // nor written back out; it survives only to be reported and then dropped + // the next time settings.toml is saved. Never re-restore it without also + // restoring a UI control — an unreachable persisted value that overrides a + // later game.toml change is exactly the write-only latch that shipped + // turbo-on to MegaManX6Recomp users who had no way to turn it off. + bool has_turbo_loads = false; bool turbo_loads = false; bool has_fast_boot = false; bool fast_boot = false; // HLE BIOS tier toggle (see RuntimeConfig::bios_hle). Overrides game.toml. bool has_bios_hle = false; bool bios_hle = false; diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 07e13b1c..1b0383b9 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -9023,7 +9023,11 @@ int main(int argc, char** argv) { constexpr bool ws_ultrawide_offered = false; constexpr bool frame_interpolation_offered = false; constexpr bool skip_fmv_offered = false; - bool turbo_loads_offered = true; + /* Load acceleration is likewise mod-owned (Fast Loading / CD Speed). The + * former game.toml `offer_turbo_loads` opt-out is deprecated and ignored: + * offering the generic switch is no longer possible for any title, so a + * config that forgot to migrate can no longer force turbo on. */ + constexpr bool turbo_loads_offered = false; bool vulkan_offered = false; /* game.toml [video] offer_vulkan; developer opt-in for launcher visibility */ /* Legacy single deadzone (<0 => keep per-slot / input.ini defaults). */ int resolved_deadzone = -1; @@ -9107,9 +9111,22 @@ int main(int argc, char** argv) { if (gc.runtime.has_instant_max_per_frame) instant_rate = gc.runtime.instant_max_per_frame; warm_cd_routes = gc.runtime.warm_cd_routes; - if (gc.runtime.turbo_loads) { - g_turbo_loads_enabled = 1; - std::fprintf(stdout, "psxrecomp: turbo_loads enabled (opt-in)\n"); + /* turbo_loads / offer_turbo_loads are deprecated and ignored. Load + * acceleration is owned by the Mods catalog (Fast Loading / CD + * Speed), which ships with every title and defaults off, so the + * legacy config key no longer enables anything. Say so loudly: + * MMX6 shipped `turbo_loads = true` in v1.0.4/v1.0.5 and players + * had no UI to turn it back off. */ + if (gc.runtime.has_turbo_loads || gc.runtime.has_offer_turbo_loads) { + std::fprintf(stdout, + "psxrecomp: game.toml [runtime] %s%s%s is DEPRECATED and " + "ignored; load acceleration is off unless the player " + "enables the \"Fast Loading (host pacing)\" mod. Remove " + "the key from game.toml.\n", + gc.runtime.has_turbo_loads ? "turbo_loads" : "", + (gc.runtime.has_turbo_loads && + gc.runtime.has_offer_turbo_loads) ? " / " : "", + gc.runtime.has_offer_turbo_loads ? "offer_turbo_loads" : ""); } if (gc.runtime.turbo_audio_sink) { g_turbo_audio_sink_enabled = 1; @@ -9306,7 +9323,8 @@ int main(int argc, char** argv) { if (!gc.ws_cull_w_imms.empty() || !gc.ws_cull_h_imms.empty()) gpu_ws_set_cull_imms(gc.ws_cull_w_imms.data(), (int)gc.ws_cull_w_imms.size(), gc.ws_cull_h_imms.data(), (int)gc.ws_cull_h_imms.size()); - turbo_loads_offered = gc.runtime.offer_turbo_loads; + /* gc.runtime.offer_turbo_loads is deprecated and ignored — see + * turbo_loads_offered above. Nothing to assign. */ vulkan_offered = gc.vulkan_offered; /* Register the [widescreen.backdrop] store PCs so the dirty-RAM * interpreter applies the backdrop screenX squash on the interp @@ -9495,7 +9513,20 @@ int main(int argc, char** argv) { g_video_perspective_texturing = us.perspective_texturing ? 1 : 0; if (us.has_screen_kind) g_video_screen = us.screen_kind; if (us.has_auto_skip_fmv) g_auto_skip_fmv = us.auto_skip_fmv ? 1 : 0; - if (us.has_turbo_loads) g_turbo_loads_enabled = us.turbo_loads ? 1 : 0; + /* turbo_loads is deliberately NOT restored from settings.toml. It is a + * write-only latch: the launcher stopped drawing a Turbo loads row when + * load acceleration moved to the Mods catalog, so a persisted `true` + * was simultaneously authoritative and unreachable — one run of a build + * whose game.toml said true latched it forever and no later config + * change could undo it (MegaManX6Recomp#14). Report it and move on; + * write_user_settings no longer emits the key, so the stale row is + * dropped on the next save. */ + if (us.has_turbo_loads && us.turbo_loads) + std::fprintf(stdout, + "psxrecomp: settings.toml [video] turbo_loads = true is " + "DEPRECATED and ignored; enable the \"Fast Loading (host " + "pacing)\" mod instead. The stale key is dropped on the next " + "settings save.\n"); if (us.has_fast_boot) fast_boot = us.fast_boot; if (us.has_bios_hle) bios_hle = us.bios_hle; if (us.has_fullscreen) g_fullscreen = us.fullscreen; @@ -9581,13 +9612,16 @@ int main(int argc, char** argv) { "ignoring the legacy Settings value\n"); g_auto_skip_fmv = 0; } - /* A game may likewise move load acceleration into its mod catalog. The - * activation plugin runs after final plan commit, so clear both the - * game.toml default and stale Settings value before the launcher is seeded. */ - if (!turbo_loads_offered && g_turbo_loads_enabled) { + /* Load acceleration is mod-owned on PSX, unconditionally. Nothing upstream + * of this point is allowed to have enabled it (the game.toml and + * settings.toml keys are both deprecated and ignored), so this is a + * belt-and-braces clamp rather than the migration path it used to be — an + * enabled Fast Loading / CD Speed plugin turns it on further down, after + * the final mod-plan commit. */ + if (g_turbo_loads_enabled) { std::fprintf(stdout, - "psxrecomp: Turbo loads is mod-owned for this title; " - "ignoring the legacy Settings value\n"); + "psxrecomp: Turbo loads is mod-owned on PSX; " + "ignoring the legacy value\n"); g_turbo_loads_enabled = 0; } /* Treat presentation interpolation the same way when a title moves it to @@ -11951,8 +11985,15 @@ soft_return_lobby: g_video_aa = ls.antialiasing; g_video_texfilter = ls.texture_filter; g_video_screen = ls.screen_kind; - g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0; - g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0; + /* Load acceleration and FMV skipping are mod-owned on PSX, and the + * launcher struct these come from was snapshotted BEFORE + * mod_runtime_activate_plugins() ran. Applying them here would + * clobber whatever the Fast Loading / CD Speed / Tweaks plugins + * decided with a stale pre-activation value — turning a player's + * enabled mod silently back off on the first in-game Apply. The + * offered flags are false for both, so leave both globals alone. */ + if (skip_fmv_offered) g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0; + if (turbo_loads_offered) g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0; g_fullscreen = ls.fullscreen != 0; g_frame_interpolation = ls.frame_interp ? 1 : 0; g_frame_interpolation_fps = ls.frame_interp_fps; diff --git a/runtime/tests/test_mod_load_acceleration.py b/runtime/tests/test_mod_load_acceleration.py index b12d34a0..633aa727 100644 --- a/runtime/tests/test_mod_load_acceleration.py +++ b/runtime/tests/test_mod_load_acceleration.py @@ -13,16 +13,43 @@ CONFIG_CPP = (ROOT / "recompiler/src/config_loader.cpp").read_text( ) assert "psx_mod_set_load_acceleration" in HEADER -assert "wall_clock_multiplier accepts 2..16" in HEADER +# The accepted range was widened from 2..16 to 1..PSX_MOD_LOAD_ACCEL_MAX (the +# bound is there to reject nonsense, not to curate "blessed" speeds), so pin the +# contract by its symbolic bound rather than the old literals. +assert "wall_clock_multiplier accepts 1..PSX_MOD_LOAD_ACCEL_MAX" in HEADER +assert "#define PSX_MOD_LOAD_ACCEL_MAX" in HEADER assert "zero is the precise/speedrun-safe policy" in HEADER assert "psx_mod_set_disc_speed" in HEADER assert "this changes" in HEADER -assert "bool offer_turbo_loads = true;" in CONFIG_H +# The legacy generic Turbo loads switch is retired: acceleration is mod-only. +# Both config keys are still PARSED (so old game.toml/settings.toml load), but +# neither is honoured, and no title can offer the generic switch any more. +assert "bool offer_turbo_loads = false;" in CONFIG_H +assert "DEPRECATED AND IGNORED" in CONFIG_H assert 'runtime.contains("offer_turbo_loads")' in CONFIG_CPP -assert "turbo_loads_offered = gc.runtime.offer_turbo_loads;" in MAIN +assert "rt.has_turbo_loads = true;" in CONFIG_CPP +assert "constexpr bool turbo_loads_offered = false;" in MAIN +assert "turbo_loads_offered = gc.runtime.offer_turbo_loads;" not in MAIN assert "gi.has_turbo_loads = turbo_loads_offered ? 1 : 0;" in MAIN -assert "Turbo loads is mod-owned for this title" in MAIN +assert "Turbo loads is mod-owned on PSX" in MAIN + +# game.toml [runtime] turbo_loads must not enable anything, only warn. +assert "g_turbo_loads_enabled = 1;\n std::fprintf" not in MAIN +assert '"psxrecomp: game.toml [runtime] %s%s%s is DEPRECATED and "' in MAIN +assert '"Fast Loading (host pacing)\\" mod' in MAIN + +# A stale settings.toml value is neither restored nor written back out, so it +# cannot latch: the launcher draws no row for it, which made a persisted value +# both authoritative and unreachable (MegaManX6Recomp#14). +assert "g_turbo_loads_enabled = us.turbo_loads" not in MAIN +assert "settings.toml [video] turbo_loads = true is " in MAIN +assert 'f << "turbo_loads = "' not in CONFIG_CPP + +# The in-game Settings apply path reads a launcher snapshot taken BEFORE mod +# activation, so it must not write either mod-owned global. +assert "if (turbo_loads_offered) g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0;" in MAIN +assert "if (skip_fmv_offered) g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0;" in MAIN reset = """g_mod_load_wall_multiplier = -1; g_mod_load_release_frames = -1;"""