fix(menu): keep render scale when switching upscalers #55

Open
mooki wants to merge 6 commits from mooki/Longshot:fix/upscaler-scale-after-toggle into main
First-time contributor

Why

Switching the Upscaler away from DLSS and back should not silently lower the chosen Render resolution. On current Longshot main, Balanced 58% still returns as Performance 50%; this change keeps the player's choice.

What changed

  • Menu implementation: read set_row(), the scale text/help and effective getter in gpu/vk/vk_menu.cpp, and snap_render() in gpu/vk/upscaler_rows.h; persistence is specified in docs/control.md.
  • Keep the saved numeric scale instead of overwriting it with the temporary engine's nearest supported size. The menu text, help and active render-size getter resolve that fallback when needed.
  • A deliberate Render resolution change or a non-Custom Quality preset still replaces the choice. No new saved setting, per-engine history, renderer interface or default.
  • The control bridge returns the saved value separately from the effective text; this distinction is documented.
  • Retain the original regression test and both later fixture repairs: real Auto-preset state/handlers, and an assertion-bearing guard for unrelated live Debug actions. The current fixture exercises 2,042 checks, including arrows, bridge setters/getters, saving/reloading, every numeric scale and Auto handover.
  • Record all four new Longshot runs in the tested-configurations table. The earlier Hookshot run remains labelled with its original build.

Flags and settings

No new switch, build flag, command-line flag or saved key is introduced. These existing settings change how a temporary fallback is used; first-start Auto defaults and deliberate preset choices are unchanged.

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
Upscaler Changed interaction: Switching engines no longer overwrites the saved render scale Existing saved/Auto-selected engine, unchanged On by default: do not silently discard the player's choice Switching between engines with different supported scales Does not fix the original area's persistent FPS drop or remember DLAA mode FSR runtime, AMD/Intel, Windows gameplay (should help on: Other supported engines, covered game-free)
dlss Existing ini key; behavior changed, format unchanged: Keeps the selected engine without normalizing resolution into it 0 in Options; existing first-start Auto can choose another engine On by default: stable named-key persistence Saved upscaler selections No new engine or native-mode history Windows gameplay persistence (should help on: Other supported PCs)
Render resolution Changed interpretation: Display/use the active engine's supported size while retaining the numeric preference; arrows start at the shown size Existing saved choice or Auto, unchanged On by default: preserve the requested scale until a deliberate scale/preset change Balanced 58% temporarily using Game's own 50%, and other unsupported stops Does not add new selectable scale stops Physical UI input, other GPU vendors (should help on: Other supported engines, covered game-free)
resolution Existing ini key; behavior changed, format unchanged: Stores the numeric preference, not the temporary fallback; bridge value is saved and text is effective 0 Auto before existing profile policy, or the saved value On by default: retain choice across save/reload Players changing upscalers Quality presets intentionally replace it; Native mode memory unchanged Actual restart in the Longshot game; Windows gameplay (should help on: Restart is covered by the compiled real-source fixture)

Measurements

Branch state: on main ef8b784faba4944fe3cfa07daf20b28140251011; head a3fdd78b7049a56f7d083baa34d35c9a67fe0f61; whole game-free suite on this head: 511 passed, 1 failed, 20 skipped (main ef8b784faba4944fe3cfa07daf20b28140251011: 510 passed, 1 failed, 20 skipped); the commits are unchanged by rebases apart from the results table.

main: d4a69456 (2026-11-12)
this PR: fix/upscaler-scale-after-toggle @ 01ee6ca7
Hardware: RTX 4090 24 GB, NVIDIA 615.78.08; Core i9-13900K, 96 GB; Arch Linux, kernel 7.2.15-1-omarchy
Scene: copied Hyrule Field save, standing in the same place; 1920×1080, Custom quality preset, DLSS model M, Balanced 58%, frame generation off, cap 60, shader precompile off; windowless
Method: two interleaved pairs, main → branch → main → branch, 158.5–160.6 seconds per run, on 2026-11-12. Each loaded the same save and waited for controllable play plus 60 presents before setting the rows. Read back the actual menu options before, while using Game's own, and after returning to DLSS; capture the menu at each stage. Ordinary shared runs: correctness evidence, not a speed comparison.
How to reproduce: on a Hyrule Field save, select Custom, DLSS model M and Balanced 58% at 1920×1080; switch Upscaler to Game's own, then back to DLSS without changing Render resolution. Read the row or control-bridge option_get after each change. Run python -I -B tests/test_upscaler_round_trip.py --cxx clang++ for the real-source regression.

The original case still reproduces exactly on the forge's main. The newer in-play rows and Auto preset do not remove it. These runs explicitly used Custom, so Auto was not selecting a different scale. Auto fallback/restoration and surrendering control after a manual selection are covered separately by the real-source fixture.

main @ d4a69456 this PR @ 01ee6ca7
Returned scale, run 1 / run 2 50% / 50% 58% / 58%
Median returned scale 50% 58%
Median lost percentage points 8 0
Saved resolution through DLSS → Game's own → DLSS, both runs 5 → 4 → 4 5 → 5 → 5
Round-trip fixture failures, two interleaved runs each 376 / 376 of 2,042 0 / 0 of 2,042
Median fixture failures 376 0
Whole game-free suite, this comparison 508 passed, 1 failed, 20 skipped 509 passed, 1 failed, 20 skipped

The option readbacks below are extracted verbatim by field from the saved replies. stored is the reply's value; text is what the menu displays:

run=1 arm=base phase=before frame=1205 stored=5 text=Balanced 58% (1120x630)
run=1 arm=base phase=away frame=3240 stored=4 text=50% (960x540)
run=1 arm=base phase=after frame=4322 stored=4 text=Performance 50% (960x540)
run=2 arm=branch phase=before frame=1139 stored=5 text=Balanced 58% (1120x630)
run=2 arm=branch phase=away frame=2945 stored=5 text=50% (960x540)
run=2 arm=branch phase=after frame=4149 stored=5 text=Balanced 58% (1120x630)
run=3 arm=base phase=before frame=1363 stored=5 text=Balanced 58% (1120x630)
run=3 arm=base phase=away frame=6067 stored=4 text=50% (960x540)
run=3 arm=base phase=after frame=7271 stored=4 text=Performance 50% (960x540)
run=4 arm=branch phase=before frame=1344 stored=5 text=Balanced 58% (1120x630)
run=4 arm=branch phase=away frame=5886 stored=5 text=50% (960x540)
run=4 arm=branch phase=after frame=6724 stored=5 text=Balanced 58% (1120x630)

The first pair's actual gameplay sizes agree with those rows. After returning to DLSS:

main:
[hos Presentation Thread] vk: upscaler: network 72ba090c70fe2614 is the gameplay upscale (960x540 -> 1920x1080)
branch:
[hos Presentation Thread] vk: upscaler: network f10782f9e58b6a7c is the gameplay upscale (1120x630 -> 1920x1080)
Run order Build Duration Scale after return One-minute load, start → end Result
1 main d4a69456 159.5 s 50% 65.07 → 22.22 exit 0; bug reproduced
2 branch 01ee6ca7 158.8 s 58% 49.39 → 29.49 exit 0; choice kept
3 main d4a69456 158.5 s 50% 49.86 → 17.46 exit 0; bug reproduced
4 branch 01ee6ca7 160.6 s 58% 47.26 → 42.85 exit 0; choice kept

All four runs had zero fatal/crash/device-loss/core-escape/kernel-fault counts, with complete kernel-journal coverage. Both binaries in each arm were built from that arm's source, using the same release profile and the already-installed NVIDIA SDK. No SDK was downloaded or licence accepted for this move.

The complete suite used the repository's default compiler (Clang 23.1.3), a fresh HOME and strict prerequisite checking. Its only failure on both arms was tests/test_updater_asset_digest.py: the installed .NET SDK cannot resolve Microsoft.AspNetCore.App.Ref (= 10.0.13) offline (NU1100). This is an inherited local prerequisite failure, not a passing test.

main:   508 passed, 1 failed, 20 skipped in 476 s (linux, 6 at once)
branch: 509 passed, 1 failed, 20 skipped in 314 s (linux, 6 at once)
main:   upscaler round trip: 2042 checks, 376 failures
branch: upscaler round trip: 2042 checks, 0 failures
main:   upscaler round trip: 2042 checks, 376 failures
branch: upscaler round trip: 2042 checks, 0 failures
PASS: 5 captured translation units, 0 failures, 0 unjudged drivers/files; Windows execution NOT TESTED

The day's shared baseline suite on the same main commit was 507 passed, 2 failed, 20 skipped: the same .NET failure and a timing-dependent test_appimage_package_retry.py failure under heavy load. Its one isolated follow-up passed all nine cases. The already-completed main suite above and this branch's suite both passed that AppImage test; neither the shared failure nor the differing raw totals is hidden. Forgejo has no CI runner.

The original published head added only the four evidence rows after built/tested code 01ee6ca7d947ec99718a75635ce972385a577133. The base update used for this branch changed only scripts, docs and tests, not game code, and all six patches are unchanged. The measured commits above remain the actual game builds; their observations still hold. Later base refreshes are handled when the PR is marked ready. No additional game run or speed measurement is claimed.

The whole game-free suite ran on the exact head in the branch-state block, including its documentation rows; the baseline is the suite run once on that main for all of these pull requests. The only failure on both is tests/test_updater_asset_digest.py, the local .NET reference-pack failure described above. Earlier results above retain their actual measured commits.

Testing

Tested: on the RTX 4090/NVIDIA 615.78.08, Core i9-13900K/96 GB and Arch Linux system named above: the whole game-free suite, the real-source round-trip fixture against both commits, Windows-target syntax for five fixture units, and two interleaved windowless Hyrule Field runs per build. The real menu/bridge option values and gameplay-size log lines show the lost preference on main and its preservation here. Menu and gameplay appearance were also inspected without a visual-quality or menu-overhead measurement; no speed claim.

Not tested: the following coverage remains unrun because these checks used this Linux/NVIDIA PC, windowless automation and the stated saved scene; syntax is not Windows execution. These limitations are not represented as additional runs:

  • Windows gameplay or a complete Windows build. The five changed C/C++ fixture units passed Windows-target syntax; their Windows execution was not repeated after the rename.
  • A real window, physical menu input, AMD/Intel GPUs, FSR runtime, other scenes, or restarting the game while another engine is selected. Save/reload is covered by the game-free fixture.
  • Auto's live tuning on this hardware; these game runs deliberately used Custom.
  • FPS gains or the underlying persistent frame-rate drop in the original report. No test request has been opened on the new forge for the remaining platforms.
  • Native/DLAA mode memory remains unchanged: DLAA → Game's own → DLSS is 2 → 0 → 1. Remembering the last native-versus-scaled engine mode is a separate maintainer decision, not part of this numeric-scale fix.

Needs other testers:
None. The change is the menu's saved-scale choice and fallback in shared code, with no shader, device or platform path changed; every engine-capability combination is covered by the fixture and the DLSS round trip was run in the game. Windows, AMD and the FSR runtime remain explicitly unrun above.

Screenshots

main @ d4a69456 versus this PR @ 01ee6ca7. New Longshot builds, same saved scene after the same toggle: main returns at Performance 50%; the branch returns at Balanced 58%. Both source images and this panel were inspected.

Earlier Hookshot evidence from 2026-11-12, build v2.1.0-rc.3-313-g1c47586f0, source 9806dded: the before menu and after gameplay. Its missing after-menu capture was a test-driver assertion error; it is not a new Longshot run or an after-menu picture. The new panel above supplies that missing view.

Risk and rollback

A saved numeric preference can differ from the active engine's fallback: tools must use the bridge's text or the effective scale getter rather than treat value as the active pixel ratio. Deliberately changing Render resolution or a quality preset still replaces the preference. Native/DLAA mode memory and the original area's FPS drop remain out of scope. There is no new off switch; reverting the change restores the previous snap-on-switch behavior, with no options-file schema migration.

Reviewer notes

Start with set_row(), tx_resolution(), hn_resolution() and resolution_scale() in gpu/vk/vk_menu.cpp, then the removed special repair in menu_setting_set(). The real-source round-trip fixture preserves its assertions, adds Auto ownership/fallback coverage, and fails if an unrelated Debug renderer action is called. The earlier Hookshot evidence and the two port-worker fixture repairs are retained rather than relabelled as a new Windows execution.

Issues

Original report: https://github.com/NotRexed/Hookshot/issues/2780. This addresses the scale lost during its upscaler workaround, not the whole performance report; it does not close it.

Original pull request: https://github.com/NotRexed/Hookshot/issues/2916. Its archived discussion and review lists are empty; no formal review request is being dropped.

On this forge: See #89 (the whole performance report, which this does not close). The separate DLSS-model report on the forge is not this saved-scale bug. No old GitHub issue number is used as a forge closing reference.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README). — Existing named-key behavior is documented in README and docs/control.md; no new switch.
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.

Moved from NotRexed/Hookshot#2916.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]

## Why Switching the Upscaler away from DLSS and back should not silently lower the chosen Render resolution. On current Longshot main, Balanced 58% still returns as Performance 50%; this change keeps the player's choice. ## What changed - Menu implementation: read `set_row()`, the scale text/help and effective getter in `gpu/vk/vk_menu.cpp`, and `snap_render()` in `gpu/vk/upscaler_rows.h`; persistence is specified in `docs/control.md`. - Keep the saved numeric scale instead of overwriting it with the temporary engine's nearest supported size. The menu text, help and active render-size getter resolve that fallback when needed. - A deliberate Render resolution change or a non-Custom Quality preset still replaces the choice. No new saved setting, per-engine history, renderer interface or default. - The control bridge returns the saved `value` separately from the effective `text`; this distinction is documented. - Retain the original regression test and both later fixture repairs: real Auto-preset state/handlers, and an assertion-bearing guard for unrelated live Debug actions. The current fixture exercises 2,042 checks, including arrows, bridge setters/getters, saving/reloading, every numeric scale and Auto handover. - Record all four new Longshot runs in the tested-configurations table. The earlier Hookshot run remains labelled with its original build. ## Flags and settings No new switch, build flag, command-line flag or saved key is introduced. These existing settings change how a temporary fallback is used; first-start Auto defaults and deliberate preset choices are unchanged. | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | Upscaler | Changed interaction: Switching engines no longer overwrites the saved render scale | Existing saved/Auto-selected engine, unchanged | On by default: do not silently discard the player's choice | Switching between engines with different supported scales | Does not fix the original area's persistent FPS drop or remember DLAA mode | FSR runtime, AMD/Intel, Windows gameplay (should help on: Other supported engines, covered game-free) | | `dlss` | Existing ini key; behavior changed, format unchanged: Keeps the selected engine without normalizing `resolution` into it | 0 in Options; existing first-start Auto can choose another engine | On by default: stable named-key persistence | Saved upscaler selections | No new engine or native-mode history | Windows gameplay persistence (should help on: Other supported PCs) | | Render resolution | Changed interpretation: Display/use the active engine's supported size while retaining the numeric preference; arrows start at the shown size | Existing saved choice or Auto, unchanged | On by default: preserve the requested scale until a deliberate scale/preset change | Balanced 58% temporarily using Game's own 50%, and other unsupported stops | Does not add new selectable scale stops | Physical UI input, other GPU vendors (should help on: Other supported engines, covered game-free) | | `resolution` | Existing ini key; behavior changed, format unchanged: Stores the numeric preference, not the temporary fallback; bridge value is saved and text is effective | 0 Auto before existing profile policy, or the saved value | On by default: retain choice across save/reload | Players changing upscalers | Quality presets intentionally replace it; Native mode memory unchanged | Actual restart in the Longshot game; Windows gameplay (should help on: Restart is covered by the compiled real-source fixture) | ## Measurements <!-- branch-state --> Branch state: on main `ef8b784faba4944fe3cfa07daf20b28140251011`; head `a3fdd78b7049a56f7d083baa34d35c9a67fe0f61`; whole game-free suite on this head: `511 passed, 1 failed, 20 skipped` (main `ef8b784faba4944fe3cfa07daf20b28140251011`: `510 passed, 1 failed, 20 skipped`); the commits are unchanged by rebases apart from the results table. <!-- /branch-state --> main: d4a69456 (2026-11-12) this PR: fix/upscaler-scale-after-toggle @ 01ee6ca7 Hardware: RTX 4090 24 GB, NVIDIA 615.78.08; Core i9-13900K, 96 GB; Arch Linux, kernel 7.2.15-1-omarchy Scene: copied Hyrule Field save, standing in the same place; 1920×1080, Custom quality preset, DLSS model M, Balanced 58%, frame generation off, cap 60, shader precompile off; windowless Method: two interleaved pairs, main → branch → main → branch, 158.5–160.6 seconds per run, on 2026-11-12. Each loaded the same save and waited for controllable play plus 60 presents before setting the rows. Read back the actual menu options before, while using Game's own, and after returning to DLSS; capture the menu at each stage. Ordinary shared runs: correctness evidence, not a speed comparison. How to reproduce: on a Hyrule Field save, select Custom, DLSS model M and Balanced 58% at 1920×1080; switch Upscaler to Game's own, then back to DLSS without changing Render resolution. Read the row or control-bridge option_get after each change. Run `python -I -B tests/test_upscaler_round_trip.py --cxx clang++` for the real-source regression. **The original case still reproduces exactly on the forge's main.** The newer in-play rows and Auto preset do not remove it. These runs explicitly used Custom, so Auto was not selecting a different scale. Auto fallback/restoration and surrendering control after a manual selection are covered separately by the real-source fixture. | | main @ d4a69456 | this PR @ 01ee6ca7 | |---|---|---| | Returned scale, run 1 / run 2 | 50% / 50% | 58% / 58% | | Median returned scale | 50% | 58% | | Median lost percentage points | 8 | 0 | | Saved resolution through DLSS → Game's own → DLSS, both runs | 5 → 4 → 4 | 5 → 5 → 5 | | Round-trip fixture failures, two interleaved runs each | 376 / 376 of 2,042 | 0 / 0 of 2,042 | | Median fixture failures | 376 | 0 | | Whole game-free suite, this comparison | 508 passed, 1 failed, 20 skipped | 509 passed, 1 failed, 20 skipped | The option readbacks below are extracted verbatim by field from the saved replies. `stored` is the reply's `value`; `text` is what the menu displays: ```text run=1 arm=base phase=before frame=1205 stored=5 text=Balanced 58% (1120x630) run=1 arm=base phase=away frame=3240 stored=4 text=50% (960x540) run=1 arm=base phase=after frame=4322 stored=4 text=Performance 50% (960x540) run=2 arm=branch phase=before frame=1139 stored=5 text=Balanced 58% (1120x630) run=2 arm=branch phase=away frame=2945 stored=5 text=50% (960x540) run=2 arm=branch phase=after frame=4149 stored=5 text=Balanced 58% (1120x630) run=3 arm=base phase=before frame=1363 stored=5 text=Balanced 58% (1120x630) run=3 arm=base phase=away frame=6067 stored=4 text=50% (960x540) run=3 arm=base phase=after frame=7271 stored=4 text=Performance 50% (960x540) run=4 arm=branch phase=before frame=1344 stored=5 text=Balanced 58% (1120x630) run=4 arm=branch phase=away frame=5886 stored=5 text=50% (960x540) run=4 arm=branch phase=after frame=6724 stored=5 text=Balanced 58% (1120x630) ``` The first pair's actual gameplay sizes agree with those rows. After returning to DLSS: ```text main: [hos Presentation Thread] vk: upscaler: network 72ba090c70fe2614 is the gameplay upscale (960x540 -> 1920x1080) branch: [hos Presentation Thread] vk: upscaler: network f10782f9e58b6a7c is the gameplay upscale (1120x630 -> 1920x1080) ``` | Run order | Build | Duration | Scale after return | One-minute load, start → end | Result | |---|---|---:|---:|---|---| | 1 | main `d4a69456` | 159.5 s | 50% | 65.07 → 22.22 | exit 0; bug reproduced | | 2 | branch `01ee6ca7` | 158.8 s | 58% | 49.39 → 29.49 | exit 0; choice kept | | 3 | main `d4a69456` | 158.5 s | 50% | 49.86 → 17.46 | exit 0; bug reproduced | | 4 | branch `01ee6ca7` | 160.6 s | 58% | 47.26 → 42.85 | exit 0; choice kept | All four runs had zero fatal/crash/device-loss/core-escape/kernel-fault counts, with complete kernel-journal coverage. Both binaries in each arm were built from that arm's source, using the same release profile and the already-installed NVIDIA SDK. No SDK was downloaded or licence accepted for this move. The complete suite used the repository's default compiler (Clang 23.1.3), a fresh HOME and strict prerequisite checking. Its only failure on both arms was `tests/test_updater_asset_digest.py`: the installed .NET SDK cannot resolve `Microsoft.AspNetCore.App.Ref (= 10.0.13)` offline (`NU1100`). This is an inherited local prerequisite failure, not a passing test. ```text main: 508 passed, 1 failed, 20 skipped in 476 s (linux, 6 at once) branch: 509 passed, 1 failed, 20 skipped in 314 s (linux, 6 at once) main: upscaler round trip: 2042 checks, 376 failures branch: upscaler round trip: 2042 checks, 0 failures main: upscaler round trip: 2042 checks, 376 failures branch: upscaler round trip: 2042 checks, 0 failures PASS: 5 captured translation units, 0 failures, 0 unjudged drivers/files; Windows execution NOT TESTED ``` The day's shared baseline suite on the same main commit was **507 passed, 2 failed, 20 skipped**: the same .NET failure and a timing-dependent `test_appimage_package_retry.py` failure under heavy load. Its one isolated follow-up passed all nine cases. The already-completed main suite above and this branch's suite both passed that AppImage test; neither the shared failure nor the differing raw totals is hidden. Forgejo has no CI runner. The original published head added only the four evidence rows after built/tested code `01ee6ca7d947ec99718a75635ce972385a577133`. The base update used for this branch changed only scripts, docs and tests, not game code, and all six patches are unchanged. The measured commits above remain the actual game builds; their observations still hold. Later base refreshes are handled when the PR is marked ready. No additional game run or speed measurement is claimed. The whole game-free suite ran on the exact head in the branch-state block, including its documentation rows; the baseline is the suite run once on that main for all of these pull requests. The only failure on both is `tests/test_updater_asset_digest.py`, the local .NET reference-pack failure described above. Earlier results above retain their actual measured commits. ## Testing **Tested:** on the RTX 4090/NVIDIA 615.78.08, Core i9-13900K/96 GB and Arch Linux system named above: the whole game-free suite, the real-source round-trip fixture against both commits, Windows-target syntax for five fixture units, and two interleaved windowless Hyrule Field runs per build. The real menu/bridge option values and gameplay-size log lines show the lost preference on main and its preservation here. Menu and gameplay appearance were also inspected without a visual-quality or menu-overhead measurement; no speed claim. **Not tested:** the following coverage remains unrun because these checks used this Linux/NVIDIA PC, windowless automation and the stated saved scene; syntax is not Windows execution. These limitations are not represented as additional runs: - Windows gameplay or a complete Windows build. The five changed C/C++ fixture units passed Windows-target syntax; their Windows execution was not repeated after the rename. - A real window, physical menu input, AMD/Intel GPUs, FSR runtime, other scenes, or restarting the game while another engine is selected. Save/reload is covered by the game-free fixture. - Auto's live tuning on this hardware; these game runs deliberately used Custom. - FPS gains or the underlying persistent frame-rate drop in the original report. No test request has been opened on the new forge for the remaining platforms. - Native/DLAA mode memory remains unchanged: DLAA → Game's own → DLSS is 2 → 0 → 1. Remembering the last native-versus-scaled engine mode is a separate maintainer decision, not part of this numeric-scale fix. **Needs other testers:** None. The change is the menu's saved-scale choice and fallback in shared code, with no shader, device or platform path changed; every engine-capability combination is covered by the fixture and the DLSS round trip was run in the game. Windows, AMD and the FSR runtime remain explicitly unrun above. ## Screenshots ![main @ d4a69456 versus this PR @ 01ee6ca7. New Longshot builds, same saved scene after the same toggle: main returns at Performance 50%; the branch returns at Balanced 58%. Both source images and this panel were inspected.](https://git.magicbean.dev/attachments/d0414f6b-cb82-415c-abd2-0c6afcd76d65) ![Earlier Hookshot evidence from 2026-11-12, build `v2.1.0-rc.3-313-g1c47586f0`, source `9806dded`: the before menu and after gameplay. Its missing after-menu capture was a test-driver assertion error; it is not a new Longshot run or an after-menu picture. The new panel above supplies that missing view.](https://git.magicbean.dev/attachments/3bd2256e-aa00-42ad-adeb-8411da503ae7) ## Risk and rollback A saved numeric preference can differ from the active engine's fallback: tools must use the bridge's text or the effective scale getter rather than treat value as the active pixel ratio. Deliberately changing Render resolution or a quality preset still replaces the preference. Native/DLAA mode memory and the original area's FPS drop remain out of scope. There is no new off switch; reverting the change restores the previous snap-on-switch behavior, with no options-file schema migration. ## Reviewer notes Start with `set_row()`, `tx_resolution()`, `hn_resolution()` and `resolution_scale()` in `gpu/vk/vk_menu.cpp`, then the removed special repair in `menu_setting_set()`. The real-source round-trip fixture preserves its assertions, adds Auto ownership/fallback coverage, and fails if an unrelated Debug renderer action is called. The earlier Hookshot evidence and the two port-worker fixture repairs are retained rather than relabelled as a new Windows execution. ## Issues Original report: https://github.com/NotRexed/Hookshot/issues/2780. This addresses the scale lost during its upscaler workaround, **not** the whole performance report; it does not close it. Original pull request: https://github.com/NotRexed/Hookshot/issues/2916. Its archived discussion and review lists are empty; no formal review request is being dropped. On this forge: See #89 (the whole performance report, which this does not close). The separate DLSS-model report on the forge is not this saved-scale bug. No old GitHub issue number is used as a forge closing reference. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). — Existing named-key behavior is documented in README and docs/control.md; no new switch. - [x] Screenshots for a change that shows on screen. - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated. --- Moved from NotRexed/Hookshot#2916. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <[email protected]>
Keep the saved scale separate from the active engine’s nearest supported size. Cover menu arrows, the control bridge, presets and options saved while another engine is selected.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Compile the real bridge getters and assert the stored choice separately from the active size. Keep Auto through every engine and options persistence, including a run-only override.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Record the option values and actual input-size logs from the single windowless run, including the test driver’s premature stop and missing after-return menu picture.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Define the Auto preset's persistence state with no active hardware profile for
manual scale round trips. Extract the preset constant and real Auto handlers
instead of omitting their dependencies from the compiled menu slices.

Cover Auto's fallback scale restoration and its handover to Custom through
both menu arrows and the control bridge, including saved tuning metadata
and restart. Keep the existing round-trip assertions unchanged.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
The round trip already selects only four menu rows and exact function definitions. Its complete shared set_row still contains neighbouring descriptor/sampler dispatch, so the fixture depends on that sliced region’s neighbours. Removing that branch from the extracted function would stop testing the real function; moving it needs product refactoring outside this fixture fix.

Provide the real hook signature with an assertion-bearing stand-in: every call fails the fixture. The 2042 passing checks show the round-trip and Auto cases do not invoke that unrelated renderer action. This dependency is Longshot-only at 7afe6ae1; the GitHub counterpart on e0e609 does not call the hook and remains unchanged.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Record two interleaved main/branch comparisons with the real Longshot builds, menu values and inspected captures. Keep the earlier Hookshot evidence labelled separately.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
mooki changed title from fix(menu): keep render scale when switching upscalers to WIP: fix(menu): keep render scale when switching upscalers 2026-11-13 00:58:55 +00:00
Author
First-time contributor

Back to work in progress, for its description only. This was opened shortly before #48 merged and follows the earlier template, so it does not pass the new description check (no Flags and settings and no Risk and rollback section, table columns named Before and After). The code and the measurements are unchanged: the whole suite passes on this exact commit apart from one local .NET failure that also fails on main. The description is being moved to the new template and the branch rebased onto d725b363; it comes back through pr ready once the check passes.

Back to work in progress, for its description only. This was opened shortly before #48 merged and follows the earlier template, so it does not pass the new description check (no Flags and settings and no Risk and rollback section, table columns named Before and After). The code and the measurements are unchanged: the whole suite passes on this exact commit apart from one local .NET failure that also fails on main. The description is being moved to the new template and the branch rebased onto d725b363; it comes back through `pr ready` once the check passes.
Author
First-time contributor

Updated the description to the newest template, including Testing, the complete checklist, settings/defaults, risk/rollback and reviewer notes, while keeping the uploaded pictures and all earlier evidence. The branch-state block records the tested rebase and its exact-head whole suite, with only the same local .NET failure as main. The game evidence keeps its measured commit labels because that completed base update changed no game code.

Rebased onto main ef8b784f for this update: the only conflict was the results table, where both sides' rows are kept; the commits are otherwise unchanged. Whole suite on a3fdd78b: 511 passed, 1 failed, 20 skipped; the one failure is the local .NET reference pack that also fails on main.

Updated the description to the newest template, including Testing, the complete checklist, settings/defaults, risk/rollback and reviewer notes, while keeping the uploaded pictures and all earlier evidence. The branch-state block records the tested rebase and its exact-head whole suite, with only the same local .NET failure as main. The game evidence keeps its measured commit labels because that completed base update changed no game code. Rebased onto main `ef8b784f` for this update: the only conflict was the results table, where both sides' rows are kept; the commits are otherwise unchanged. Whole suite on `a3fdd78b`: 511 passed, 1 failed, 20 skipped; the one failure is the local .NET reference pack that also fails on main.
mooki force-pushed fix/upscaler-scale-after-toggle from 065d3b1714 to a3fdd78b70
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 2s
2026-11-13 01:59:35 +00:00
Compare
mooki changed title from WIP: fix(menu): keep render scale when switching upscalers to fix(menu): keep render scale when switching upscalers 2026-11-13 01:59:35 +00:00
Author
First-time contributor

Wording only: removed sentences about how my local test tooling ran the suite; they were of no use to a reviewer. The results are unchanged.

Wording only: removed sentences about how my local test tooling ran the suite; they were of no use to a reviewer. The results are unchanged.
All checks were successful
PR description / Description follows the template (pull_request_target) Successful in 2s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'fix/upscaler-scale-after-toggle:mooki-fix/upscaler-scale-after-toggle'
git switch 'mooki-fix/upscaler-scale-after-toggle'
Sign in to join this conversation.
No description provided.