Repository navigation
feat(dev): test a change against a base, as a pack of what it changes - #2732
klimovich008 wants to merge 20 commits into
Conversation
scripts/dev_packs.py builds the game once as a base and each change as a pack: the game for host/ and nx2/, the renderer alone for gpu/, a prepare when the shader translator changed, chosen by the changed paths. A runtime is the base with romfs hard-linked and the packs' modules over it. New bases seed their shader caches from an older game folder. It calls make_game.py and build.py as they are; nothing players build changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntracked sources A review found that choosing modules by path missed renderer files the game is built from, non-gpu files the renderer is built from, generated files (button prompts) and untracked sources. A pack now builds the renderer alone only for gpu/vk files the game does not read (found from host/'s includes and build.py's dependencies), and otherwise builds the game and keeps each module whose linked objects differ from the base's, plus generated files that differ. Module names follow RX_TARGET. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… deleted prompts Review round 2: a header change can reach only hos_main.c or vk_menu.cpp, the objects that hold the version, so any header change now counts them; an nx2/ file other than the translator's, or a non-C file under host/, gpu/ or third_party/, reaches the modules outside their objects (flags, link options, the manifest), so both modules are kept; a generated prompt the change no longer makes is removed from the runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a clean output folder Review round 3: a64-kit's toolchain.py is read by the lift and also sets the link options, so the build's own scripts (build.py and what it imports) now keep both modules whether or not the lift reads them; the output folder is cleared but for romfs before each game pack, so an earlier pack's files are not taken for this one's; any of the base's files outside romfs that the change's build no longer makes (the shipped pipeline list) is removed from the runtime, the files the game writes as it runs aside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts can go Review round 4: make_game.py sets build.py's environment (the hot order, the profile), so a change there keeps both modules; and a played base holds saves and settings no build makes, so a pack now only removes files the base's own build made, which `base` records (an adopted base records none). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w base folder Review round 5: a pack name such as ../game would delete the base, so names are plain; a base adopted from its own packs' folders would be rebuilt by them, so that is refused; a base is built only into a new game folder, so a played folder's saves are never recorded as build outputs. The test writes the map only where the platform has one (Linux has none). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…played-folder check comes first Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the base's language Review round 6: a base folder inside a pack folder (or the reverse) is refused, not only the same folder; a runtime inside the base or a pack folder is refused before anything is copied; the base records its language (base --language, adopt reads language.txt) and its packs are built in it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s; a launcher layout refused Review round 7: overlaps() resolves links and junctions (realpath), and folders on two drives never overlap instead of raising; a game folder with its modules in bin/ (make_game.py's launcher layout) is refused with a reason rather than failing on a missing module. The link case is tested with a junction where links need a right Windows has not given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 8: a base whose work or game folder is on another drive than its packs or a runtime failed at the first hard link; the objects and romfs are copied there instead, as make_game.py does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uilt on Linux prepares at its first start Review round 9: RX_TARGET=Windows chose the Linux names; and rt ran hookshot.exe on Linux for a prepare, which make_game.py leaves to the game's first start on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, all three are done in c3f666f:
The Linux line stays in the description. |
…d runtimes never leave the PC Review (hckrman101): pack.json is a plain file, so rt now refuses a module name that is not hookshot/hookshot_vk and any generated or deleted path that is absolute or resolves outside the runtime, before anything is made; the docs say that bases, packs and runtimes are built from the game and are never sent; the AGENTS.md bullet is cut to the pointer and the rules agents need. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s written A second review of the guard (sol, on c3f666f) found two ways past it: - the check ran before the runtime existed, so a link in the base's romfs, cloned into the runtime, could carry a deleted path outside it; - normcase let ../Runtime pass as the runtime in a case-sensitive folder. in_runtime now judges a pack path by its text alone: relative, not rooted ("/x" is not isabs on Windows since Python 3.13), no "..", not ".". No name comparison is left to fool. Where each file is written or removed, after the runtime exists, resolves_inside checks that its folder, links followed, is the runtime or below it, compared by file identity; a link itself is removed, never written through. Tests: the reviewer's cloned-link case (skipped where the account cannot make a file link, as on Windows without developer mode; CI's Linux runs it), resolves_inside with a folder link or junction, and in_runtime's text cases. With resolves_inside disabled the junction case fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h the base) Sol on ac5af7b: romfs/foo:evil passed both checks and named a stream of the hard link romfs/foo shares with the base, so removing or writing it changed the base too. Pack paths never hold a ':'; in_runtime refuses one. The case test now runs only where the temporary folder is case-insensitive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Extra hardening of point 2 in ac5af7b and 1307585: |
…the runners) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se at a time A development base prepares the whole game's ~51,000 predicted pipelines, about 30 of a base's 41-60 minutes, though a test draws a few scenes. base --predict-s N puts RX_VK_PREDICT_S=N into make_game.py's environment (and rt's re-prepare): the rest are built in play when first drawn (0, the default, builds them all; a player's build never uses it). warm plays a runtime with no pack once per --run command and copies the caches that grew back into the base, so later runtimes start warm for the tested scenes. base, pack, rt and warm hold <base>/dev_packs.busy, so a second job on one base is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: make_game.py's lift key hashes every RX_ variable, so a budgeted base now builds with HOOKSHOT_NO_PREPARE=1 and runs the game's preparation itself with RX_VK_PREDICT_S. warm copies back only plain cache files of the runtime (never a link), through a new temporary file in the base's game folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
I merged 1307585 into current main and ran tests/test_dev_packs.py, which passes. The two red jobs are not from this PR. One is test_release_scripts.py (test_nightly_two_runs_at_once_end_at_the_newer_commit), which is flaky. The other is test_pipeline_cache_save.py on Windows part 1, a timing wait on the in-play save. This PR only adds scripts/dev_packs.py, its test and docs, so a re-run should clear both.
hckrman101's points are all addressed. Docs and AGENTS.md now say plainly that bases, packs and runtimes never leave the PC. rt refuses absolute paths, .. paths and module keys other than the two modules, and re-checks each destination by file identity, and test_guards has a refusal case for each. The AGENTS.md bullet is down to three lines. I also read clear_output and the pack rmtree: both only work inside the dev folder, and the pack name is validated before the delete.
The one gap is Linux (hookshot/hookshot_vk.so), which the description already states. That's fine for a development tool.
…n the Linux runner) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testing a change in the game means a new full build: a new work folder translates the game again, and a new game folder prepares every shader from cold, 69 minutes in all on an RX 9070 XT. Most of that is the same for every change. This adds a development tool that builds the game once as a base and each change as a small pack over it.
What changed
scripts/dev_packs.py(new, development only):basebuilds a checkout at a commit.--seedcopies an older game folder's shader caches first, as an update keeps them.packbuilds a change. Commits, uncommitted edits and untracked files all count.RX_VK_ONLY, seconds): when every changed file is a renderer file the game isn't built from. Those files are found in the checkout fromhost/'s includes andbuild.py's dependencies.make_game.py, which builds both modules.objs.rsp,vk_objs.rsp) differs from the base's. Bytes can't decide this:nx2/version.pyputs a fingerprint of every source into both modules.nx2/, or a non-C file changed.rtmakes a runtime: the base withromfshard-linked and the other files copied, the packs' files over them. It runs a prepare when the shader translator or the seed changed. Two packs that bring the same file are refused, and so is a pack built on another base.docs/development.md: a new section, Testing a change against a base.AGENTS.md: one bullet pointing agents to it.make_game.pyandbuild.pyas they are; no build or CI file is touched.Testing
tests/test_dev_packs.py(no game):build.py's stamp, and that the version-stamped objects arebuild.py's;Ten review rounds with gpt-6.1-sol, every finding fixed; the last found none.
End to end on Windows (Ryzen 7 5700X3D, RX 9070 XT), packs over a full build of 57ed3aa:
host/nvn_native.chookshot.exehost/nvdrv.c,vk_memory.cppvk_replay.cpphookshot_vk.dllEach pack's modules are byte-identical to that change's own earlier full build, apart from 2 PE header bytes. The renderer-only route gives the same DLL as
make_game.pyfor the same tree.Seeded base on main 4678050 from 57ed3aa's caches:
In the game: a runtime made by
rtfrom two packs over 57ed3aa (the host-only pack's exe, with its new switches off, and the renderer-only pack's DLL):Not tested: Linux. The module names there are
hookshotandhookshot_vk.so, andRX_TARGET=windowsuses the Windows names; neitherpacknorrthas run there.Related: the dev-loop proposal (#2636). This covers part of its "middle loop" without changing the build.
🤖 Generated with Claude Code