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>
This commit is contained in:
Matthew Stanley
2026-08-10 18:52:16 -07:00
parent e6ab9a6a0f
commit f0cfa92afa
6 changed files with 164 additions and 45 deletions
+9 -3
View File
@@ -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.
+25 -6
View File
@@ -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.
+12 -2
View File
@@ -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<bool>(runtime, "turbo_loads");
rt.has_turbo_loads = true;
}
if (runtime.contains("offer_turbo_loads")) {
rt.offer_turbo_loads =
toml::find<bool>(runtime, "offer_turbo_loads");
rt.has_offer_turbo_loads = true;
}
if (runtime.contains("turbo_audio_sink")) {
rt.turbo_audio_sink = toml::find<bool>(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<bool>(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<bool>(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)
+32 -16
View File
@@ -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;
+55 -14
View File
@@ -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;
+31 -4
View File
@@ -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;"""