Product ·
Bitwright
Pixel art described as data rather than generated as an image, so fifty sprites drawn one at a time still look like one artist made them.
Every AI art tool can draw you one good sprite. None of them can draw you the fiftieth one that still belongs beside the first. That is the problem worth solving, because a game does not need a picture, it needs a set.
Bitwright does not generate an image. You type a sentence, a model writes a small data file, and a renderer I wrote draws it pixel by pixel. What comes back is not a picture of a sprite, it is the recipe: an ordered list of shapes, each naming a colour role rather than a colour, with a frame count and what drives the animation. You can open it, take it apart, change one part, and redraw.

Ten sprites for a small game, each drawn from its own sentence and each matched to the drone. Nobody chose a palette.
Why the consistency actually holds
Two mechanisms, and neither of them is a better prompt.
A part names a role, not a colour. A crate does not say (46,54,72), it says casing. The kit decides what casing is. Point a new sprite at one you already have and it copies the role table, so the new thing is made of the same material as the old thing by construction. Drag casing to gold afterwards and every part made of it follows.
The vocabulary is closed. Eighteen shapes, a fixed set of glyphs, a named list of roles. Anything outside that is rejected by the validator with a message naming what was allowed, which is what makes a spec written by a model safe to accept. A silently ignored field is the dangerous case: it renders a sprite that is wrong in a way nobody goes looking for.
It came out of a real mess
I have a colony game with 114 sprite generators in it, about 15,400 lines of Python. Before designing anything I measured what was actually in there. A px() helper redefined in 74 of them. The outline pass in 77, under three different names. The casing colour written five different ways.

Nobody decided this. It is what happens when a colour is typed rather than named.
Porting 42 of those sprites onto one role table took the distinct colours across them from 32 to 25. Eight values stopped existing. Side by side you cannot tell which is which, which is exactly the point: invisible in play, and one fewer thing that can quietly drift apart over a year.
What it will not do
Machines and items port well. Characters, animals and crops do not, and the game keeps those on a separate path for the same reason. It only draws in the style of a kit it has been given, so it is a consistency engine rather than a creativity engine.
It also draws sprites, not tiles. I found that out by building a real game with it: the floor plate came back outlined on all four sides, because every sprite is drawn standalone, and tiled that outline becomes a hard grid that fights everything standing on it. The renderer can wrap now, so all eighteen shapes tile without any of them knowing about it, but that turned out to be necessary and nowhere near sufficient. The grid in that floor was drawn art, not an artefact of the outline. A tile needs the model to draw a surface that reaches all four edges rather than a subject with margins around it, and that is composition, not rendering.
Built to be checked
The renderer is deterministic: same spec, same bytes, forever. That is not tidiness, it is what makes the tests possible. The first thing it had to do was reproduce sprites the game already shipped, pixel for pixel, before anything was built on top of it, and reference PNGs are committed as fixtures so the fidelity test cannot silently skip on a machine without the game checked out.
Three real bugs came out of that port and every one of them looked like tidying up. Post positions have to floor rather than round, because with an even column count the centre lands on a half pixel and rounding splits a pair. Meter segments have to round from both ends so they tile exactly at any height. The lightning glyph is lifted pixel for pixel off the shipped sprite, because the old generator drew it as a polyline and what ships is what that rasterised to, not what a tidier bolt looks like.
Running it
A Cloudflare Worker with one route on it. Nothing anyone types is stored: the usage table records what a draw cost and how long it took, not the sentence, not the spec, not the image. Model and effort are environment variables rather than constants, so the quality against money dial can be turned without a code change, and which setting to use was decided by running the same ten prompts through each one and looking at the results rather than by reasoning about it.
A public write-up of the most useful thing this project taught me is in when the model keeps asking for the same thing.