From f0cfa92afa10872878bb84adbb1ef8f7cbebd9c2 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 10 Aug 2026 18:52:16 -0700 Subject: [PATCH] =?UTF-8?q?config:=20retire=20the=20legacy=20generic=20tur?= =?UTF-8?q?bo=5Floads=20switch=20=E2=80=94=20load=20acceleration=20is=20mo?= =?UTF-8?q?d-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 12 +++- docs/config_schema.md | 31 +++++++-- recompiler/src/config_loader.cpp | 14 ++++- recompiler/src/config_loader.h | 48 +++++++++----- runtime/src/main.cpp | 69 ++++++++++++++++----- runtime/tests/test_mod_load_acceleration.py | 35 +++++++++-- 6 files changed, 164 insertions(+), 45 deletions(-) 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;"""