fix(menu): longshot_options.ini is replaced whole or not at all #550

Merged
HumanBean merged 2 commits from zeldaootr/Longshot:menu-options-atomic-save into main 2026-11-15 17:57:44 +00:00
Contributor

Type: fix · Risk: low · Tested on: Linux, no GPU (game-free checks); Steam Deck OLED (a tester's run, before the rebase) · Needs: Windows, any GPU

Why

If the disk was full, or the game was closed while saving the menu's settings, longshot_options.ini was left cut short, and the next start took the defaults for every setting past the cut (or, with an empty file, chose Auto over the player's preset). Since #729 the file can live in the launcher's user folder (--user, %APPDATA%\Longshot on Windows), shared by every build the launcher starts, so one cut-short save there loses the settings of all of them.

How to reproduce

Seen on: read off the code, and reproduced game-free on Linux (tests/test_menu_save_atomic.py at the test-only commit)
Settings and switches: defaults
Steps: fill the disk that holds longshot_options.ini (an SD card on a Steam Deck), then change a row in the Insert menu; or close the game while it saves
Expected: the file holds either the old settings or the new ones, whole
Actual: on main the file is written in place and left cut where the disk ran out: in the test, 200 bytes of 1006, everything past the cut gone at the next start

What changed

The menu's save writes the settings to a file beside the real one and moves it over once it is whole, so a failed save leaves the old file as it was. Where that can't be done it saves in place as before, and it says once a run, with the file's whole path, what went wrong. It applies to whichever file options_path() names: the user folder's since #729, else the game folder's.

  • menu: save() in gpu/vk/vk_menu.cpp writes the lines to <file>.new beside the file (beside its target when it is a link, so a synced file stays linked), with the old file's mode, checks the write and the close, and only then moves it over the old file (rename; MoveFileExA with MOVEFILE_REPLACE_EXISTING on Windows). A write that fails leaves the old file as it was. Where nothing can be made beside it or moved over it (a folder that is not writable, the file held open by another program) it writes in place as before; a read-only file is not written. Each failure is logged once a run, with the error and the whole path (menu: <folder>/longshot_options.ini.new could not be written whole (File too large): <folder>/longshot_options.ini is kept as it was), so a report shows which folder it was. A run with no settings file (options_path() empty: the host found no place for one and logged so at the start) saves nothing, as on main, and makes no .new in the folder the game runs in.
  • tests: tests/test_menu_save_atomic.py runs the real save() against a file that runs out of room part way, a linked file, an unwritable folder or file, a file with its own mode, a file in a user folder away from the folder the game runs in (nothing made in the game's folder, the log line naming the whole path), and no settings file at all. tests/test_mesh_mode_switch.py, tests/test_menu_env_overrides.py and tests/test_upscaler_round_trip.py, which compile save() from the source, get its headers and a logf stand-in.

Measurements

Verdict: passes: the check fails on main (a save that runs out of room leaves the file cut short) and passes here, with g++ and clang, twice each, alternating. No frame rate changes; what the Deck run checked is the behaviour (Testing).

Hardware: Linux (Ubuntu 24.04, kernel 6.8), x86-64, no GPU or game used; g++ 13.3.0, clang 23.1.5 (llvm-mingw's, --target=x86_64-linux-gnu)
Scene: no game: the game-free check
Method: python3 -I -B tests/test_menu_save_atomic.py --cxx g++, and with clang, at the test-only commit 540aaf3f (main aa95d35f plus the test) and at 2248676d, twice each, alternating
How to reproduce: check out each commit and run the command above

Before: main @ aa95d35f (with the test, 540aaf3f) After: this PR @ 2248676d
longshot_options.ini after a save that did not fit (both runs, both compilers the same) cut short; folder, file and user-folder read-only cases fail the old file, whole; all 8 cases pass
540aaf3f: FAIL: a save that did not fit left longshot_options.ini at 200 bytes of 1006 / FAIL: a read-only file in the user folder is not said once with its path (0 lines) / FAIL: full_disk, folder_read_only, file_read_only, user_folder (both runs, g++ and clang)
2248676d: PASS: the options file is replaced whole or not at all, and saved wherever it was before (both runs, g++ and clang)
2248676d: log: menu: <tmp>/full_disk/longshot_options.ini.new could not be written whole (File too large): <tmp>/full_disk/longshot_options.ini is kept as it was

Flags and settings

None: no switch, menu row or ini key is added or changed; the file's contents are the same, only how it is written.

Testing

Tested:

  • Linux x86-64, game-free, after the rebase onto main aa95d35f (which moved the file into the user folder, #729): the check fails at the test-only commit 540aaf3f and passes at 2248676d, as above. The 72 checks that read the files this PR changes (vk_menu.cpp and the three edited checks) ran with g++ and with clang: 69 pass, and the three that fail (test_options_budget_once, test_pipeline_tour, test_walk_key) fail the same way on clean main aa95d35f. vk_menu.cpp passes a syntax-only compile for Windows (llvm-mingw 20261104, -Wall -Wextra: the same 6 warnings as main, none new) and for Linux with clang (g++ stops at the same three render/rx_nvn.h errors as on main).
  • The full suite (python -I -B tests/run_all.py, Linux, g++, LONGSHOT_VULKAN_HEADERS set) ran once on main aa95d35f with this PR and the other open fixes from the same sweep merged locally (ac6a1e97, not pushed): 550 passed, 5 failed, 51 skipped. All five fail on clean main aa95d35f too: test_make_game_cache, test_crash_dialog (its repair, #626, needs one more line since #729), test_options_budget_once, test_mods_size_table and test_walk_key.
  • Steam Deck, a tester's run (test request T-SRCU: pass), on the head before the rebase: dukenheimer ran 5089cac6 (in a build of main 7ff5256e with #550 to #558 merged) on a Steam Deck OLED, SteamOS 3.9.5, ext4 on the internal drive, started through Steam in Game Mode; the comment of 2026-11-14 22:32 on this PR has the details. Six rows changed in play through the menu's own setter were all in the file after a restart, the file kept its mode, and no longshot_options.ini.new was left in play or after the close. With the options file a symlink, it stayed a link and its target got the change. With the game folder read-only and the file writable, both changes were saved in place and the log said so once. No crash or error in 4 starts. (Rumble strength read back 50 after a set of 60: the control bridge set a value the menu's steps cannot make, and the start snaps it; the save kept what was set.) That run still holds for the Deck after the rebase: the Linux launcher passes no --user yet (#729), so on the Deck options_path() is still the game folder's longshot_options.ini, the file the tester saved, and the save's steps are unchanged. Only the log line's wording changed: it now names the whole path (menu: <game folder>/longshot_options.ini.new cannot be made beside it (Permission denied): <game folder>/longshot_options.ini is written in place).

Not tested: the rebased head 2248676d in the game (no build here: the build base was in use). A real full disk or power loss in the game, and an exFAT SD card. The user folder in the game: on Windows %APPDATA%\Longshot (a launcher start); on Linux only with HOS_OPTIONS_FILE or a --user given by hand until the launcher passes one. Windows: the rebased head was compiled but not linked or run; the read-only check, the link (reparse point) written in place and the MoveFileExA path were not run.

Must be tested:

  • Windows, any GPU, a build of this PR started from the launcher (settings in %APPDATA%\Longshot): change a few Insert menu rows, close and start again, expecting the rows kept and no longshot_options.ini.new left in %APPDATA%\Longshot or the game folder (by hand: the folder and logs\longshot_log.txt, which should have no menu: line about the file)

Risks

What could break: saving the menu's settings. There is no fsync before the rename, by design (a save comes with every arrow press in the menu): on a file system that can reorder the rename before the data, XFS among them, a power loss right after a save can leave a zero-length longshot_options.ini, and the next start chooses Auto. A crash between the write and the rename can leave a .new file, which the next save overwrites. Where the file is written in place (an unwritable folder, a Windows link), a full disk still cuts it short, as on main. The Windows paths (the read-only check, a reparse point written in place, the MoveFileExA fallback) are untested.

Turning it off: revert the commit

Compatibility: Nothing changes: the same file in the same place (options_path()), with the same lines; a longshot_options.ini.new may appear beside it for the moment of a save.

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.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • 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. The head before the rebase, 5089cac6, linked for both (longshot_vk.dll with llvm-mingw 20261104, and longshot_vk.so). The rebased head's only changed source file, gpu/vk/vk_menu.cpp, compiles (syntax-only, no full build: the build base was in use) for Windows with llvm-mingw 20261104's x86_64-w64-mingw32-clang++ -Wall -Wextra, with the same 6 warnings as main and none new, and for Linux with clang; it adds no new call or symbol over 5089cac6. The path calls are the narrow ones the renderer already uses under the UTF-8 manifest (GetFileAttributesA, MoveFileExA), which take the user folder's UTF-8 path.
**Type:** fix · **Risk:** low · **Tested on:** Linux, no GPU (game-free checks); Steam Deck OLED (a tester's run, before the rebase) · **Needs:** Windows, any GPU ## Why If the disk was full, or the game was closed while saving the menu's settings, `longshot_options.ini` was left cut short, and the next start took the defaults for every setting past the cut (or, with an empty file, chose Auto over the player's preset). Since https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/729 the file can live in the launcher's user folder (`--user`, `%APPDATA%\Longshot` on Windows), shared by every build the launcher starts, so one cut-short save there loses the settings of all of them. ## How to reproduce **Seen on:** read off the code, and reproduced game-free on Linux (`tests/test_menu_save_atomic.py` at the test-only commit) **Settings and switches:** defaults **Steps:** fill the disk that holds `longshot_options.ini` (an SD card on a Steam Deck), then change a row in the Insert menu; or close the game while it saves **Expected:** the file holds either the old settings or the new ones, whole **Actual:** on `main` the file is written in place and left cut where the disk ran out: in the test, 200 bytes of 1006, everything past the cut gone at the next start ## What changed The menu's save writes the settings to a file beside the real one and moves it over once it is whole, so a failed save leaves the old file as it was. Where that can't be done it saves in place as before, and it says once a run, with the file's whole path, what went wrong. It applies to whichever file `options_path()` names: the user folder's since #729, else the game folder's. - menu: `save()` in `gpu/vk/vk_menu.cpp` writes the lines to `<file>.new` beside the file (beside its target when it is a link, so a synced file stays linked), with the old file's mode, checks the write and the close, and only then moves it over the old file (`rename`; `MoveFileExA` with `MOVEFILE_REPLACE_EXISTING` on Windows). A write that fails leaves the old file as it was. Where nothing can be made beside it or moved over it (a folder that is not writable, the file held open by another program) it writes in place as before; a read-only file is not written. Each failure is logged once a run, with the error and the whole path (`menu: <folder>/longshot_options.ini.new could not be written whole (File too large): <folder>/longshot_options.ini is kept as it was`), so a report shows which folder it was. A run with no settings file (`options_path()` empty: the host found no place for one and logged so at the start) saves nothing, as on `main`, and makes no `.new` in the folder the game runs in. - tests: `tests/test_menu_save_atomic.py` runs the real `save()` against a file that runs out of room part way, a linked file, an unwritable folder or file, a file with its own mode, a file in a user folder away from the folder the game runs in (nothing made in the game's folder, the log line naming the whole path), and no settings file at all. `tests/test_mesh_mode_switch.py`, `tests/test_menu_env_overrides.py` and `tests/test_upscaler_round_trip.py`, which compile `save()` from the source, get its headers and a `logf` stand-in. ## Measurements **Verdict:** passes: the check fails on `main` (a save that runs out of room leaves the file cut short) and passes here, with g++ and clang, twice each, alternating. No frame rate changes; what the Deck run checked is the behaviour (Testing). Hardware: Linux (Ubuntu 24.04, kernel 6.8), x86-64, no GPU or game used; g++ 13.3.0, clang 23.1.5 (llvm-mingw's, `--target=x86_64-linux-gnu`) Scene: no game: the game-free check Method: `python3 -I -B tests/test_menu_save_atomic.py --cxx g++`, and with clang, at the test-only commit 540aaf3f (`main` aa95d35f plus the test) and at 2248676d, twice each, alternating How to reproduce: check out each commit and run the command above | | Before: main @ aa95d35f (with the test, 540aaf3f) | After: this PR @ 2248676d | |---|---|---| | `longshot_options.ini` after a save that did not fit (both runs, both compilers the same) | cut short; folder, file and user-folder read-only cases fail | the old file, whole; all 8 cases pass | ``` 540aaf3f: FAIL: a save that did not fit left longshot_options.ini at 200 bytes of 1006 / FAIL: a read-only file in the user folder is not said once with its path (0 lines) / FAIL: full_disk, folder_read_only, file_read_only, user_folder (both runs, g++ and clang) 2248676d: PASS: the options file is replaced whole or not at all, and saved wherever it was before (both runs, g++ and clang) 2248676d: log: menu: <tmp>/full_disk/longshot_options.ini.new could not be written whole (File too large): <tmp>/full_disk/longshot_options.ini is kept as it was ``` ### Flags and settings None: no switch, menu row or ini key is added or changed; the file's contents are the same, only how it is written. ## Testing **Tested:** - Linux x86-64, game-free, after the rebase onto `main` aa95d35f (which moved the file into the user folder, #729): the check fails at the test-only commit 540aaf3f and passes at 2248676d, as above. The 72 checks that read the files this PR changes (`vk_menu.cpp` and the three edited checks) ran with g++ and with clang: 69 pass, and the three that fail (`test_options_budget_once`, `test_pipeline_tour`, `test_walk_key`) fail the same way on clean `main` aa95d35f. `vk_menu.cpp` passes a syntax-only compile for Windows (llvm-mingw 20261104, `-Wall -Wextra`: the same 6 warnings as `main`, none new) and for Linux with clang (g++ stops at the same three `render/rx_nvn.h` errors as on `main`). - The full suite (`python -I -B tests/run_all.py`, Linux, g++, `LONGSHOT_VULKAN_HEADERS` set) ran once on `main` aa95d35f with this PR and the other open fixes from the same sweep merged locally (ac6a1e97, not pushed): 550 passed, 5 failed, 51 skipped. All five fail on clean `main` aa95d35f too: `test_make_game_cache`, `test_crash_dialog` (its repair, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/626, needs one more line since #729), `test_options_budget_once`, `test_mods_size_table` and `test_walk_key`. - Steam Deck, a tester's run (test request T-SRCU: pass), on the head before the rebase: dukenheimer ran 5089cac6 (in a build of main 7ff5256e with #550 to #558 merged) on a Steam Deck OLED, SteamOS 3.9.5, ext4 on the internal drive, started through Steam in Game Mode; the comment of 2026-11-14 22:32 on this PR has the details. Six rows changed in play through the menu's own setter were all in the file after a restart, the file kept its mode, and no `longshot_options.ini.new` was left in play or after the close. With the options file a symlink, it stayed a link and its target got the change. With the game folder read-only and the file writable, both changes were saved in place and the log said so once. No crash or error in 4 starts. (Rumble strength read back 50 after a set of 60: the control bridge set a value the menu's steps cannot make, and the start snaps it; the save kept what was set.) That run still holds for the Deck after the rebase: the Linux launcher passes no `--user` yet (#729), so on the Deck `options_path()` is still the game folder's `longshot_options.ini`, the file the tester saved, and the save's steps are unchanged. Only the log line's wording changed: it now names the whole path (`menu: <game folder>/longshot_options.ini.new cannot be made beside it (Permission denied): <game folder>/longshot_options.ini is written in place`). **Not tested:** the rebased head 2248676d in the game (no build here: the build base was in use). A real full disk or power loss in the game, and an exFAT SD card. The user folder in the game: on Windows `%APPDATA%\Longshot` (a launcher start); on Linux only with `HOS_OPTIONS_FILE` or a `--user` given by hand until the launcher passes one. Windows: the rebased head was compiled but not linked or run; the read-only check, the link (reparse point) written in place and the `MoveFileExA` path were not run. **Must be tested:** - Windows, any GPU, a build of this PR started from the launcher (settings in `%APPDATA%\Longshot`): change a few Insert menu rows, close and start again, expecting the rows kept and no `longshot_options.ini.new` left in `%APPDATA%\Longshot` or the game folder (by hand: the folder and `logs\longshot_log.txt`, which should have no `menu:` line about the file) ## Risks **What could break:** saving the menu's settings. There is no `fsync` before the rename, by design (a save comes with every arrow press in the menu): on a file system that can reorder the rename before the data, XFS among them, a power loss right after a save can leave a zero-length `longshot_options.ini`, and the next start chooses Auto. A crash between the write and the rename can leave a `.new` file, which the next save overwrites. Where the file is written in place (an unwritable folder, a Windows link), a full disk still cuts it short, as on `main`. The Windows paths (the read-only check, a reparse point written in place, the `MoveFileExA` fallback) are untested. **Turning it off:** revert the commit **Compatibility:** Nothing changes: the same file in the same place (`options_path()`), with the same lines; a `longshot_options.ini.new` may appear beside it for the moment of a save. ## 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] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [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. The head before the rebase, 5089cac6, linked for both (`longshot_vk.dll` with llvm-mingw 20261104, and `longshot_vk.so`). The rebased head's only changed source file, `gpu/vk/vk_menu.cpp`, compiles (syntax-only, no full build: the build base was in use) for Windows with llvm-mingw 20261104's `x86_64-w64-mingw32-clang++ -Wall -Wextra`, with the same 6 warnings as `main` and none new, and for Linux with clang; it adds no new call or symbol over 5089cac6. The path calls are the narrow ones the renderer already uses under the UTF-8 manifest (`GetFileAttributesA`, `MoveFileExA`), which take the user folder's UTF-8 path.
Assisted-by: Claude
fix(menu): write longshot_options.ini beside it and move it over once whole
Some checks failed
PR description / Description follows the template (pull_request_target) Failing after 7s
beanstalk/approval Test farm not requested
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
5089cac60e
A full disk or a close mid-save left the file cut short; the next start took
the defaults past the cut, or a first start (Auto) when it was empty. A link
is followed, the mode kept, a read-only file kept; where nothing can be made
beside it or moved over it, the file is written in place as before. Each
failure is logged once a run, with the error. The checks that compile
`save()` get its headers and a `logf` stand-in.

Assisted-by: Claude
Author
Contributor

Filled in: the full game-free suite on the combined branch, the Windows and Linux builds, and the test request T-SRCU. Still work in progress until a Steam Deck run is in.

Filled in: the full game-free suite on the combined branch, the Windows and Linux builds, and the test request T-SRCU. Still work in progress until a Steam Deck run is in.
Contributor

Test request T-SRCU: pass

Hardware: Steam Deck OLED (AMD Custom APU 0932, RADV VANGOGH, 16 GB), Mesa 26.2.0-devel git-5c65d4fc31, SteamOS 3.9.5 (build
20261023.100), kernel 7.2.7-valve2. File system: ext4 on the internal NVMe (the game folder and the options file).

Built from main 7ff5256e with #550 to #558 merged in order (all clean); this PR at its head 5089cac6. Built for x86-64-v3 with the
release play profile and LLVM 23.1.5. The game ran in a test folder with its own options file (the defaults its first start wrote),
started through Steam in Game Mode, docked.

How the rows were changed. With the control bridge on (HOS_CONTROL), each row was set with its option set, which goes through
the Insert menu's own setter and saves as the menu does (docs/control.md).

What I checked

  1. Changes in play, then a restart. In Kokiri Forest I changed six rows: Text speed 0 to 2 and Field of view 100 to 110 (Gameplay
    tab); Camera speed 100 to 120, Invert camera Y off to on, Stick dead zone 0 to 2, Rumble strength 100 to 60 (Controls tab).
    • The file was rewritten with all six, keeping its mode (-rw-r--r--).
    • No longshot_options.ini.new in the folder, in play or after the game closed.
    • After closing the game and starting it again, the settings read back Text speed 2, Field of view 110, Camera speed 120,
      Invert camera Y on and Stick dead zone 2.
    • Rumble strength read back 50, while the file still held 60. That one is the bridge's doing, not this PR's: the row steps by
      25, the bridge's set took 60 (which the menu's arrows can't make), and the start snaps it to the step (rumble_step,
      "a hand-edited 60: 50"). The save kept exactly what was set.
  2. The file as a symlink. With longshot_options.ini a link to a file in another folder, after a change (Text speed 2 to 1)
    it is still the link and its target has the change. No .new beside the target or in the game folder, before or after the close.
  3. The game folder read-only. With chmod a-w on the folder, the file itself writable, I made two changes (Text speed to 3,
    Field of view to 105). Both were saved, in place. The log said once:
    menu: longshot_options.ini.new cannot be made beside it (Permission denied): the file is written in place.
    No .new. The folder was made writable again afterwards.

No crash or error in the 4 starts.

Not tested:

  • the key bindings: the bridge sets rows only;
  • Windows;
  • a full disk;
  • an exFAT SD card.
Test request T-SRCU: pass Hardware: Steam Deck OLED (AMD Custom APU 0932, RADV VANGOGH, 16 GB), Mesa 26.2.0-devel git-5c65d4fc31, SteamOS 3.9.5 (build 20261023.100), kernel 7.2.7-valve2. File system: ext4 on the internal NVMe (the game folder and the options file). Built from main 7ff5256e with #550 to #558 merged in order (all clean); this PR at its head 5089cac6. Built for x86-64-v3 with the release play profile and LLVM 23.1.5. The game ran in a test folder with its own options file (the defaults its first start wrote), started through Steam in Game Mode, docked. **How the rows were changed.** With the control bridge on (`HOS_CONTROL`), each row was set with its `option set`, which goes through the Insert menu's own setter and saves as the menu does (docs/control.md). **What I checked** 1. **Changes in play, then a restart.** In Kokiri Forest I changed six rows: Text speed 0 to 2 and Field of view 100 to 110 (Gameplay tab); Camera speed 100 to 120, Invert camera Y off to on, Stick dead zone 0 to 2, Rumble strength 100 to 60 (Controls tab). - The file was rewritten with all six, keeping its mode (`-rw-r--r--`). - No `longshot_options.ini.new` in the folder, in play or after the game closed. - After closing the game and starting it again, the settings read back Text speed 2, Field of view 110, Camera speed 120, Invert camera Y on and Stick dead zone 2. - Rumble strength read back 50, while the file still held 60. That one is the bridge's doing, not this PR's: the row steps by 25, the bridge's set took 60 (which the menu's arrows can't make), and the start snaps it to the step (`rumble_step`, "a hand-edited 60: 50"). The save kept exactly what was set. 2. **The file as a symlink.** With `longshot_options.ini` a link to a file in another folder, after a change (Text speed 2 to 1) it is still the link and its target has the change. No `.new` beside the target or in the game folder, before or after the close. 3. **The game folder read-only.** With `chmod a-w` on the folder, the file itself writable, I made two changes (Text speed to 3, Field of view to 105). Both were saved, in place. The log said once: `menu: longshot_options.ini.new cannot be made beside it (Permission denied): the file is written in place`. No `.new`. The folder was made writable again afterwards. No crash or error in the 4 starts. **Not tested:** - the key bindings: the bridge sets rows only; - Windows; - a full disk; - an exFAT SD card.
Author
Contributor

Thank you for the Deck run, dukenheimer: your result is in the description as you reported it, including what it did not cover. Marking this ready for review.

Thank you for the Deck run, dukenheimer: your result is in the description as you reported it, including what it did not cover. Marking this ready for review.
zeldaootr changed title from WIP: fix(menu): longshot_options.ini is replaced whole or not at all to fix(menu): longshot_options.ini is replaced whole or not at all 2026-11-14 22:51:13 +00:00
Author
Contributor

Rebased onto main aa95d35f (2248676d): test_upscaler_round_trip.py keeps main's apply_auto(), and test_menu_save_atomic.py gets #736's GPU card stand-ins. Since #729 save() writes whatever file options_path() names (the launcher's user folder's or the game folder's), and the atomic write already followed it there.
Added for the new location: a user-folder case and a no-settings-file case in the check, log lines that name the file's whole path, and no stray .new when a run has no settings file. The Deck result (T-SRCU) still holds, since on the Deck the file is still the game folder's.

Rebased onto `main` aa95d35f (2248676d): `test_upscaler_round_trip.py` keeps `main`'s `apply_auto()`, and `test_menu_save_atomic.py` gets #736's GPU card stand-ins. Since #729 `save()` writes whatever file `options_path()` names (the launcher's user folder's or the game folder's), and the atomic write already followed it there. Added for the new location: a user-folder case and a no-settings-file case in the check, log lines that name the file's whole path, and no stray `.new` when a run has no settings file. The Deck result (T-SRCU) still holds, since on the Deck the file is still the game folder's.
zeldaootr force-pushed menu-options-atomic-save from 5089cac60e
Some checks failed
PR description / Description follows the template (pull_request_target) Failing after 7s
beanstalk/approval Test farm not requested
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 2248676d40
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Has been cancelled
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-15 13:32:18 +00:00
Compare
HumanBean deleted branch menu-options-atomic-save 2026-11-15 17:57:47 +00:00
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Has been cancelled
Website / Website on Cloudflare (pull_request_target) Has been skipped
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!550
No description provided.