Repository navigation
feat(builder): the launcher, one window to play, build and update (Windows and Linux) - #2373
madebyjordan wants to merge 17 commits into
Conversation
…ndows and Linux) The game folder's hookshot.exe (and the AppImage) now opens a launcher drawn from the Paper design "Hookshot v2": the picture, the wordmark, Play, and a side panel with three tabs. - Build: the game file and the install folder, Build Game, which becomes the build's bar with its step and time left, or "Build stopped" with the reason; the language, workers and memory options under it. - Releases: every published release, newest first, the one in the folder ticked; pressing another installs it beside the builder in use (setup.c --engine) and rebuilds the game with that release's own make_game.py, after asking. Older releases fold into one row. - Settings: Prepare Shaders, DLSS, FSR 4 and Enable pre-releases (the Release / Release candidate update track). - The panel's foot: Check for updates, Update Available, the update's bar with Cancel, or "Update stopped". Installing a version says it rebuilds the game and about how long that takes on this PC (build_times.json). Release candidates are ordered as semver orders them (rc.2 after rc.1, rc.10 after rc.9), so the next candidate is an update, not a switch back. The Linux launcher (builder/linux/hookshot_launcher.py) is the same screen on one Tk canvas, painted with numpy from the XAML's own path data. Tests: tests/builder_launcher (the version lists and the progress words, and a whole-flow rig with fake releases and a stand-in make_game.py), tests/test_linux_launcher_*.py, and the details hit-box test moved to the new Show details / Cancel row. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes in order - Cancel during an update's download stops it, and nothing from the cancelled work starts a build afterwards; a program that will not stop is killed 8 s after SIGTERM; a work whose program is gone but whose pipe stays open ends after 10 s; its end runs once. - Tk is only touched from the Tk thread; an exception in a message no longer stops the builder's message loop (pump), and the launcher's own messages are guarded the same way. - Timers are cancelled when the window closes; an @@eta of inf or nan, a damaged picture.png, a version file that is not UTF-8 and a settings file that cannot be written no longer break the screen. - Long errors are cut to a few lines with an ellipsis instead of running over the buttons; one panel image instead of one per window height. - Release candidates are ordered as semver orders them (rc.10 after rc.9), as on Windows; a greyed-out release says why when the folder cannot be rebuilt at all. - The game version check reads make_game.py's output as UTF-8 and says when it cannot run, instead of "Checking the game version..." for good. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d cannot get stuck - Any close during a build or an update (Alt+F4, the taskbar, its own button) asks first, and the hidden programs stop with the window; the builder's own exits (Play, Repair, the updater) go through Leave() and ask nothing. A Cancel pressed before the build's program started is kept. - A release with only the zip (no setup list) installs through the builder's own update instead of failing in the setup's engine mode. - Half-installed (.part) and half-removed (.gone) versions, and versions with no installed.txt, are not taken for installed builders; the clean-up after an update counts as busy and never deletes a .part folder. - Play, the folder link and Repair say when the game or setup cannot be started (an antivirus that took it) instead of closing the app; an unexpected error is written to launcher-error.txt and shown, and the window stays. - "Update stopped" can be dismissed; long reasons are cut to a few lines with the whole text as a tooltip; the setup's engine mode sends its reasons on one line; "Copied" resets for the next failure. - Installing a release keeps the saved DLSS and FSR 4 choices, and passes an older release's make_game.py only the options it knows. - The switch-back question no longer says the builder goes back on the path where it does not; greyed-out rows say why; Check for updates gives up at once when there is no network at all. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last step, preparing the shaders, has 8% of the bar (92-100%), but on some GPUs it takes as long as the compile: the bar sat at 94% for half an hour and looked stuck. Under the bar (an update's and the Build tab's) the step now says its own percentage and a time left from how fast it has gone, and the build's usual time, which has no shaders in it, is not used for it. Windows and Linux. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… of releases that are out too The Releases tab listed a pre-release only while it was ahead of the latest release. Hookshot's release candidates come before their release (v2.1.0-rc.1 to rc.3, then v2.1.0), so with pre-releases on the list showed nothing more. Now the pre-releases among the releases in sight are listed in version order (below their release), and all of them once the older releases are unfolded. Windows and Linux. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A suggestion for the new Settings tab: an option for #2206's Why. #2206 (merged) lets the game skip loading and saving It fits what's already here.
Two things to decide:
|
|
I'm investigating #2352, claimed here: the game-folder wrapper can select an older installed Builder over the recorded AppImage, and the legacy default update mode can offer older tools even offline. Both are reproduced with authored fixtures on main. This overlaps |
…CACHE_LOAD=0 Suggested in review. NotRexed#2206 lets the game skip its pipeline cache file (mkw_vk_pipelines.bin, up to 2.5 GB, held by the driver for the whole session: about 3 GB less memory), but only through an environment variable or by editing hookshot_env.txt. The switch writes or removes that one line in the game folder's hookshot_env.txt, so it holds however the game is started (Play, a shortcut, bin\hookshot.exe). The rest of the player's file and its line ends stay as they are, the file goes when nothing is left in it, and the cache file is never deleted: switched off again, the game uses it as before. A note under the switch gives the file's size here. Off by default, as in NotRexed#2206. Windows (GameEnv.cs) and Linux (game_env.py), with tests of both. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an older one The game's own update offer starts the launcher with --update <folder> and no --tag: the target is then the update track's, which can be older than the folder's version (a newer release candidate's or nightly's, NotRexed#2352). It was built with no question; now it asks first, as a row of an older release does. Raised on the PR by Arkex-ai. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@nullifyshot Thanks, added in 96eaeef: Lower memory use in Settings, off by default. It writes |
|
@Arkex-ai Thanks for checking. This PR doesn't touch |
|
Going to do very deep testing on this, plus will have a few design feedback. Few to start:
|
|
Thanks @hckrman101, deep testing is very welcome. On the design side (a full-screen build window, the three steps, the style of the Play/Build button): I'll take those as suggestions. The design here isn't final. I have bigger plans for it that I kept out of this release so the launcher could be ready sooner, and I'll weigh your ideas in that. The other points are fair, and I'm addressing them in this PR:
|
…en a newer release is out From hckrman101's review. With no game in the folder the main button says Build and opens the build's choices (the menu's Build tab), instead of a faint Play. While a newer release is out, an Update button stands beside Play: Play plays the version in the folder, Update brings the new one, asked first as Update Available does. It is Play's torn strip in the panel's dark with the accent's words, at Play's right so the menu's panel never covers it. Windows and Linux. Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@hckrman101 The two cases are in (4ee39e2), on Windows and Linux:
|
hckrman101
left a comment
There was a problem hiding this comment.
Thanks for this, it's a big step up from three windows, and the two cases I asked for (Build with no game, Play + Update side by side) are in with screenshots on both platforms. I'm still doing the deeper hands-on testing I mentioned, so this is a first pass on the code, not a sign-off.
The one thing that has to be sorted before anything else: the background picture. builder/HookshotBuilder/Images/launcher.jpg is a picture of the game (the XAML calls it that: "the picture of the game, dim behind everything"), and builder/linux/launcher/picture.png is the same image dimmed by make_picture.py. CONTRIBUTING.md is fine with screenshots of the game in issues and PRs, but this goes further: it is committed to the repo and embedded in every HookshotBuilder.exe (<Resource>) and copied into every AppImage (cp -a builder/linux). That's redistributing the game's art with every release, and the PR doesn't say where the image comes from. That's a project and legal call for @NotRexed, not something to slip in with a launcher; until he's OK'd it, I'd drop both files and use something we own (a flat colour or gradient, or original art), keeping make_picture.py only if there's an image that can ship.
Updater / setup security looks right to me:
setup.c --enginekeeps the existing checks: parts must be https with a 64-hex sha256 that's verified before unpacking, list names go throughlist_name_ok, the--tagis restricted to[0-9A-Za-z._-], and a list whose tag differs from the one asked for stops the install. Unpacking still goes into<tag>.partand is renamed when complete, and it refuses to install over the version incurrent.txt.- The
http://127.0.0.1:exception inpart_url_okonly applies whenHOOKSHOT_SETUP_LISTitself points at localhost, so a list downloaded from GitHub can't use it. Fine for the rig. link_treehard-links the shared parts (or copies them), doesn't follow reparse points, and removes the half-made folder and downloads it again if it fails. Good.- On Linux the launcher uses the existing
linux_update.installpath (https only, redirects too, sha256 per part) and checksmanifest.tag == tagbeforeexecve. ReleaseListdrops tags that aren't versions, and the tag goes into the API URL withUri.EscapeDataStringand to the setup throughArgumentList. No quoting problems that I could see.
Process launching: Windows StartGame is unchanged apart from the error box (working directory is the folder, HOOKSHOT_NO_UPDATE_CHECK=1). Linux Play still goes through hookshot_builder.play(), so the socketpair stderr from #2279 is kept and the game still writes hookshot_log.txt. The Lower memory switch only edits RX_VK_PCACHE_LOAD lines in hookshot_env.txt, through a .new file and a replace, and never touches the cache file. Thanks for doing it that way (@nullifyshot's suggestion).
Saves / install folders: I found nothing that moves or deletes saves or the game folder. Storage.Freeable now skips <tag>.part, which is needed now that an engine install can run next to a build. One thing to be aware of: after switching to an older release or a pre-release, FreeAfterBuild deletes that version's builder again (it isn't current.txt), so switching back and forth downloads it again each time. That's probably intended, just confirming.
Things for @NotRexed (product, not code):
- The Track menu goes away. A saved Nightly stays Nightly, but nobody can choose Nightly any more from either launcher, while
docs/versioning.mdstill lists it as a track. Either keep a way to opt in or say in the docs that Nightly can't be chosen in the UI any more. - The scope (a new main window on both platforms, the Releases tab that can install any older release) is his decision.
Overlaps: this conflicts textually with #2367 (portable installs: Storage.Base, setup.c, Updater.cs, Runner.cs, App.xaml.cs, the asset-digest test), #2201 (MainWindow.xaml(.cs), Updater.cs), #2141 (MainWindow.xaml.cs) and #2150/#2159/#2160 (hookshot_builder.py, linux_update.py). The one that's more than a rebase is #2367: whichever lands second has to make sure the launcher's hidden --engine setup and FreeAfterBuild use the portable home rather than %LOCALAPPDATA%. #2352's two paths are Arkex-ai's, as agreed above.
CI: the new tests run (Windows builder_launcher: 37 + 33 + 13 checks passed; test_linux_launcher_* are picked up by run_all, and _art declares numpy). The two red jobs aren't from this PR: Windows part 2/2 fails test_native_astc_texture_count.py (a scanf deprecation in its fixture under clang -Werror), and appimage timed out installing system packages. Re-run the AppImage job once the runner's network is back, since appimage.sh changed here.
Small things inline.
| { | ||
| StartGame(launchFolder); | ||
| Application.Current.Shutdown(); | ||
| chosenTrack = PreTick.IsChecked == true ? (Track == Updater.Nightly ? Updater.Nightly : Updater.Candidate) : Updater.Release; |
There was a problem hiding this comment.
With the Track menu gone, this is the only switch, and it can't select Nightly (only keep a saved one). Turning it off and on again moves a Nightly player to Release candidate for good. Fine if that's the intent, but docs/versioning.md's table still presents Nightly as a track you choose.
…ys, and review nits From hckrman101's review: - The launcher's background was a screenshot of the game (launcher.jpg, and the Linux launcher's dimmed picture.png made from it by make_picture.py), built into every HookshotBuilder.exe and copied into every AppImage: the game's art, shipped with each release. All three are gone; the window is the page's colour behind the wordmark, until original art takes its place. - The clean-up after a build or an update kept only the builder in use (current.txt) and the one running: after switching to an older release, its builder was deleted, and switching again downloaded it again. The builder of the release the game folder holds stays too. - The launcher's .NET check ran on both Windows shards: now on the first only, as the builder's workers check beside it. - docs/versioning.md: Nightly is not chosen in the launcher (its own setup or AppImage installs it, and it stays until Enable pre-releases is turned off). Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @hckrman101, that's a thorough first pass. Fixed in d1a4155:
#2367: agreed. If this lands second, I'll make sure the hidden CI reds: agreed, neither is from this PR. Could someone re-run the AppImage job once the runner's network is back, since Looking forward to the hands-on testing. |
|
#2449 is ready for review: one runtime-readiness check before installed reuse, staging publication and fast launch. Hosted CI passes, and an actual disposable installation recovered a missing For your |
|
Thanks for the heads-up @Arkex-ai. The One thing to know: I have a small follow-up planned that lets a later release rename the builder (the setup records the list's |
## Summary On Linux, the game-folder launcher could select an older installed Builder and offer to rebuild a newer game backwards, even offline. This keeps the recorded Builder associated with its game and prevents an implicit downgrade, while preserving confirmed version changes. Fixes #2352. [Claim](https://discord.com/channels/1568810484818378804/1569019786766188665/1570119562655961089). Scope coordinated with #2373; this does not redesign its launcher. ## Technical changes - Prefer the game's recorded Builder; retain installed-Builder and direct-game fallbacks when it is unavailable. No candidate execution or extra interpreter needed to choose one. - Default to Play for a known older update target. Confirm older rebuilds at the common update entry, including DLSS changes and direct launcher updates. - Carry accepted downgrade consent once across an exact-version handoff, bound to the unchanged game version. A legacy fetched flag alone grants no consent. - Require the download list and fetched tools to match the selected target before rebuilding; report release-list lag instead of silently using another version. ## Validation Current tested head **`04ea74bb`**, rebased onto main **`34f05785`**. The worklist conflict preserves main’s damaged-file row and places this package’s row in score order. Focused version-admission tests pass again on Windows and Linux; Windows skips the POSIX executable-permission case, which passes on Linux. No fresh game rebuild or native Linux GUI run is claimed. Fresh [hosted checks](https://github.com/NotRexed/Hookshot/pull/2406/checks) are separate exact-head evidence. <details> <summary>Earlier validation evidence and limits</summary> Tested **`2dcca138`** on main **`7759a6f3`**: - **14 focused methods pass on Linux**; Windows passes the 13 handler/version methods and skips the POSIX-wrapper method. Tests execute the actual shell wrapper, version selector and complete production Tk handlers with authored UI/thread/download boundaries. - Original main fails the regression. **11 broken variants rejected on Linux / 10 on Windows**, including lost or stale consent, wrong fetched tools, mismatched release lists and bypassed confirmation. - Full Linux: **374 passed / 0 failed / 23 skipped**, versus exact base373/0/23. - Full Windows: **364 passed / 2 failed / 31 skipped**, versus exact base363/2/31. The same recorder negative-control and NUP link-privilege failures remain; no new failure. - Existing updater30 and launch6 tests pass; shell syntax passes. The screenshot is the actual Tk launcher on Windows using authored version metadata. No game files, real downloads or builds were used. Native Linux GUI rendering, a real AppImage upgrade and gameplay remain untested. Unknown hashes/differing nightlies retain the existing ordering policy; version labels are not commit-ancestry proof. [Behavior and limits](docs/linux-builder-version-selection.md).  </details> <!-- pr-ci-status --> | Current-head workflow | Result | |---|---| | [Game-free source regressions](https://github.com/NotRexed/Hookshot/actions/runs/43090325870) | Passed | | [Linux AppImage](https://github.com/NotRexed/Hookshot/actions/runs/43090325735) | Passed | | [PR title](https://github.com/NotRexed/Hookshot/actions/runs/43090324841) | Passed | | [Windows builder](https://github.com/NotRexed/Hookshot/actions/runs/43090325627) | Passed | <!-- /pr-ci-status -->
Joey-Magnus
left a comment
There was a problem hiding this comment.
It now conflicts with main in MainWindow.xaml.cs, builder/linux/hookshot_builder.py, builder/linux/linux_update.py and tests/test_updater_asset_digest.py (#2141, #2150, #2159).
The art blocker is fixed: only the Marcellus font is new, with its OFL licence. I spot-checked the risky paths and they hold: --engine still requires https parts with sha256, the localhost exception only applies when the list itself came from localhost, and RX_VK_PCACHE_LOAD is rewritten through .new plus rename.
Minor: link_tree hard-links shared parts between versions, so nothing may ever write those files in place.
Merge order: I'd take #2367 first (clean), then this rebased with BuilderHome.PassTo on the hidden --engine setup and the portable home in FreeAfterBuild. The scope (Releases tab installing any older release, Nightly no longer selectable) is NotRexed's call.
## Summary Windows Builder could not stay portable on an external SSD: setup, app storage and the game updater selected different locations, while saved absolute paths broke after a move. This adds opt-in `--portable` setup with one marked, relocatable Builder home. Standard installs retain their existing location. Fixes #2258. [Claim](https://discord.com/channels/1568810484818378804/1569019786766188665/1570073925902270599). ## Technical changes - Share the home across setup, WPF settings/work/SDKs, child builds and self-updates, using process-scoped context. - Keep same-volume paths and the game's Builder association relative. Invalid portable associations fail instead of selecting AppData. - Stage setup/ZIP repair publication, retain installed parts until success, and reject truncated native paths before file operations. - Add source-owned native, .NET, archive and published-app regressions. The startup fixture now compares directory identity, including DOS aliases, and executes the shipped self-contained Release package. ## Validation Current tested head **`f75c156f`**, rebased onto main **`355453de`**. A successful standard rebuild now removes the former portable association before publishing standard Builder metadata. The transition regression rejects the previous production source; it also checks repeated standard builds and denied removal preserving old metadata. All eight focused Windows fixtures pass, including native setup/transactions, archive/relocation and actual published self-contained WPF CLI startup. ZIP fallback repair limits and portable setup discovery are documented. Physical removable-drive relocation and a complete public-release/game repair remain untested. Fresh [hosted checks](https://github.com/NotRexed/Hookshot/pull/2367/checks) are separate exact-head evidence. <details> <summary>Earlier validation evidence and limits</summary> Earlier tested head **`6affe74b`**, rebased onto main **`7759a6f3`**: - All eight focused portable checks pass on Windows/Python 3.12, plus the worker fixture's **104 assertions**. - Published Release app: **seven startup/child/relocation/error scenarios**, including an owned DOS alias, with installed-runtime discovery isolated. Restoring the old textual path comparison reproduces the hosted false failure; the rejected-home startup mutation is still caught. - Fresh hosted checks will run on this head. Earlier hosted failures include the missing `env_on` fixtures now fixed on main by #2380; the old WPF failure was a short/long path assertion, not selection of a different home. At `64856415` on `b744b1ec`: Windows **359 passed / 5 failed / 31 skipped**; Linux **362 passed / 2 failed / 31 skipped**. Both hit the two missing-`env_on` fixtures; Windows also retained the documented recorder negative control, NUP link-privilege and compute-guard fixture failures. These are earlier-head results, not current-head green checks. At `9e3aa287`: complete native setup/host-update compilation and self-contained WPF publish passed; ten broken variants were rejected. Full Linux was 351/0/31; Windows349/3/30 had the same failure identities as its exact main baseline. Tests use authored packages/files, with no game data, downloaded SDKs or built game. Actual removable-media/drive-letter changes, live release download/repair and gameplay remain untested. #2373's new launcher shares setup/storage paths and needs the portable contract preserved whichever lands second.  </details> <!-- pr-ci-status --> | Current-head workflow | Result | |---|---| | [Game-free source regressions](https://github.com/NotRexed/Hookshot/actions/runs/43095610848) | Queued | | [Linux AppImage](https://github.com/NotRexed/Hookshot/actions/runs/43095610874) | Queued | | [PR title](https://github.com/NotRexed/Hookshot/actions/runs/43095609270) | Queued | | [Vulkan back end with NVIDIA DLSS](https://github.com/NotRexed/Hookshot/actions/runs/43095610937) | Queued | | [Windows builder](https://github.com/NotRexed/Hookshot/actions/runs/43095610860) | Queued | <!-- /pr-ci-status -->
## Summary Starting a Windows build could stop Python or compiler processes belonging to another work folder. Cleanup now requires positive work-folder or output ownership and preserves processes whose ownership cannot be established. Fixes #2142. ## Technical changes - Parse complete Windows arguments and compare normalized absolute paths at component boundaries. Unrelated orphans, sibling-prefix outputs and include/input paths no longer qualify. - Protect the full ancestor chain, including non-Python launchers. Recognize this installation's default work and a proven make_game parent's matching build.py child; preserve unknown orphans and unrelated descendants. - Stop eligible processes individually, without `taskkill /T`; report successful stops only and retain best-effort cleanup. ## Validation Current tested head **`40321e87`**, rebased onto main **`355453de`**. Default-work cleanup now recognizes previous packaged Builder versions under the same home, including their proven build child. Other homes, sibling prefixes, unexpected layouts and custom-work mismatches remain excluded. Eleven tests pass on Windows and Linux, including 500 quoted-argument controls; the previous production source fails the two new cross-version/query-diagnostic regressions on both systems. All process queries and termination calls are intercepted. No real process was stopped. Fresh [hosted checks](https://github.com/NotRexed/Hookshot/pull/2250/checks) are separate exact-head evidence. <details> <summary>Earlier validation evidence and limits</summary> Earlier tested head **`e14e3a8c`**, rebased onto main **`6df80a8d`**. The selector, test and documentation patch is unchanged by range-diff; the worklist conflict preserves both main's existing row and this PR's row. **Ten focused tests pass on Windows and Linux**, including 500 deterministic quoted-argument round trips. All process queries and termination calls are intercepted; no real process was stopped. Fresh [CI checks](https://github.com/NotRexed/Hookshot/pull/2250/checks) run on this head. At `6a3af365` on base `ebbba2ea`, the unmodified helper selected unrelated synthetic processes as well as the intended control. Four causal mutations removing work ownership, path boundaries, ancestor protection or individual termination were rejected. Full Windows: **342 passed / 3 failed / 30 skipped**, versus exact base **341 / 3 / 30**, with the same failure names. Full Linux: **352 / 0 / 23**; a separate release-script run with jq supplied its missing internal coverage (**24 cases passed**). These full/causal results refer to that earlier head, not the current rebase. This is a synthetic selector regression, not a real termination or PID-reuse guarantee. Unknown orphan build.py processes and outputs known only through relative paths or response files remain untouched. Linux cleanup behavior is unchanged; gameplay does not exercise this Windows process selector. [Dev-work claim](https://discord.com/channels/1568810484818378804/1569019786766188665/1570037297234051113). Coordination with #2324 and #2373 preserves this engine fix independently of retained-lift and launcher UI changes. </details> <!-- pr-ci-status --> | Current-head workflow | Result | |---|---| | [Game-free source regressions](https://github.com/NotRexed/Hookshot/actions/runs/43095862413) | Queued | | [Linux AppImage](https://github.com/NotRexed/Hookshot/actions/runs/43095862343) | Queued | | [PR title](https://github.com/NotRexed/Hookshot/actions/runs/43095862104) | Queued | | [Vulkan back end with NVIDIA DLSS](https://github.com/NotRexed/Hookshot/actions/runs/43095862318) | Queued | | [Windows builder](https://github.com/NotRexed/Hookshot/actions/runs/43095862349) | Queued | <!-- /pr-ci-status -->
Conflicts with NotRexed#2141, NotRexed#2160, NotRexed#2367 and NotRexed#2447, resolved for the launcher: - NotRexed#2367 (portable homes): Storage.Base is BuilderHome.Base. The launcher's hidden --engine setup gets the home (BuilderHome.PassTo), and the setup's engine mode opens it before it installs (it ran before init_home and would have stopped on every install). In engine mode a reused part is linked, in a portable home too, so the version in use stays whole; otherwise a part is moved, or copied in a portable home, as NotRexed#2367 has it. The post-build clean-up is Storage's, so it already uses the portable home. A portable home makes no shortcuts by default, as NotRexed#2367's portable install. - NotRexed#2447 (Save my pipeline list): its button was on the launch page the launcher replaces. It is a row of the menu's Settings now, on Windows and Linux, with the short of what it did under it and the whole in its tooltip. - NotRexed#2141 (NotRexed#2129, back from an update that stopped): the launcher's one way back is BackFromBuild, for Back and for a cancel alike: to the launcher of the game, which lets the update go and offers it again, else to the build's choices. test_builder_update_back.py's Windows half now checks that. - NotRexed#2160 (Cancel during a Linux update download): its checks are in the launcher's message_in, which pump hands each line to; test_linux_update_cancel.py's stand-in compiles both. The hard-linked parts (link_tree): nothing writes a part's file in place (compilers, Python, headers and libraries are read; Python's bytecode caches are written to a new file and renamed over), now said where the links are made. test_portable_setup_transaction.py also runs the engine mode's reuse: linked, in a standard and a portable home, the version in use whole. test_portable_builder_home.py's fixture compiles LaunchList.cs with Storage.cs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @Joey-Magnus. #2367 was already in, so I merged
Agreed that the scope (installing older releases, Nightly not selectable) is @NotRexed's call. |
A reason from the pipeline list tool can run to five lines (no list yet, and why), which pushed the rows under it, and the foot, out of the menu's panel. The note under Save is cut at three lines now, whole under the pointer, on Windows and Linux, as the panel's other long reasons are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Build tab is drawn from the top down and its build options from the panel's foot up. A long reason under the game file (three lines) with the time line under Build Game ran into "Build options" on a 640 high window: the button and the label were drawn over each other. The reason is one line now, whole under the pointer, and the line under Build Game takes only the lines there is room for above the options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DLSS and FSR 4 take effect at a build, and the launcher had no way to build
the game in the folder again: a player who forgot to switch FSR 4 on had to
go back through the Build tab with the game file. Now:
- Settings says when the switches no longer match how the game was built
("Built without FSR 4", "Built with FSR 4", "Built without DLSS"), with
Rebuild beside it; the whole sentence and the time it takes are under the
pointer. Settings scrolls, as Releases does, when that makes it taller.
- The version in the folder, pressed in Releases, offers the same rebuild.
Either asks first, then builds the folder again from the game files it keeps
with that version's own make_game.py --update and the settings as they are
now: FSR 4 on adds it, off takes it out (--no-fsr), DLSS on adds it. Its bar
says "Rebuilding...", and a stop says "Rebuild stopped". The Linux launcher
does the same for DLSS (it has no FSR 4).
Also fixed on the way: in the launcher, Settings showed its switches off
until the Build tab had been opened, and changing one before that saved the
whole settings file from that screen, losing the game file chosen last time
and the other switches. The saved choices are read when the launcher opens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A builder-settings.json saved before the license was asked has Fsr on and no FsrLicense: the switch showed on, and no build or update used it (each needs the license). It shows off now, as builds take it; switched on, the license is asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntime v2.1.0's make_game.py takes --fsr but not --fetch-fsr-runtime (it came with NotRexed#1866, after that release was cut). The launcher asked for all three words, so with v2.1.0's tools it left FSR 4 out without a word: a rebuild for FSR 4 had none, and Settings still said "Built without FSR 4". A first build with FSR 4 on was worse: the builder's own tools were given the words they do not know, and stopped at once. Now --fsr goes to any make_game.py that takes it, and the fetch words only to one that knows them. For one that does not, the launcher fetches AMD's runtime itself (FfxRuntime.cs: the same pinned release, files and SHA-256s as scripts/ffx_sdk.py, each file checked) into the builder's ffx-sdk folder and puts it in the game folder: before an update or rebuild, and at the end of a first build. Settings counts FSR 4 as in the game only with AMD's runtime beside it. tests/test_ffx_runtime_pins.py keeps FfxRuntime.cs and ffx_sdk.py the same. Checked with AMD's real files: a rebuild through a stand-in for v2.1.0's tools passed --fsr alone, and the three files reached the game folder, checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s runners (#2543) **What this is for:** on GitHub's runners the updater digest check must keep building for the builder's `net8.0`. Since #2294 it falls back to the SDK's own framework when the .NET 8 reference pack is missing. That is right on a PC with only a later SDK and wrong in CI, where it would hide a runner image without the pack, or an `Updater.cs` that no longer builds for .NET 8. This is the follow-up @Joey-Magnus suggested in his review of #2294. **What is measured:** on Linux with only the .NET 10 SDK (10.0.113): run as a PC the check passes through the fallback (20 cases); with `GITHUB_ACTIONS=true` it fails with dotnet's NU1100 output; main's file passes in that same situation. **Not measured:** a runner that really lacks the pack. This pull request's own jobs have .NET 8 installed, so they only show that the normal path is unchanged. ## Problem From the review of #2294: "on CI the fallback would hide exactly the regression the builder cares about. If a runner image ever drops the .NET 8 pack, or `Updater.cs` starts using an API newer than .NET 8, the check would quietly build for net10.0 and pass, while the real builder no longer compiles. CI only prints one line in that case." ## What changed - `tests/test_updater_asset_digest.py`: when the `net8.0` restore fails with NU1100 and `GITHUB_ACTIONS` is `true`, the check exits with a message and dotnet's output instead of switching the framework. `GITHUB_ACTIONS == "true"` is the test `tests/run_all.py` already uses to tell a runner from a PC. Everywhere else the fallback is unchanged. ## How to try it ``` python -I -B tests/test_updater_asset_digest.py # a PC with only a later SDK: falls back, 20 passed GITHUB_ACTIONS=true python -I -B tests/test_updater_asset_digest.py # the same PC as a runner: fails with NU1100 ``` ## How it was tested Arch Linux, .NET SDK 10.0.113 only (no .NET 8 reference pack), head `2294f29d` on main `fa2b25b2`. | File | Environment | Result | |---|---|---| | this branch | `GITHUB_ACTIONS` unset | the fallback's line, then `Updater digest cases: 20 passed, 0 failed.`, exit 0 | | this branch | `GITHUB_ACTIONS=true` | `on a GitHub runner the updater must build for the builder's net8.0, and its reference pack is missing: no fallback to a later framework here`, then dotnet's NU1100 lines, exit 1 | | main's file | `GITHUB_ACTIONS=true` | falls back, 20 passed, exit 0 (what this change prevents) | ## Not tested - A real runner without .NET 8, and Windows on my side (no Windows machine). - `--strict` on a PC is unchanged: the fallback still applies there. The review named `--strict` as an alternative; `run_all.py` does not pass it on to a check, and `GITHUB_ACTIONS` is what it already reads. ## Risks - A self-hosted runner (or a local runner tool) that sets `GITHUB_ACTIONS=true` without .NET 8 now fails this check instead of passing for a later framework. That is the intent. ## Overlaps - #2373 touches the same file and merges cleanly with this change (`git merge-tree`). Assisted-by: Claude Fable 5.2 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
|
This is starting to look good. Few notes:
|
Joey-Magnus
left a comment
There was a problem hiding this comment.
My last points are handled: BuilderHome.PassTo on the hidden --engine setup, the portable home in FreeAfterBuild (and the init_home ordering bug you caught), and the link_tree no-write rule written down. It conflicts with main again in builder/HookshotBuilder/MainWindow.xaml.cs (#2528, #2598), and only the title check has run on 5ccc2f1.
The new FfxRuntime.cs is sound: a pinned commit, .part first, each SHA-256 checked, and tests/test_ffx_runtime_pins.py keeps it in step with scripts/ffx_sdk.py. One small thing: Launcher.cs calls FfxRuntime.Ensure(Storage.FfxSdk) without a cancellation token, so Cancel waits out the 120 s timeout.
On the PR head, test_linux_launcher_lists, test_linux_launcher_work, test_linux_update_cancel, test_builder_update_back and test_ffx_runtime_pins pass (the C# and mingw checks need Windows).
At +7.4k lines it keeps gaining features (rebuild, FSR runtime fetch). The scope (installing any older release, Nightly not selectable) still needs NotRexed's decision; moving the rebuild and FSR work to a follow-up would help it land. hckrman101's layout notes are open too.




Players have to juggle three things today: the builder window, the game folder's launcher, and the update dialogs. Nothing tells them which version they have or what an update will do (it rebuilds the game, ~30 minutes). This replaces all of that with one launcher window, on Windows and Linux, drawn from the "Hookshot v2" design.
What changed
hookshot.exein the game folder (and the AppImage) opens the launcher. It shows the wordmark, then Play (Build while there is no game yet, which opens the build). Update stands beside Play while a newer release is out. Under Play are the version, then Credits / GitHub / Discord. Menu opens a side panel with three tabs. The window is the page colour behind the wordmark. No picture of the game ships, and original art will come in its own PR.setup.c --engine, no window) and rebuilds the game with that release's ownmake_game.py.rc.10afterrc.9).RX_VK_PCACHE_LOAD=0, one line of the game folder'shookshot_env.txt, so it holds however the game is started. The cache file is kept, and a note gives its size.build_times.json).Linux (
builder/linux/hookshot_launcher.py): the same screen on one Tk canvas, painted with numpy from the XAML's own path data. The art test checks the paths are byte-for-byte the same. The old Tk pages remain, for a Python without numpy and for the builder's own update.Smaller changes:
Updater.ReleaseList: every release with a builder to download. Tags that are not versions (nightly,ultrawide,low-vram) are skipped.setup.c --engine --tag <tag>: installs a release beside the one in use and reports progress on stdout. It hard-links the parts the two versions share (copying where links can't be made), in a portable home too.docs/versioning.md: the update-track section describes the switch.For reviewers
Where to start:
builder/HookshotBuilder/Launcher.cs: the launcher's state and its update flow (Bring→StartWork→PullEngine→Runner).LaunchList.csandLaunchWork.cs: the pure rules (which versions show, what the bar and the words say). The Linux side mirrors them inlaunch_list.py/launch_work.py, and both have tests.MainWindow.xaml: the screen itself.setup.c: the--enginemode (g_engine).Already settled in review (no need to raise again):
launcher.jpg, the Linuxpicture.png,make_picture.py). The only new asset is the Marcellus font, with its OFL licence.list_name_ok;--taglimited to[0-9A-Za-z._-]; never over the version incurrent.txt; staged in<tag>.part. The127.0.0.1exception applies only to a list that itself came from localhost (the rig).BuilderHome.PassTo.Storage.BaseisBuilderHome.Base, so the clean-up after a build stays in the portable home.work()path, sobuilder_readyruns before@@installed.link_tree.builder_workers.BackFromBuild, returns to the launcher, which offers the update again.Checks to run:
dotnet run -c Releaseintests/builder_launcher: list, work and settings rules.python -I -B tests/run_all.py builder launcher portable setup update.tests/test_linux_launcher_{lists,work,art}.pyandtests/test_linux_update*.py.tests/builder_launcher/rig/run.ps1. It uses fake releases from a local server, a scratch install (HOOKSHOT_SETUP_BASE) and a stand-inmake_game.py. The real install is untouched.HOOKSHOT_UI_SHOT=<file.png>draws the window to a PNG and exits.HOOKSHOT_UI_PREVIEWpicks the state:tab=build|releases|settings,older,pick=…,go, orfound,build/found,stopped/update,menufor the build pages.HOOKSHOT_LATEST_TAG=v2.2.0,v2.1.0fakes GitHub's answer. The Linux launcher takesHOOKSHOT_UI_PREVIEW=menu,tab=…,building|stopped|updating|update-stopped.How it was tested
tests/builder_launcher(new, in CI on Windows): the version lists, release-candidate order, and the progress and rebuild wording.tests/builder_launcher/rig/run.ps1, run locally against the real Windows build. It covers:--update;tests/test_linux_launcher_{lists,work,art}.py(new): the Linux list rules, the progress and rebuild wording, the launcher's state handling (cancel during a download, a second exit, a stuck pipe), and the drawing.tests/test_builder_details_hitbox.pyhit-tests the Show details / Cancel row: the words take clicks, and the blank beside them does not.main:test_portable_setup_transaction.pyalso runs the engine mode's part reuse: linked, in a standard and a portable home, with the version in use left whole.test_builder_update_back.pychecks the launcher's way back.test_linux_update_cancel.pycompiles the Linux builder'smessage_inbesidepump.main:tests/run_all.py: 387 passed. Five failed (test_lift_cache,test_nup_paths,test_vram_budget_row,test_fpx_sticky,test_bake_portability), and the same five fail on a clean checkout ofmainon the same machine.test_linux_update*,test_release_scriptsand the launcher's screens, in each state.Open for @NotRexed
Screenshots
960 × 640. The Windows ones are drawn off-screen from this branch, the Linux ones taken in Ubuntu 22.04. The paths shown are placeholders.
Windows
Linux
Linux draws the same screens, with Inter in place of Segoe UI.
🤖 Generated with Claude Code