Day 16

A small contract for my asset folders


For a while my asset folder had turned into a place where things went to hide. Sprites here, static imports there, a config.items.sprites mapping somewhere else, a BAGS constant, an ORE_ITEMS list, and seventeen direct JSON imports in a file called blockAssets.ts. Every time I moved a PNG or renamed a folder, something broke in a way I only noticed when a menu suddenly rendered flat-colored trees. So this round was all about the folder structure itself.

The idea: a small contract with a big playground

The rule I landed on is simple. There is a short list of category root folders, and they are the only fixed part of the system: _avatar, _spawns, _items, _blocks, _vegetation, _objects and _cursors, with room to add more when new kinds of content show up. The underscore prefix marks them as the contract. Everything inside them is free organization. I can rename files, shuffle subfolders around, dissolve a group, it does not matter, the game does not care. Moving a file between categories fails loudly at the build, which is exactly what I want, because that usually means I am changing what an asset actually is.

That last part was the real insight of the session. My original plan was to derive gameplay mappings from the folder structure, so a file’s location would tell the game which bag it belongs to. I rejected it halfway through. Folders describe what an asset is, bags describe what it does in the game. A tree log lives in the material folder but belongs in the timbering bag. If the folder were the identity, every cleanup move would silently change gameplay.

Identity moves into the assets

Instead, the identity now lives with the asset itself. Every atlas item is a JSON plus PNG pair, and the JSON carries what the game needs to know: a type, HUD display info, and which bag it belongs to. The bag lists, the item sprite mapping, the block colors, all of those hardcoded tables are gone. The build script scans the assets, checks that every file is actually claimed by a pipeline, and spits out a generated manifest that the game imports. Deleting a category folder or sneaking in an unlisted top-level folder fails the build too, so the contract cannot quietly rot.

The nicest side effect: my HUD icons went through the same door. The toolbar, the bags, the plant panels, block thumbnails, all of them used to import PNG files directly. Now they render from atlas rects by item type. Which means the storage icons are just normal items in _items/, and I can put them wherever I like.

An asset JSON file with its type, hud display info and bag membership next to the folder structure of the asset categories
The metadata lives with the asset: type, HUD info and bag membership in the JSON

The old environment/ folder got dissolved in the process. Trees moved into _vegetation/, props into _objects/. I briefly kept a grouped folder with sub-pipelines, then realized a growing environment/ tree would recreate the exact mapping pain one level deeper. Much more vegetation content is coming anyway, so flat categories age better.

And of course, a bug found me

While restructuring I noticed freshly generated worlds sometimes had single floating leaf tiles near the basecamp. Not every world, and not in a pattern any amount of code reading could explain. I had three plausible theories and none of them survived contact with reality. What finally worked was brute force: a little harness that generates fresh worlds through the real build and stamp pipeline and counts orphans. Eight out of forty worlds had them before the fix, zero out of a hundred and fifty after. Turns out the region carving cleared the trunk column deeper than the leaf columns, so whole tree clusters now get removed when a stamp touches them, instead of single columns.

I also taught water a new trick while I was in there: floods now douse torches. The torch relights when the water recedes, since the doused state is derived from the water level and not saved anywhere. That one was a freebie, but it makes flooding a real tool instead of a cosmetic nuisance.

The asset build is now the strictest thing in my codebase, and honestly the codebase is calmer for it. Next up on my list is feeding all that coming vegetation content into the pipeline and seeing if the contract survives contact with an actual jungle. Follow along if you want to find out with me.