feat(options): settings in the launcher's user folder, so an update keeps them #729

Merged
HumanBean merged 1 commit from dyst/Longshot:feat/user-settings into main 2026-11-15 08:12:12 +00:00
Member

Type: feat · Risk: medium · Tested on: Windows 11 and Linux (WSL Ubuntu), game-free checks only · Needs: Windows, any GPU (a launcher update in the game); Linux after the launcher PR

Why

After an update, Longshot starts from the default settings: the Insert menu's choices and the controls are lost. The launcher puts each version in a folder of its own, and longshot_options.ini was kept beside the game, so the new folder had none. Saves didn't have this problem because they were already shared through the launcher's user folder. This puts the settings there too, so every build the launcher starts reads and writes the same file.

What changed

The settings file now follows --user, the user folder the launcher already gives the game for the saves (%APPDATA%\Longshot on Windows, a portable launcher's own folder). Without --user (RecompLauncher, which sets its rows in the game folder's file and updates in place, a shortcut, the tools) it stays beside the game, as before. The first time a build runs this way, a game folder's file from before is copied into the user folder once.

  • host: host/options_path.c (new) chooses the file once at the start: HOS_OPTIONS_FILE, else --user's folder (relative paths resolved the way the game itself resolves them), else beside the game. It has no side effects, so the crash window's helper and the Discord status, which start before the saves are set up, read the same file.
    • hos_options_carry() runs after the user folder is made. It logs options: the settings file is <path>, then copies the game folder's file into the user folder when the user folder has none. A file holding only compile_shaders=, which make_game.py writes into a new folder, is skipped. A user folder that can't be written keeps the game folder's file for that run.
    • Every host reader now opens hos_options_file(): hos_main.c (Aspect ratio), nn_applet.c (hos_ini_option), nn_audio.c, nn_hid.c (controls), prompts.c, discord_rpc.c.
  • renderer: MkwVkHost.options_path (new, MKWVK_API_VERSION 23); options_path() in gpu/vk/vk_paths.h / vk_main.cpp, used by ini_path() (vk_menu.cpp), menu_env_on (vk_draw.cpp) and the mesh and Robust buffers reads (vk_main.cpp). The bug report takes the game folder from game_folder_path() instead of from the settings path.
  • build choice: compile_shaders= is still read from the game folder's file (vk_draw.cpp compile_all), where make_game.py and the launcher write it.
  • crash window: the helper is given the run's settings file as an optional last argument (hos_crash_dialog.h hos_crash_args), so the report's "Changes from the defaults" is the right file. Without it, the game folder's is read as before.
  • tools: nx2/pipeline_share.py reads the file the log names (options_file), else the game folder's.
  • docs: README (where the files go, the menu section), docs/graphics-and-controls.md, docs/development.md (the options file, HOS_OPTIONS_FILE), docs/versioning.md.

Measurements

Nothing to measure: the settings file's location changes, not the frame. The checks under Testing are what count.

Flags and settings

One new switch for test runs; no menu row or ini key changes.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
HOS_OPTIONS_FILE=<path> The settings file a run reads and writes instead of the player's, as HOS_ENV_FILE does for longshot_env.txt. From the environment only (longshot_env.txt takes no paths) not there unset A scripted or A/B run brings its own settings and leaves the player's file alone Tested game-free (tests/test_options_path.py) Unset for players; for scripts and tests only

Testing

Tested:

  • tests/test_options_path.py (new) builds host/options_path.c as the game builds it, on Windows (llvm-mingw 20250402, Windows 11) and Linux (gcc, WSL Ubuntu 24.04), and runs it from a made-up game folder. It covers:
    • the order: HOS_OPTIONS_FILE, --user, beside the game;
    • relative paths, and --romfs / --compile-shaders taking their values;
    • choosing once;
    • the one-time copy: skipped for a compile_shaders=-only file, never over an existing file, not with HOS_OPTIONS_FILE, not when the file is already the game folder's;
    • a user folder that can't be written;
    • the log line.
  • python -I -B tests/run_all.py on Windows, branch and a clean main worktree (e090cc48) side by side: the same 17 failures on both, none new. They fail on main on this PC too: test_appimage_player_build, test_bake_portability, test_cpm_discovery, test_cpp_preflight, test_crash_dialog, test_env_on, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_readback_late, test_tested_configurations, test_vk_pgo, test_vram_budget_row, test_walk_key.
  • On Linux (WSL) the checks this PR touches pass: options_path, game_layout, bound_keys, ocarina_, output_resolution, audio_output_, menu_env_*, bug_report, pipeline_share, display_startup_failure, pad_layout. test_crash_dialog, test_pipeline_tour and test_walk_key fail there on clean main too.
  • test_crash_dialog stops on main at its first command-line check, before this PR's code. So the helper's new argument was checked with a standalone program against hos_crash_dialog.h (good, -, empty and one-too-many arguments; watch and show), and those cases were added to the test.
  • Syntax-only compiles of every changed host file, on Windows (llvm-mingw, x86_64-w64-mingw32) and Linux (gcc, POSIX shim). The changed renderer files (vk_main.cpp, vk_menu.cpp, vk_draw.cpp) with the build's flags on Windows: the same three __try errors as main from a flag my check lacks, nothing else.

Not tested:

  • No build and no run in the game yet: the one-time copy on a real update, the menu saving into %APPDATA%\Longshot, the crash window's report with the new argument.
  • Linux in the game: the Linux launcher doesn't pass --user yet, so on Linux the file stays beside the game until the launcher PR (below).

Must be tested:

  • Windows, any GPU: change a few Insert menu settings (Upscaler, Frame generation, a control) in a launcher-built game, update it to a build of this PR with the launcher, and start it from the launcher. Expect the same settings, %APPDATA%\Longshot\longshot_options.ini present, and options: the settings in ... were copied to ... in logs\longshot_log.txt. Then change a setting, start another build from the launcher, and expect the change there too (by hand).

Risks

What could break: the settings of a launcher-started build on Windows now come from %APPDATA%\Longshot. If the copy fails, the run keeps the game folder's file and logs why. Any reader of the file this PR missed would keep reading the game folder's file; grep finds no other in host/ and gpu/. RecompLauncher, direct starts and the tools are unchanged: without --user, the file is where it was.

Turning it off: revert the commit. For one run, HOS_OPTIONS_FILE=<game folder>\longshot_options.ini reads the old file.

Compatibility:

  • Settings: the first launcher start of this build copies the game folder's longshot_options.ini into %APPDATA%\Longshot, and from then on that file is used by every build the launcher starts; the game folder's copy is left in place and no longer read, apart from compile_shaders=. A build from before this PR started afterwards still reads its own folder's file.
  • Renderer interface: MKWVK_API_VERSION 22 → 23, so the exe and longshot_vk.dll must come from the same build.
  • Caches and saves: shader caches and saves are untouched.

Issues

See MagicBeanDev/LongshotLauncher#90 (settings lost between updates).

None found for this exact case. Related: LongshotLauncher#57 (an update in place), #224 (driver settings lost per update), #259 and #229 (several user profiles: these settings would move into a default profile).

Follow-ups, in their own PRs:

  • Launcher: pass --user ~/.local/share/longshot on Linux, and run a step on Play that brings an older build's settings file, the Windows per-app GPU preference and the NVIDIA profile over to a new build. Whether saving an NVIDIA profile needs admin is still an open question for that PR's discussion.
  • Game: the GPU device row saved by the card itself, not by its place in the list.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • 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.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • 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.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.

🤖 Generated with Claude Code

**Type:** feat · **Risk:** medium · **Tested on:** Windows 11 and Linux (WSL Ubuntu), game-free checks only · **Needs:** Windows, any GPU (a launcher update in the game); Linux after the launcher PR ## Why After an update, Longshot starts from the default settings: the Insert menu's choices and the controls are lost. The launcher puts each version in a folder of its own, and `longshot_options.ini` was kept beside the game, so the new folder had none. Saves didn't have this problem because they were already shared through the launcher's user folder. This puts the settings there too, so every build the launcher starts reads and writes the same file. ## What changed The settings file now follows `--user`, the user folder the launcher already gives the game for the saves (`%APPDATA%\Longshot` on Windows, a portable launcher's own folder). Without `--user` (RecompLauncher, which sets its rows in the game folder's file and updates in place, a shortcut, the tools) it stays beside the game, as before. The first time a build runs this way, a game folder's file from before is copied into the user folder once. - host: `host/options_path.c` (new) chooses the file once at the start: `HOS_OPTIONS_FILE`, else `--user`'s folder (relative paths resolved the way the game itself resolves them), else beside the game. It has no side effects, so the crash window's helper and the Discord status, which start before the saves are set up, read the same file. - `hos_options_carry()` runs after the user folder is made. It logs `options: the settings file is <path>`, then copies the game folder's file into the user folder when the user folder has none. A file holding only `compile_shaders=`, which `make_game.py` writes into a new folder, is skipped. A user folder that can't be written keeps the game folder's file for that run. - Every host reader now opens `hos_options_file()`: `hos_main.c` (Aspect ratio), `nn_applet.c` (`hos_ini_option`), `nn_audio.c`, `nn_hid.c` (controls), `prompts.c`, `discord_rpc.c`. - renderer: `MkwVkHost.options_path` (new, **`MKWVK_API_VERSION` 23**); `options_path()` in `gpu/vk/vk_paths.h` / `vk_main.cpp`, used by `ini_path()` (`vk_menu.cpp`), `menu_env_on` (`vk_draw.cpp`) and the mesh and Robust buffers reads (`vk_main.cpp`). The bug report takes the game folder from `game_folder_path()` instead of from the settings path. - build choice: `compile_shaders=` is still read from the game folder's file (`vk_draw.cpp` `compile_all`), where `make_game.py` and the launcher write it. - crash window: the helper is given the run's settings file as an optional last argument (`hos_crash_dialog.h` `hos_crash_args`), so the report's "Changes from the defaults" is the right file. Without it, the game folder's is read as before. - tools: `nx2/pipeline_share.py` reads the file the log names (`options_file`), else the game folder's. - docs: README (where the files go, the menu section), `docs/graphics-and-controls.md`, `docs/development.md` (the options file, `HOS_OPTIONS_FILE`), `docs/versioning.md`. ## Measurements Nothing to measure: the settings file's location changes, not the frame. The checks under Testing are what count. ### Flags and settings One new switch for test runs; no menu row or ini key changes. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `HOS_OPTIONS_FILE=<path>` | The settings file a run reads and writes instead of the player's, as `HOS_ENV_FILE` does for `longshot_env.txt`. From the environment only (`longshot_env.txt` takes no paths) | not there | unset | A scripted or A/B run brings its own settings and leaves the player's file alone | Tested game-free (`tests/test_options_path.py`) | Unset for players; for scripts and tests only | ## Testing **Tested:** - `tests/test_options_path.py` (new) builds `host/options_path.c` as the game builds it, on Windows (llvm-mingw 20250402, Windows 11) and Linux (gcc, WSL Ubuntu 24.04), and runs it from a made-up game folder. It covers: - the order: `HOS_OPTIONS_FILE`, `--user`, beside the game; - relative paths, and `--romfs` / `--compile-shaders` taking their values; - choosing once; - the one-time copy: skipped for a `compile_shaders=`-only file, never over an existing file, not with `HOS_OPTIONS_FILE`, not when the file is already the game folder's; - a user folder that can't be written; - the log line. - `python -I -B tests/run_all.py` on Windows, branch and a clean `main` worktree (e090cc48) side by side: the same 17 failures on both, none new. They fail on `main` on this PC too: test_appimage_player_build, test_bake_portability, test_cpm_discovery, test_cpp_preflight, test_crash_dialog, test_env_on, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_readback_late, test_tested_configurations, test_vk_pgo, test_vram_budget_row, test_walk_key. - On Linux (WSL) the checks this PR touches pass: options_path, game_layout, bound_keys, ocarina_*, output_resolution, audio_output_*, menu_env_*, bug_report, pipeline_share, display_startup_failure, pad_layout. test_crash_dialog, test_pipeline_tour and test_walk_key fail there on clean `main` too. - test_crash_dialog stops on `main` at its first command-line check, before this PR's code. So the helper's new argument was checked with a standalone program against `hos_crash_dialog.h` (good, `-`, empty and one-too-many arguments; watch and show), and those cases were added to the test. - Syntax-only compiles of every changed host file, on Windows (llvm-mingw, x86_64-w64-mingw32) and Linux (gcc, POSIX shim). The changed renderer files (`vk_main.cpp`, `vk_menu.cpp`, `vk_draw.cpp`) with the build's flags on Windows: the same three `__try` errors as `main` from a flag my check lacks, nothing else. **Not tested:** - No build and no run in the game yet: the one-time copy on a real update, the menu saving into `%APPDATA%\Longshot`, the crash window's report with the new argument. - Linux in the game: the Linux launcher doesn't pass `--user` yet, so on Linux the file stays beside the game until the launcher PR (below). **Must be tested:** - Windows, any GPU: change a few Insert menu settings (Upscaler, Frame generation, a control) in a launcher-built game, update it to a build of this PR with the launcher, and start it from the launcher. Expect the same settings, `%APPDATA%\Longshot\longshot_options.ini` present, and `options: the settings in ... were copied to ...` in `logs\longshot_log.txt`. Then change a setting, start another build from the launcher, and expect the change there too (by hand). ## Risks **What could break:** the settings of a launcher-started build on Windows now come from `%APPDATA%\Longshot`. If the copy fails, the run keeps the game folder's file and logs why. Any reader of the file this PR missed would keep reading the game folder's file; `grep` finds no other in `host/` and `gpu/`. RecompLauncher, direct starts and the tools are unchanged: without `--user`, the file is where it was. **Turning it off:** revert the commit. For one run, `HOS_OPTIONS_FILE=<game folder>\longshot_options.ini` reads the old file. **Compatibility:** - **Settings:** the first launcher start of this build copies the game folder's `longshot_options.ini` into `%APPDATA%\Longshot`, and from then on that file is used by every build the launcher starts; the game folder's copy is left in place and no longer read, apart from `compile_shaders=`. A build from before this PR started afterwards still reads its own folder's file. - **Renderer interface:** `MKWVK_API_VERSION` 22 → 23, so the exe and `longshot_vk.dll` must come from the same build. - **Caches and saves:** shader caches and saves are untouched. ## Issues See https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/issues/90 (settings lost between updates). None found for this exact case. Related: LongshotLauncher#57 (an update in place), #224 (driver settings lost per update), #259 and #229 (several user profiles: these settings would move into a `default` profile). Follow-ups, in their own PRs: - **Launcher:** pass `--user ~/.local/share/longshot` on Linux, and run a step on Play that brings an older build's settings file, the Windows per-app GPU preference and the NVIDIA profile over to a new build. Whether saving an NVIDIA profile needs admin is still an open question for that PR's discussion. - **Game:** the GPU device row saved by the card itself, not by its place in the list. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [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. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [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] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(options): settings in the launcher's user folder, so an update keeps them
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 6s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 5s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 5s
01f73919d4
An update into a new game folder started from the defaults: longshot_options.ini was read and written
beside the executable, so each build kept its own. The file now follows --user (the launcher's
%APPDATA%\Longshot on Windows, a portable launcher's own folder), the folder the saves are already shared
in, and every build the launcher starts reads the same one. Without --user (RecompLauncher, a shortcut,
the tools) it stays beside the game, as before.

- host/options_path.c chooses the file once at the start (HOS_OPTIONS_FILE, else --user's folder, else
  beside the game), copies a game folder's file from before into the user folder once, and logs where it
  is; every host reader uses hos_options_file()
- the renderer gets the path from the host (MkwVkHost.options_path, MKWVK_API_VERSION 23) and its readers
  share options_path() (vk_paths.h)
- compile_shaders= stays in the game folder's file, where make_game.py writes it
- the crash window's helper is given the run's settings file; pipeline_share.py reads the one the log names
- tests: test_options_path.py; the checks that cut these readers out of the source get the new stubs

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
HumanBean changed title from WIP: feat(options): settings in the launcher's user folder, so an update keeps them to feat(options): settings in the launcher's user folder, so an update keeps them 2026-11-15 08:12:01 +00:00
HumanBean deleted branch feat/user-settings 2026-11-15 08:12:13 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!729
No description provided.