feat(builder): an up-to-date game without a ticked DLSS or FSR 4 offers Update, not Play #2591

Open
roblesleo wants to merge 1 commit from roblesleo/feat/builder-update-adds-upscalers into main
roblesleo commented 2026-11-12 00:39:08 +00:00 (Migrated from github.com)

A game is built with NVIDIA DLSS or AMD FSR 4 or without it, so ticking one after the game was built changed nothing: the Builder still said "Your game is up to date" and showed Play, and the game kept starting without it until the next release's update.

Now, when the game is up to date but lacks a ticked upscaler, the page says "Update to add NVIDIA DLSS" (or AMD FSR 4, or both), and Update builds the folder again from the game files it keeps, with the ticks. It updates to the game's own version, so nothing is downloaded when the builder is that release (also when GitHub does not answer).

UpscalerParts.Missing decides it from what make_game.py leaves in the folder:

  • NVIDIA DLSS: NVIDIA's runtime in the ngx folder (place_ngx_runtime, only for a build with NVIDIA's SDK).
  • AMD FSR 4: the .hookshot_fsr mark, and, with AMD's license accepted (#1866), AMD's upscaler DLL beside the exe or in the folder, where gpu/vk/vk_fsr4.cpp looks. Without the license the DLLs are the player's to put there, so a folder built with FSR 4 is not sent back to Update for them.

Both ticks refresh the page.

Tests: tests/test_builder_upscaler_parts.py runs UpscalerParts.Missing on authored placeholder folders (.NET 8, Windows, like test_builder_dlss_update) and checks that RefreshMode asks it for an up-to-date game and that both ticks refresh. Ran locally on Windows with test_builder_dlss_update, test_ffx_sdk, test_builder_status, test_builder_python and dotnet build of the Builder, all passing.

A game is built with NVIDIA DLSS or AMD FSR 4 or without it, so ticking one after the game was built changed nothing: the Builder still said "Your game is up to date" and showed Play, and the game kept starting without it until the next release's update. Now, when the game is up to date but lacks a ticked upscaler, the page says "Update to add NVIDIA DLSS" (or AMD FSR 4, or both), and Update builds the folder again from the game files it keeps, with the ticks. It updates to the game's own version, so nothing is downloaded when the builder is that release (also when GitHub does not answer). `UpscalerParts.Missing` decides it from what make_game.py leaves in the folder: - NVIDIA DLSS: NVIDIA's runtime in the `ngx` folder (`place_ngx_runtime`, only for a build with NVIDIA's SDK). - AMD FSR 4: the `.hookshot_fsr` mark, and, with AMD's license accepted (#1866), AMD's upscaler DLL beside the exe or in the folder, where `gpu/vk/vk_fsr4.cpp` looks. Without the license the DLLs are the player's to put there, so a folder built with FSR 4 is not sent back to Update for them. Both ticks refresh the page. Tests: `tests/test_builder_upscaler_parts.py` runs `UpscalerParts.Missing` on authored placeholder folders (.NET 8, Windows, like `test_builder_dlss_update`) and checks that `RefreshMode` asks it for an up-to-date game and that both ticks refresh. Ran locally on Windows with `test_builder_dlss_update`, `test_ffx_sdk`, `test_builder_status`, `test_builder_python` and `dotnet build` of the Builder, all passing.
Owner

@SimonKimDev reviewed on GitHub · 2026-11-12 07:10 UTC · original

A legacy FSR tick (saved before #1866: Fsr=true, FsrLicense=false) on an up-to-date folder without .hookshot_fsr gets "Update to add AMD FSR 4" forever:

  • UpscalerParts.Missing flags the missing mark whatever fsrLicense is.
  • Update goes to UpdateBuild, where fsr = (saved.Fsr && saved.FsrLicense) || (mark && AMD DLL && license file) is false, so Run adds no --fsr.
  • make_game.py's fsr_wanted is then false (no --fsr, no mark), the folder is rebuilt without FSR and the mark is never written.

So each Update is a full rebuild that changes nothing, and Play isn't offered until the box is unticked. (ApplySettings sets the tick without the Click handler, so this state survives loading.)

Either UpdateBuild should pass --fsr for the saved tick without the license, as StartBuild does (keeping --fetch-fsr-runtime gated on the license), or Missing shouldn't require the mark when fsrLicense is false.

> [@SimonKimDev](https://github.com/SimonKimDev) reviewed on GitHub · 2026-11-12 07:10 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2591#pullrequestreview-5870533588) A legacy FSR tick (saved before #1866: `Fsr=true`, `FsrLicense=false`) on an up-to-date folder without `.hookshot_fsr` gets "Update to add AMD FSR 4" forever: - `UpscalerParts.Missing` flags the missing mark whatever `fsrLicense` is. - Update goes to `UpdateBuild`, where `fsr = (saved.Fsr && saved.FsrLicense) || (mark && AMD DLL && license file)` is false, so `Run` adds no `--fsr`. - `make_game.py`'s `fsr_wanted` is then false (no `--fsr`, no mark), the folder is rebuilt without FSR and the mark is never written. So each Update is a full rebuild that changes nothing, and Play isn't offered until the box is unticked. (`ApplySettings` sets the tick without the `Click` handler, so this state survives loading.) Either `UpdateBuild` should pass `--fsr` for the saved tick without the license, as `StartBuild` does (keeping `--fetch-fsr-runtime` gated on the license), or `Missing` shouldn't require the mark when `fsrLicense` is false. <!-- github-sync:r:5870533588 -->
Owner

@roblesleo commented on GitHub · 2026-11-12 13:14 UTC · original

Thanks, you're right: that state looped. Fixed in aa03b862.

UpdateBuild now passes --fsr for the saved tick whether AMD's license was accepted or not, as StartBuild does for the page's tick; fetching AMD's runtime stays gated on the license. So a legacy tick (Fsr=true, FsrLicense=false) builds the folder with FSR 4 once, make_game.py writes .hookshot_fsr, and Play is offered afterwards.

I took your first option rather than relaxing UpscalerParts.Missing, so a ticked FSR 4 still means the folder is built with it.

tests/builder_dlss_update_test.cs now checks the arguments: --fsr with the saved tick (with and without the license), and none for a folder that only has the mark, where make_game.py keeps FSR 4 on its own.

> [@roblesleo](https://github.com/roblesleo) commented on GitHub · 2026-11-12 13:14 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2591#issuecomment-6824621631) Thanks, you're right: that state looped. Fixed in aa03b862. UpdateBuild now passes --fsr for the saved tick whether AMD's license was accepted or not, as StartBuild does for the page's tick; fetching AMD's runtime stays gated on the license. So a legacy tick (Fsr=true, FsrLicense=false) builds the folder with FSR 4 once, make_game.py writes .hookshot_fsr, and Play is offered afterwards. I took your first option rather than relaxing UpscalerParts.Missing, so a ticked FSR 4 still means the folder is built with it. tests/builder_dlss_update_test.cs now checks the arguments: --fsr with the saved tick (with and without the license), and none for a folder that only has the mark, where make_game.py keeps FSR 4 on its own. <!-- github-sync:c:6824621631 -->
Owner

@Joey-Magnus approved on GitHub · 2026-11-12 14:33 UTC · original

I merged this into current main, which already has #2528's dlssLicense record. It merges cleanly, and it also applies cleanly with #2553 and #2565 on top. tests/test_ffx_sdk.py and test_builder_status.py pass. The .NET fixtures need the SDK, so for those I'm relying on the Windows jobs, which are green.

SimonKimDev's loop is fixed. MainWindow.xaml.cs:1468 now passes --fsr for the saved tick whether or not AMD's license was accepted, so a legacy Fsr=true, FsrLicense=false folder gets built once with the mark, and Play comes back after that. The fetch is still gated on the license. UpscalerParts.Missing (called at :189) only asks for AMD's DLLs when the license was accepted, so a player who brings their own DLLs isn't sent back to Update. The DLSS side follows saved.Dlss in UpdateBuild, so it can't loop the same way.

The one red job is tests/test_release_scripts.py (test_nightly_two_runs_at_once_end_at_the_newer_commit). It's flaky and unrelated to the Builder.

> [@Joey-Magnus](https://github.com/Joey-Magnus) approved on GitHub · 2026-11-12 14:33 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2591#pullrequestreview-5876218659) I merged this into current main, which already has #2528's `dlssLicense` record. It merges cleanly, and it also applies cleanly with #2553 and #2565 on top. tests/test_ffx_sdk.py and test_builder_status.py pass. The .NET fixtures need the SDK, so for those I'm relying on the Windows jobs, which are green. SimonKimDev's loop is fixed. MainWindow.xaml.cs:1468 now passes `--fsr` for the saved tick whether or not AMD's license was accepted, so a legacy `Fsr=true, FsrLicense=false` folder gets built once with the mark, and Play comes back after that. The fetch is still gated on the license. `UpscalerParts.Missing` (called at :189) only asks for AMD's DLLs when the license was accepted, so a player who brings their own DLLs isn't sent back to Update. The DLSS side follows `saved.Dlss` in `UpdateBuild`, so it can't loop the same way. The one red job is tests/test_release_scripts.py (`test_nightly_two_runs_at_once_end_at_the_newer_commit`). It's flaky and unrelated to the Builder. <!-- github-sync:r:5876218659 -->
Commenting is not possible because the repository is archived.
No description provided.