Tonight I shipped v0.9.2 of WaifuHatch — the Alchemy Update. The user-facing pitch is that there is now a building you can install in your private lab, called the Alchemy Table, and once it’s up you can do two things you couldn’t do before: convert any potion into any other potion at a flat fee, and mix two potions together when you use them, so both effects fold into the resulting render.
The mix part is the interesting one. The way it works in the inventory: certain potions are flagged mixable. When you click one of those, you don’t immediately confirm “use it”; the lab opens a modal that asks whether you want to use the potion alone or blend it with a second potion from your stash. Pick a second one, hit confirm, both potions decrement by one, and the generation pipeline gets both their tag-sets folded into the prompt at the same time. Chibi + Rage = a chibi rage girl. Painted Cinema + Ahegao = a painted-cinema ahegao. Compose what you want.
This is a design choice. It is not the obvious one, and the obvious one is wrong, and I want to write down why.
The default architecture for “things you can combine in a game” — the one that came out of decades of inventory-driven crafting systems — looks like this. You have a table somewhere of base items. You have a recipe table somewhere of combinations. When the player presents two base items to the workbench, the recipe table is consulted, the combination is matched, and a new item is produced. The new item is a thing with its own database row. Its own icon, its own name, its own description, its own balance number.
This is the right architecture for Minecraft, where one stick + one diamond + one stick at specific positions on a 3×3 grid produces a Diamond Pickaxe and not anything else. It is the right architecture for Stardew Valley, where one Pufferfish + one Sea Cucumber + one Pomegranate produces Maki Roll and the player should learn the recipe and feel rewarded for finding it. It is right whenever your output space is enumerable — when there is a finite, small, designer-curated set of valid results, each of which has its own identity and its own gameplay role.
It is wrong when your output space is generative.
The Alchemy Table outputs an image of a girl. Not from a list of fifty pre-rendered images keyed by recipe, but from a stable-diffusion model whose prompt is the concatenation of all the relevant tags. Chibi contributes chibi, super deformed, sd character, big head small body. Rage contributes furious, gritted teeth, glowing red eyes, screaming, dynamic motion lines. Sleep contributes `peaceful, eyes closed, lying down, calm breathing*. When you mix Chibi with Rage, the prompt the model sees is a tag-list that contains both, and the model — being a thing trained on the open internet’s idea of how images compose — produces a chibi rage girl. There is no need for me, the designer, to pre-author what a chibi rage girl looks like. The model already knows.
This is the architectural leverage I want to point at. When the output space is generative, the combination operator is concatenation, and concatenation is associative and commutative for free. I do not have to design a Chibi-Rage potion. I do not have to draw it an icon. I do not have to write it flavor text. I do not have to balance its rarity against the rest of the catalog. I do not have to garbage-collect it when one of its parents gets removed in a future patch. It does not exist as an entity. It exists only as a momentary fold of tag-lists at the moment of generation, and then it dissipates.
If I had shipped the eager version of this system — the version where mixable potions pre-compute their combinations as discrete inventory items — the math gets ugly very fast.
The current catalog has roughly forty potions in active rotation and another nine I added tonight as alchemy-only style modifiers. Suppose half of those are flagged mixable, which is conservative for where the system is heading. That is twenty-five mixable items combined pairwise into 300 distinct combinations. Each of those 300 combinations would require: an inventory row in the database, an icon (because empty inventory squares look broken), a localized name in seven languages, a localized description in seven languages, a stable hash so the same combo always points to the same image, a balance pass, and a tag-rule for what happens when the combo itself is then mixed with a third thing. That last one is where the architecture eats itself: pre-compute is recursive, mix-at-use is not.
Add one new mixable potion later and the catalog gains twenty-five new combinations to author and translate and balance. Add a tenth language and the localization team has to translate three hundred new strings before the new language can ship without missing keys. Remove one mixable potion and you have twenty-five orphans to tombstone, which is a real problem: players had them in their inventories, you have to refund them or repurpose them or migrate them, and every decision is wrong for some subset of users.
The mix-at-use architecture has none of these costs. Add a new potion with a tag-list and a mixable flag. It is automatically combinable with every other mixable potion. Drop a potion. It is automatically removed from every mixing partner’s options. Add a language. You localize one new potion’s name and description, never the combinations, because the combinations have no UI surface — they exist for the duration of one generation request and then they are gone.
This is the same lesson the database normalization people learned in the 1970s, applied to inventory: don’t store derived data. The combination is derived data. Storing it is the bug.
The counter-intuition that gets pre-compute shipped in games where it shouldn’t is that it feels like it gives you control. If I author the Chibi-Rage potion explicitly, I get to decide that its name is “Tiny Tantrum” and its icon is a clenched-fisted toddler and its tag-set isn’t just chibi+rage tags but also a third thing I want to add for flavor. The eager version offers me, the designer, the seductive lever of individuated authorship. The lazy version offers only the rule.
For some games, that lever is the whole point. Stardew’s Maki Roll should be its own thing with its own portrait and its own gift-preferences for villagers, because Stardew is a game whose entire texture is hand-curated artisan output. The recipe system is the point, not the implementation detail.
For WaifuHatch, the texture is the opposite. The game is always surprising the player with a generated image, and the player’s experience of the system is that they pull a lever and a thing they have never seen before comes out. The author is not the designer; the author is the model. The designer’s job is to expose levers that compose, and to keep the rules legible enough that the player’s intuition about what they’re going to get is roughly accurate. Chibi + Rage is legible: the player knows the result will be a small angry version. The model handles the rest. No one wanted me to invent a name for it.
So the architecture follows the texture. Generative texture wants lazy composition. Curated texture wants eager composition. Both are valid; only one of them was correct here.
There is a tactical version of this principle that I’ve started to phrase as an internal rule when I’m reviewing my own code: eager when the output is enumerable, lazy when the output is generative. Eager when you’re going to compute the same thing many times and the result is cacheable. Lazy when each invocation is unique and caching would be a lie. Eager when the cardinality is small and the schema is finite. Lazy when the cardinality is combinatorial.
The version of myself that doesn’t think about this defaults to eager, because eager-architectures look more “real” — there are rows in tables, there are entities with names, you can point at them. Lazy-architectures look like they don’t exist, because the artifact only exists during a request. That feels like nothing is happening. That feels like the system is “not built.” But the system is built; it’s built into the way the pieces compose. The combination potion exists, in some sense, the moment two mixable potions are designed; you just don’t have to enumerate the existence into a table for it to be true.
I find this satisfying as a working principle because it has saved me, repeatedly, from the same trap: building a CRUD system for something that should have been a pure function. The trap is shaped by the language we use — we say “the Chibi-Rage potion” the same way we say “the iron pickaxe”, and the grammar implies an entity, and the entity implies a database row, and the database row implies migrations and orphans and translations and icons, and then six months later you cannot ship a new base item without a sprint of catalog work.
The fix is to notice that some nouns are verbs in disguise. “The Chibi-Rage” is not a thing; it is the result of chibi-raging, an action two existing things compose into, which dissolves the moment it’s done. Once you see it as a verb, you stop trying to store it.
The Alchemy Table is live tonight. I am writing this from the post-ship state, which is a state I don’t talk about often enough — the quiet hour after the deploy, when the changelog is up and the Discord post is up and the early reactions are coming in but the player base hasn’t woken up yet, and I get to sit with the thing I built and ask whether it was the right shape. Tonight it was. The architecture is small, the catalog is twenty-five lines of JSON, the new server route is one file, and it does the thing it claims to do: it lets the player compose what they want at the moment they want it, and the result is whatever the model makes of the tags.
The combinations don’t exist. Only the rule does. That’s the part that worked.