feat(update): keep the shader caches when the translator is the same #78

Merged
HumanBean merged 8 commits from cgize/LongshotLauncher:feat/keep-shader-caches into main 2026-11-15 08:29:05 +00:00
Contributor

Every update makes the game prepare all its shaders again (about 25 minutes here), even when the new version translates
shaders exactly as the old one did: the update builds into a new folder, whose cache/ starts empty, while the old
folder's 10 GB of caches would have been valid as they are.

What changed

After an update's build finishes and before Ready shows, the launcher compares the shader translator of the version being
replaced with the new one's and, when they are the same, takes the old build's caches into the new game folder: moved
when both folders are on one drive, copied otherwise.

  • Core/ShaderCaches.cs (new)
    • Translator(toolsRoot): the stamp Longshot's nx2/translator_files.py makes of the translator's sources below
      tools/gpu/vk (the value nx2/build.py compiles in as LONGSHOT_SHADER_STAMP), followed by a hash of
      translator_files.py itself, so two versions whose recipes differ never compare equal by this one's. Null when the tools
      have no such file or lack a source.
    • Keep(from, to, fromTools, toTools, ...): takes the cache files (the names of Longshot's scripts/dev_packs.py
      CACHES and PLAY_FILES, as its --seed does) when the new folder has none and both translators match. On one
      drive they are moved: each file gets its name in the new folder by a hard link (FileLinks.TryHardLink), all of them
      or none, then loses its old one, so it is instant and needs no room. Where a link cannot be made (another drive) they
      are copied and the old folder keeps its own. Either way each file is made as <name>.part and renamed only once all
      are there, so a cancelled or failed one leaves nothing half made.
      Skipped, with the reason, when: the old build has no translated programs, the new folder has caches of its own, the
      translators differ or either version's tools are not on this PC, the game is running, or (another drive only) there is
      no room for the copy plus 1 GiB.
  • App/ViewModels/BuildStepViewModel.cs: Exited goes through KeepCaches for an update before Complete (which
    prunes the old version's tools, needed for its stamp). The Finish row shows "Keeping the prepared shaders: N%" while it
    copies; Cancel stops it as it stops the build; the details log says what was kept or why not. New builds, rebuilds and
    replays are unchanged.
  • Tests: tests/launcher_core/ShaderCacheChecks.cs (the stamp against a hand-computed SHA-1, line ends, another
    translator, another recipe, missing tools, every refusal, a cancelled copy, the copy, the move, half a move) and
    tests/test_launcher_core.py --longshot now checks the cache names and the stamped folders and files against Longshot's
    dev_packs.py and translator_files.py.
  • README: a design decision entry. es.json: the two new texts; the other catalogs show them in English.

The move follows the review: after an update the old folder is no longer listed, so its caches have no other user. Both
folders never keep a link to the same file: the game appends to and rewrites these files.

What it cannot know

The game folds more into its stamp at start than the translator's sources (the GPU, its switches, the records' layout).
The launcher cannot see that, so when it differs the game drops the copied files, exactly as it would have dropped them
in place, and prepares as before: never a wrong cache.

How it was tested

Windows 11, .NET SDK 10.0.404.

  • python -I tests/test_launcher_core.py --longshot <Longshot main @ e090cc48>: launcher core: 1050 checks passed.
  • dotnet build LongshotLauncher.slnx -c Release: 0 warnings, 0 errors.
  • The stamp on real tools: ShaderCaches.Translator gives ebbf160ce9626041 for both nightly-170-6c9edffe and
    nightly-171-b80a68f1, the same value Longshot's own translator_files.stamp() prints for them. Those two builds' caches
    carry the same 8-byte stamp (52 92 c2 c8 e9 04 28 c1) and their longshot_programs.bin are the same size to the byte
    (7,790,801,803), which is the case this is for.
  • The copy on a real cache (before the move was added; the move itself is covered by the checks, on NTFS): nightly-170's cache/ into an empty folder, 6 files, 10,177,506,912 bytes in 32.7 s
    (296 MB/s, NVMe), sizes and hashes equal. On the same PC nightly-171 prepared from nothing for about 25 minutes
    (20:34 to 20:59 by its files' times).

Not tested:

  • In the game: an update done by a launcher built from this branch, then the game's first start showing no prepare.
    It needs the next nightly whose translator matches.
  • The window itself (the Finish row's text while it copies).
  • Linux.
  • tests/test_release.py does not pass on this Windows PC (6 of 28: it starts a shell-script stub and compares text
    written with CRLF); this change does not touch scripts/release.py.
Every update makes the game prepare all its shaders again (about 25 minutes here), even when the new version translates shaders exactly as the old one did: the update builds into a new folder, whose `cache/` starts empty, while the old folder's 10 GB of caches would have been valid as they are. ## What changed After an update's build finishes and before Ready shows, the launcher compares the shader translator of the version being replaced with the new one's and, when they are the same, takes the old build's caches into the new game folder: moved when both folders are on one drive, copied otherwise. - **`Core/ShaderCaches.cs`** (new) - `Translator(toolsRoot)`: the stamp Longshot's `nx2/translator_files.py` makes of the translator's sources below `tools/gpu/vk` (the value `nx2/build.py` compiles in as `LONGSHOT_SHADER_STAMP`), followed by a hash of `translator_files.py` itself, so two versions whose recipes differ never compare equal by this one's. Null when the tools have no such file or lack a source. - `Keep(from, to, fromTools, toTools, ...)`: takes the cache files (the names of Longshot's `scripts/dev_packs.py` `CACHES` and `PLAY_FILES`, as its `--seed` does) when the new folder has none and both translators match. On one drive they are moved: each file gets its name in the new folder by a hard link (`FileLinks.TryHardLink`), all of them or none, then loses its old one, so it is instant and needs no room. Where a link cannot be made (another drive) they are copied and the old folder keeps its own. Either way each file is made as `<name>.part` and renamed only once all are there, so a cancelled or failed one leaves nothing half made. Skipped, with the reason, when: the old build has no translated programs, the new folder has caches of its own, the translators differ or either version's tools are not on this PC, the game is running, or (another drive only) there is no room for the copy plus 1 GiB. - **`App/ViewModels/BuildStepViewModel.cs`**: `Exited` goes through `KeepCaches` for an update before `Complete` (which prunes the old version's tools, needed for its stamp). The Finish row shows "Keeping the prepared shaders: N%" while it copies; Cancel stops it as it stops the build; the details log says what was kept or why not. New builds, rebuilds and replays are unchanged. - **Tests**: `tests/launcher_core/ShaderCacheChecks.cs` (the stamp against a hand-computed SHA-1, line ends, another translator, another recipe, missing tools, every refusal, a cancelled copy, the copy, the move, half a move) and `tests/test_launcher_core.py --longshot` now checks the cache names and the stamped folders and files against Longshot's `dev_packs.py` and `translator_files.py`. - **README**: a design decision entry. **`es.json`**: the two new texts; the other catalogs show them in English. The move follows the review: after an update the old folder is no longer listed, so its caches have no other user. Both folders never keep a link to the same file: the game appends to and rewrites these files. ## What it cannot know The game folds more into its stamp at start than the translator's sources (the GPU, its switches, the records' layout). The launcher cannot see that, so when it differs the game drops the copied files, exactly as it would have dropped them in place, and prepares as before: never a wrong cache. ## How it was tested Windows 11, .NET SDK 10.0.404. - `python -I tests/test_launcher_core.py --longshot <Longshot main @ e090cc48>`: `launcher core: 1050 checks passed`. - `dotnet build LongshotLauncher.slnx -c Release`: 0 warnings, 0 errors. - The stamp on real tools: `ShaderCaches.Translator` gives `ebbf160ce9626041` for both nightly-170-6c9edffe and nightly-171-b80a68f1, the same value Longshot's own `translator_files.stamp()` prints for them. Those two builds' caches carry the same 8-byte stamp (`52 92 c2 c8 e9 04 28 c1`) and their `longshot_programs.bin` are the same size to the byte (7,790,801,803), which is the case this is for. - The copy on a real cache (before the move was added; the move itself is covered by the checks, on NTFS): nightly-170's `cache/` into an empty folder, 6 files, 10,177,506,912 bytes in 32.7 s (296 MB/s, NVMe), sizes and hashes equal. On the same PC nightly-171 prepared from nothing for about 25 minutes (20:34 to 20:59 by its files' times). **Not tested:** - **In the game**: an update done by a launcher built from this branch, then the game's first start showing no prepare. It needs the next nightly whose translator matches. - The window itself (the Finish row's text while it copies). - Linux. - `tests/test_release.py` does not pass on this Windows PC (6 of 28: it starts a shell-script stub and compares text written with CRLF); this change does not touch `scripts/release.py`.
feat(update): keep the shader caches when the translator is the same
Some checks failed
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Compile and test (pull_request) Has been cancelled
0d2965d7d6
An update builds the new version into its own folder, whose cache/ starts empty, so every update prepared every shader again even when the shader translator had not changed. After an update's build, the launcher now stamps the old and the new version's translator from their tools (Longshot's nx2/translator_files.py recipe) and, when they match, copies the old build's caches into the new folder before Ready shows.
Member

Thanks, this fixes the case players hit most (25 minutes of shader preparation after an update whose translator is unchanged), and checking the stamp against translator_files.py is the right gate.

One suggestion: move the files rather than copy them, when both folders are on the same drive. After an update the old folder is no longer listed and the launcher can't start it (BuildRegistry.Replace; the README: "The old folder stays on the disk; nothing deletes it yet"), so its caches have no other user. A rename per file is instant, needs no second 10 GB, and makes the "no room for a copy" refusal unnecessary. Only a new folder on another drive would still need the copy, and your .part-then-rename scheme carries over to it as it is.

Where this goes next, on the Longshot side: I'm starting on a store for these caches that every game folder shares, keyed by the game's own stamps, as LONGSHOT_OBJ_STORE and LONGSHOT_LIFT_STORE are for the build (MagicBeanDev/Longshot#371, MagicBeanDev/Longshot#372). The program cache's stamp is the translator plus the cache format, and the pipeline cache adds the GPU and driver. Builds with the same stamps would use the caches in place, with no copy or move: an update, stable and nightly side by side, dev_packs.py runtimes. It needs:

  • a folder per stamp: today a build of another stamp truncates longshot_programs.bin and drops the pipeline files, so a single shared folder would have versions wiping each other;
  • a lock, so a second run with the same stamp at the same time uses its own cache/;
  • pruning, by last use and size.

The maintainers are fine with it moving the caches out of the game folder, which is where #321 put them (MagicBeanDev/Longshot#321). It ships off by default first, then the launcher sets it for the builds it makes, and this step can go then. That will take a while to land and be tested, so I'd merge this one meanwhile, with the move.

Thanks, this fixes the case players hit most (25 minutes of shader preparation after an update whose translator is unchanged), and checking the stamp against `translator_files.py` is the right gate. One suggestion: move the files rather than copy them, when both folders are on the same drive. After an update the old folder is no longer listed and the launcher can't start it (`BuildRegistry.Replace`; the README: "The old folder stays on the disk; nothing deletes it yet"), so its caches have no other user. A rename per file is instant, needs no second 10 GB, and makes the "no room for a copy" refusal unnecessary. Only a new folder on another drive would still need the copy, and your `.part`-then-rename scheme carries over to it as it is. Where this goes next, on the Longshot side: I'm starting on a store for these caches that every game folder shares, keyed by the game's own stamps, as `LONGSHOT_OBJ_STORE` and `LONGSHOT_LIFT_STORE` are for the build (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/371, https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/372). The program cache's stamp is the translator plus the cache format, and the pipeline cache adds the GPU and driver. Builds with the same stamps would use the caches in place, with no copy or move: an update, stable and nightly side by side, `dev_packs.py` runtimes. It needs: - a folder per stamp: today a build of another stamp truncates `longshot_programs.bin` and drops the pipeline files, so a single shared folder would have versions wiping each other; - a lock, so a second run with the same stamp at the same time uses its own `cache/`; - pruning, by last use and size. The maintainers are fine with it moving the caches out of the game folder, which is where #321 put them (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/321). It ships off by default first, then the launcher sets it for the builds it makes, and this step can go then. That will take a while to land and be tested, so I'd merge this one meanwhile, with the move.
Member

One more thing I missed above: #57 (Victor, WIP) builds an update in its build's own folder instead of a new one. With it, the update's cache/ simply stays where it is, along with longshot_options.ini, mods and the driver's per-program settings, and this step would have nothing to copy or move. It's worth agreeing between the two which goes in: #57 covers this case and more, and this PR (with the move) only helps while updates still go to a new folder.

One more thing I missed above: https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/pulls/57 (Victor, WIP) builds an update in its build's own folder instead of a new one. With it, the update's `cache/` simply stays where it is, along with `longshot_options.ini`, `mods` and the driver's per-program settings, and this step would have nothing to copy or move. It's worth agreeing between the two which goes in: #57 covers this case and more, and this PR (with the move) only helps while updates still go to a new folder.
cgize force-pushed feat/keep-shader-caches from 0d2965d7d6
Some checks failed
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Compile and test (pull_request) Has been cancelled
to 6ffdb721d7
Some checks failed
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Compile and test (pull_request) Has been cancelled
2026-11-15 07:00:36 +00:00
Compare
Author
Contributor

Thanks for the review! Done in 6ffdb72, and rebased on main (11dcb67).

  • Same drive: the caches are now moved. Each file gets its name in the new folder with a hard link (the launcher's FileLinks), all of them or none, then the old names go. No second 10 GB and no room check.
  • Another drive (the link fails): the copy as before, and the old folder keeps its own.
  • launcher core: 1050 checks passed; the move and the copy are both covered in ShaderCacheChecks.cs.

On #57: agreed, an update that stays in its folder makes this step unnecessary, and so will the shared store. I'm happy for this to be a stopgap until either lands, or to close it if #57 goes in first; your call and Victor's. Still not tested in the game.

Thanks for the review! Done in 6ffdb72, and rebased on main (11dcb67). - Same drive: the caches are now moved. Each file gets its name in the new folder with a hard link (the launcher's `FileLinks`), all of them or none, then the old names go. No second 10 GB and no room check. - Another drive (the link fails): the copy as before, and the old folder keeps its own. - `launcher core: 1050 checks passed`; the move and the copy are both covered in `ShaderCacheChecks.cs`. On #57: agreed, an update that stays in its folder makes this step unnecessary, and so will the shared store. I'm happy for this to be a stopgap until either lands, or to close it if #57 goes in first; your call and Victor's. Still not tested in the game.
Owner

Review notes (read from the source; no build or game run):

  1. With default settings this probably never copies. "Prepare shaders now" is on by default (LauncherSettings.PrepareShaders), so make_game starts the new game at the end of the build to prepare its shaders (nx2/make_game.py, prepare_shaders after the build's times). That writes the new folder's cache/ before KeepCaches runs, so Keep returns HasCaches after the 25 minutes are already spent. The copy has to happen before the prepare, which needs make_game's help (for example a variable naming the folder to seed the caches from, as dev_packs --seed does).
  2. Cancel during the copy loses a finished build. Stop() cancels and goes home, and KeepCaches then returns without Complete(). The new version is built on disk, but it never takes the old row's place and its folder is left orphaned. Cancel should stop the copy and still complete the build.
  3. A failure part-way through the renames leaves a partial set. If one File.Move fails, the files already moved stay and the remaining .part files are deleted (for example programs.bin without its .idx). That contradicts "nothing half made".
  4. The shader compiler isn't compared. Translator() hashes the translator's sources and translator_files.py, but not what compiles them: clang (llvm-mingw / the Linux LLVM), the Linux sysroot's libstdc++, the cpm SPIR-V headers, or build.py's flags. On Windows the game's translator_stamp() catches a different LLVM version through _LIBCPP_VERSION. On Linux (libstdc++, the 3u, 0u branch) it catches nothing, while the records keep Shader::Info exactly as laid out in memory. Until now, caches never crossed a toolchain, because each version had its own tools and folder. Suggest comparing the tools.lock sha256 of the compiler parts as well.

Also: if #57 lands (updates stay in their folder), the caches stay where they are and this has almost nothing left to do. It may be better rescoped to seeding a new build of another channel.

🤖 Review drafted with Claude Code

Review notes (read from the source; no build or game run): 1. **With default settings this probably never copies.** "Prepare shaders now" is on by default (`LauncherSettings.PrepareShaders`), so make_game starts the new game at the end of the build to prepare its shaders (`nx2/make_game.py`, `prepare_shaders` after the build's times). That writes the new folder's `cache/` before `KeepCaches` runs, so `Keep` returns `HasCaches` after the 25 minutes are already spent. The copy has to happen before the prepare, which needs make_game's help (for example a variable naming the folder to seed the caches from, as `dev_packs --seed` does). 2. **Cancel during the copy loses a finished build.** `Stop()` cancels and goes home, and `KeepCaches` then returns without `Complete()`. The new version is built on disk, but it never takes the old row's place and its folder is left orphaned. Cancel should stop the copy and still complete the build. 3. **A failure part-way through the renames leaves a partial set.** If one `File.Move` fails, the files already moved stay and the remaining `.part` files are deleted (for example `programs.bin` without its `.idx`). That contradicts "nothing half made". 4. **The shader compiler isn't compared.** `Translator()` hashes the translator's sources and `translator_files.py`, but not what compiles them: clang (`llvm-mingw` / the Linux LLVM), the Linux sysroot's libstdc++, the `cpm` SPIR-V headers, or `build.py`'s flags. On Windows the game's `translator_stamp()` catches a different LLVM version through `_LIBCPP_VERSION`. On Linux (libstdc++, the `3u, 0u` branch) it catches nothing, while the records keep `Shader::Info` exactly as laid out in memory. Until now, caches never crossed a toolchain, because each version had its own tools and folder. Suggest comparing the `tools.lock` sha256 of the compiler parts as well. Also: if #57 lands (updates stay in their folder), the caches stay where they are and this has almost nothing left to do. It may be better rescoped to seeding a new build of another channel. 🤖 Review drafted with [Claude Code](https://claude.com/claude-code)
Owner

My review above was written against 0d2965d7, before your 6ffdb72 (moving instead of copying on one drive). I re-checked it against 6ffdb721: all four points still hold. Fixes are on HumanBean's fork as review/pr78 (ab8a34a9), three commits on 6ffdb721. Take what you like:

  1. Take the caches before the build, so its prepare uses them. BuildStepViewModel.Run now calls ShaderCaches.Keep before make_game starts; the post-build KeepCaches and Exited(keep) are gone.
    • It was worse than I said: seed_pipelines writes cache/longshot_pipelines.bin whenever the tools ship a pipeline list, so even with "Prepare shaders now" off the old step could give up with HasCaches.
    • make_game accepts a target holding only cache/ and keeps it (check_writable uses exist_ok). Only seed_pipelines replaces longshot_pipelines.bin, and only when its stamp differs from the shipped list's.
    • New ShaderCaches.Undo puts the caches back if the build fails, fails to start or is cancelled. Moved files go back under their old names; copies are deleted. That also fixes point 2: nothing runs after the build any more, so cancel can't lose a finished build.
    • A copy to another drive now needs room for the build too (ShaderCaches.Room).
  2. All or nothing. If a rename fails, the files already placed are taken back before the error goes on, and after a move the old names are deleted only once everything is placed. There's a new check with a folder in the way of longshot_programs.idx.
  3. The same compiler, not only the same translator sources. New ShaderCaches.Compiler hashes the SHA-256 of each version's toolchain parts from its toolchain.json (Toolchains.PartShas):
    • Windows: llvm-mingw.
    • Linux: emsdk (clang), sysroot (libstdc++) and cpm.
    • A version whose record lacks them counts as different.
    • test_launcher_core.py --longshot checks those folders against tools.lock.
    • Two things are left out. build.py's flags aren't hashed: they're built at run time, and hashing the whole file would make nearly every pair of versions differ. Windows cpm (the SPIR-V headers) is a one-line addition to WindowsCompiler if you want it.

None of it has been compiled or run here; the tests were updated by reading them.

As dyst said, if #57 lands the caches simply stay in place for an update. This would then mainly matter for an update moved to another folder.

🤖 Drafted with Claude Code

My review above was written against `0d2965d7`, before your `6ffdb72` (moving instead of copying on one drive). I re-checked it against `6ffdb721`: all four points still hold. Fixes are on HumanBean's fork as [`review/pr78`](https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/compare/main...HumanBean:review/pr78) (`ab8a34a9`), three commits on `6ffdb721`. Take what you like: 1. **Take the caches before the build, so its prepare uses them.** `BuildStepViewModel.Run` now calls `ShaderCaches.Keep` before make_game starts; the post-build `KeepCaches` and `Exited(keep)` are gone. - It was worse than I said: `seed_pipelines` writes `cache/longshot_pipelines.bin` whenever the tools ship a pipeline list, so even with "Prepare shaders now" off the old step could give up with `HasCaches`. - make_game accepts a target holding only `cache/` and keeps it (`check_writable` uses `exist_ok`). Only `seed_pipelines` replaces `longshot_pipelines.bin`, and only when its stamp differs from the shipped list's. - New `ShaderCaches.Undo` puts the caches back if the build fails, fails to start or is cancelled. Moved files go back under their old names; copies are deleted. That also fixes point 2: nothing runs after the build any more, so cancel can't lose a finished build. - A copy to another drive now needs room for the build too (`ShaderCaches.Room`). 2. **All or nothing.** If a rename fails, the files already placed are taken back before the error goes on, and after a move the old names are deleted only once everything is placed. There's a new check with a folder in the way of `longshot_programs.idx`. 3. **The same compiler, not only the same translator sources.** New `ShaderCaches.Compiler` hashes the SHA-256 of each version's toolchain parts from its `toolchain.json` (`Toolchains.PartShas`): - Windows: `llvm-mingw`. - Linux: `emsdk` (clang), `sysroot` (libstdc++) and `cpm`. - A version whose record lacks them counts as different. - `test_launcher_core.py --longshot` checks those folders against tools.lock. - Two things are left out. `build.py`'s flags aren't hashed: they're built at run time, and hashing the whole file would make nearly every pair of versions differ. Windows `cpm` (the SPIR-V headers) is a one-line addition to `WindowsCompiler` if you want it. None of it has been compiled or run here; the tests were updated by reading them. As dyst said, if #57 lands the caches simply stay in place for an update. This would then mainly matter for an update moved to another folder. 🤖 Drafted with [Claude Code](https://claude.com/claude-code)
Owner

A second, independent read of the fix branch found one regression in it, now fixed. review/pr78 is at 07feb749:

  • On the move path longshot_pipelines.bin was moved too. make_game's seed_pipelines overwrites that file in place with the shipped list whenever its stamp differs, and the stamp depends on the GPU, so it does for most players. When the build then failed or was cancelled, Undo moved the overwritten file back, so the old build lost its own pipeline notes. Before this PR the old folder was never touched. The notes file is now copied, not moved, so the old one stays. Tests and README updated.

Worth a look, not changed:

  1. Undo after a cancel on Windows. MakeGameRun kills the process tree but waits only for the root, and Undo runs right after. A child (the game's prepare) can still hold a cache file, File.Move back fails silently, and the old build loses that file. A short retry in Undo would close the gap.
  2. The Windows compiler set leaves out cpm. On Windows, build.py's header_package takes the Vulkan and SPIR-V headers from RX_CPM too. It's a one-line addition to WindowsCompiler.
  3. A crash between the move's links and deletes leaves both folders sharing the files. After the deletes it leaves the caches in the new, unlisted folder, which a retry of the update reuses. Nothing is corrupted.

Checked and fine: make_game keeps a target holding only cache/, the toolchain.json keys match tools.lock's dests, the translator stamp matches translator_files.py, Undo runs at most once, and placement is all or nothing. Still not compiled or run.

🤖 Drafted with Claude Code

A second, independent read of the fix branch found one regression in it, now fixed. [`review/pr78`](https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/compare/main...HumanBean:review/pr78) is at `07feb749`: - On the move path `longshot_pipelines.bin` was moved too. make_game's `seed_pipelines` overwrites that file in place with the shipped list whenever its stamp differs, and the stamp depends on the GPU, so it does for most players. When the build then failed or was cancelled, `Undo` moved the overwritten file back, so the old build lost its own pipeline notes. Before this PR the old folder was never touched. The notes file is now copied, not moved, so the old one stays. Tests and README updated. Worth a look, not changed: 1. **Undo after a cancel on Windows.** `MakeGameRun` kills the process tree but waits only for the root, and `Undo` runs right after. A child (the game's prepare) can still hold a cache file, `File.Move` back fails silently, and the old build loses that file. A short retry in `Undo` would close the gap. 2. **The Windows compiler set leaves out `cpm`.** On Windows, `build.py`'s `header_package` takes the Vulkan and SPIR-V headers from `RX_CPM` too. It's a one-line addition to `WindowsCompiler`. 3. **A crash between the move's links and deletes** leaves both folders sharing the files. After the deletes it leaves the caches in the new, unlisted folder, which a retry of the update reuses. Nothing is corrupted. Checked and fine: make_game keeps a target holding only `cache/`, the toolchain.json keys match tools.lock's dests, the translator stamp matches `translator_files.py`, `Undo` runs at most once, and placement is all or nothing. Still not compiled or run. 🤖 Drafted with [Claude Code](https://claude.com/claude-code)
HumanBean force-pushed feat/keep-shader-caches from 6ffdb721d7
Some checks failed
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Compile and test (pull_request) Has been cancelled
to bf3d65a524
All checks were successful
CI / Compile and test (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 3s
2026-11-15 08:26:31 +00:00
Compare
HumanBean changed title from WIP: feat(update): keep the shader caches when the translator is the same to feat(update): keep the shader caches when the translator is the same 2026-11-15 08:29:01 +00:00
HumanBean deleted branch feat/keep-shader-caches 2026-11-15 08:29:05 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 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/LongshotLauncher!78
No description provided.