Finishing the Blobber repo split into pinned packages
All ten phases of the split are landed. The game repo went from 22,147 tracked files to 3,217, and a full suite run on the result passed 254 of 256 tests.
The repo split is finished. The game repo went from 22,147 tracked files to 3,217, the tools and assets live in their own repos that the game pins by commit, and the game itself is split into packages with their own tests, docs and work lists. I ran the whole suite once at the end with nothing else running. 254 of 256 tests passed, one was skipped from the cache, and one failed.
The failure is world_frames, a frame-time check. It failed the same way before the split, so I’m leaving it alone for now.
How are the packages checked?
packages.json at the root lists the seven game packages (core, ui, actors, levels, screens, editor, shell) and the non-game ones as one ordered list of path rules. The first match wins. Each package names the packages it may use and its API files.
tools/package_check.py puts every tracked file in exactly one package. It fails on any backward dependency that isn’t recorded in docs/packages/ratchet.json, which holds the 162 there are today. Actors reaching into levels is 91 of them. I planted a Party.new() in the game clock to see if it would be caught, and it was refused. The suite and every landing run the check first.
Why does the test cache need so many picks?
Every test and film belongs to a package, and suite.sh --package runs one package’s tests. A result cache skips a test that already passed with the same key. The key is a hash of everything the test can see, and it’s shared by every worktree. Computing all 256 keys takes about 0.6 seconds, and a second run of one package with everything cached takes 2 seconds.
The picks are still large. The backward edges put nearly the whole game in reach of nearly every test. The cache and the picks will shrink as those edges get removed.
How do the other repos get pinned?
The dungeon generator is its own repo now, with 47 Python files, 18 tests and a CONTRACT.md that names every game path it reads or writes. The game keeps a pin, which is the commit it uses, and one entry point: python tools/dungeongen.py <tool>. tools/deps.py sync unpacks the pinned commit once into a folder every worktree shares, and a second sync with nothing to do takes 0.08 seconds.
The art kit followed: the Blender kit, fonts, and the paint, texture, thumbnail, catalog and ambience tools, 617 files. It keeps the game’s folder layout so every model’s pipeline record still points at the right file. The path helper every tool uses became a third small repo, BlobberCommon, pinned by the game, the kit and the dungeon generator. The first worker on this froze for an hour on a refused file write, so I restarted it and the second one finished. The model pipeline test (2,330 checks over 2,212 models), the workbench (646) and the dungeon tests all pass through the pins.
I also wrote tools/port_to_pinned.py. It carries changes that paused worktrees made to moved files over into the new repos, so nothing in flight got lost.
What did moving the assets turn up?
The assets are 18,294 files, and they left the game’s git for their own repo, BlobberAssets. Its first commit is byte for byte the old assets/ folder. In each checkout, assets is now a junction to one shared, read-only checkout of the pinned commit. A fresh worktree gets its assets in about 10 seconds instead of copying 273 MB.
The worker found two surprises by measuring. Godot rewrites every .import sidecar on import even when nothing changed, so a read-only checkout failed 6,821 writes. Each pin now has a stored copy of its import products (13,548 files) that gets laid into the checkout’s import cache. And git worktree remove --force follows a junction and deletes what it points at, so the pinned checkouts are write-protected for real and every prune unlinks the junction first. To write assets I run deps.py open, then bump, which commits in the assets repo and moves the pin.
What happened to the skills and hooks?
Once the repos existed I asked for the skills and hooks to be split too. Each repo owns its skills as a Claude Code plugin. The three modeling skills moved to the art kit, and the game keeps its own. The five shared hooks (the token guard, the Review Desk and push-notice checks, the usage ledger and the context checkpoint) live once in BlobberCommon. Each repo switches them on in its own settings, so a session in the kit gets the same guard rails as one in the game without copies to keep in step. The skills check found 0 broken references across 28 files in 5 repos, and the hooks test passed 22 of 22.
What did the package docs and work lists change?
Each game package has its own docs and work list now. I split the 664 open items by feature: 220 for the levels, 92 for the actors, 36 for the screens, 16 for the editor, 10 for the workbench, 6 each for core and the shell, and 279 that are about the whole project. Every item kept its id, and a hash check of all 1,302 items came out the same before and after. 26 architecture pages moved in beside their packages.
The workbench picks became requests, kept with the workbench and only read by the art kit. While planning this I found that the audio effects would have had to sit inside two Godot projects. I didn’t want things in two places, so I added a phase that makes one shared copy reachable from every project. A pinned repo can now sit inside more than one Godot project, and pinning one that Godot reads imports it first in a scratch project. If Godot would write into the checkout, the pin is refused and the old one is kept. In the trial, one checkout was junctioned into two projects and both loaded the same script with the same uid.
Which packages moved on disk?
Two, the first ones with nothing reaching into them. The level builder joined the editor add-on, and the model workbench became addons/blobber_workbench/. tools/move_package.py did it in one run: 71 files moved and 59 lines in 30 files rewritten to the new paths. It’s a dry run unless told otherwise, and it refuses anything unsafe, so later packages can move the same way once their backward calls are gone. The count of backward edges stayed at 162.
How did the numbers change?
I ran the dependency report again on the result:
- Tracked files in the game repo: 3,217, down from 22,147.
- GDScript files: 1,025, down from 1,036.
- Python files: 94, down from 339.
- File-to-file edges: 9,119, down from 10,088.
The whole suite took 44 minutes on the clock and 251 minutes of test time.
What’s left is decoupling actors from levels behind a small API, then moving the rest of the packages one at a time as their backward edges reach zero. I’ve filed both. I also had the work list cleaned up along the way, and 275 proposed closes, cuts and merges are waiting for my yes or no.