Writing ·
Reading a spec while it is still being written
A drawn sprite takes four minutes to arrive. Filling that wait with the sprite itself rather than a progress bar turned the order of two keys in the format into something load-bearing, and moving one of them puts the first pixel at 100% of the document instead of 46%.
I have a tool that turns a sentence into pixel art. A model writes a small JSON document describing the sprite and a renderer I wrote draws it. For a hand-drawn sprite, one where the model places the pixels itself a row at a time, writing that document takes about four minutes.
Four minutes is a long time to look at nothing. The obvious thing to put in the gap is a progress bar, so that is what was there. It came out again, and what replaced it was the sprite.

Those six frames are real states off the streaming pipe, not a mockup of one. Twenty five states go
past on the way to that cat and this is every fifth. It prints downwards like a printer, because
rows is a list of strings and each string is a line of pixels.
Taking the bar out is what led to everything else in this piece.
The bar was a clock with a percent sign after it
Nothing measured that bar. It could not have. The only honest inputs available were how long the last few draws took and how long this one had been going, so it was a timer dressed as a measurement. It moved smoothly, it reached about ninety percent, and then it sat there, because the thing it was guessing about had not finished.
I also built a pane that showed the model's reasoning as it arrived, deployed it, and cut it the same evening. It filled the wait, but it put the spec format on screen in a place the rest of the product deliberately keeps it out of. That left the wait empty again, which turned out to be the right problem to be stuck on.
The document is arriving the whole time. It arrives in a useful order too: the canvas size and the kit first, then the parts as a list of self-contained objects. If you can read it before it is finished, the wait stops being a wait, because the sprite builds itself in front of you while the model writes it.

Both were on screen together for one deploy. The bar was the bigger of the two, and it was the one that knew nothing.
What replaced the bar is a number I can defend. The builder exposes the row a drawing has reached, which is the line the model is writing at that moment. It is neither a guess nor a total. It is read off the document, and when the drawing places itself from the far edge of the canvas the number is not shown at all, because a negative coordinate is resolved by the renderer and the builder is not the renderer. A missing line beats one drawn in the wrong place.
The scanner is deliberately not a JSON parser
This is the part I expected to be harder than it was. A partial JSON document is a syntax error to anything that expects a whole one, so a parser is no use until the last brace, which is exactly the moment you no longer need it.
So the thing that reads the stream does not parse. It scans for the three structures it knows about and ignores everything else, emitting four kinds of event. It never holds a complete document and never tries to.
model writes scanner emits
------------------------------------ ----------------------------
{"name":"cat","size":[32,32],
"kit":"warm","parts":[ meta size and kit known
{"shape":"art","at":[4,2],
"legend":{"o":"line", ...}, drawing at and legend known
"rows":["........", row 0
"..oooo..", row 1
".oXXXXo.", row 2
...
]} part complete, replaces the rows above
]}
Four event types, and only the last of them carries a finished object.
Two things fall out of that, and the second one was a surprise.
The first is forward compatibility for free. A shape I have not written yet still streams, because the scanner is not matching on shape names. A part is a part. Nothing needs updating when the vocabulary grows.
The second is the rule the scanner is not allowed to break, which is that it must never emit something incomplete. A half-written part is not drawn, it is waited for. A shape missing its role would throw in the renderer, and a throw does not show a partial sprite. It ends the show, in the middle of the wait the feature exists to fill. Every intermediate state has to be a legal sprite in its own right, or the feature is worse than the progress bar it replaced.
The order of two keys stopped being free
Here is the finding, and it is the reason this was worth writing up.
A drawing part has three fields that matter: at, which says where on the canvas it sits, legend,
which says what each character in the rows is made of, and rows, the lines of characters
themselves.
Before streaming, the order of those keys was arbitrary. The document was parsed once, when it was complete, and JSON object key order changes nothing. Any order was as good as any other.
Once the renderer became incremental, the order started deciding what the person sees. A row on its own cannot be painted. It is a line of letters until the legend says what those letters are made of.
legend first rows first
--------------------------------- ---------------------------------
{ "shape": "art", { "shape": "art",
"at": [4, 2], "at": [4, 2],
"legend": { "o": "line", "rows": ["........",
"X": "coat" }, "..oooo..", <- letters
"rows": ["........", ".oXXXXo.", with no
"..oooo..", <- paints ".oXXXXo."], meaning
".oXXXXo.", as it "legend": { "o": "line",
".oXXXXo."] } arrives "X": "coat" } }
Both of those describe the same cat, and both render to the same pixels. The one on the right produces a drawing head with no legend in it, so nothing can be drawn until the part closes.

One sprite, one scanner, one pair of keys in a different order.
I measured it rather than assuming it. All fourteen hand-drawn sprites in the repository, replayed through the scanner twice, once as written and once with the two keys swapped. Written with the legend first, the first pixels land at between 32% and 48% of the document, with somewhere between seventeen and thirty three drawable states on the way to the finished sprite. With the legend moved after the rows, every one of them puts the first pixels at 100% and has one state, which is the finished sprite appearing all at once.
The important part is what did not change. A legend written after the rows is still a completely legal spec. The validator accepts it, the renderer draws it, and the finished sprite is identical to the pixel. Nothing got stricter.
What changed is the field list the model is shown. It writes a part in roughly the order it is
given the fields, so declaring legend before rows in the schema is what actually produces
sprites that stream. That line in the shape definition used to be documentation. It is now a
performance characteristic, and it is one that nothing in the type system or the test suite would
have flagged if somebody had tidied the two fields into alphabetical order.
Eleven of the fourteen are written the good way. The other three, goat.json, grey_rabbit.json
and white_duck.json, have their keys the other way round, and those three show you nothing at all
until the final brace arrives. Same format, same scanner, same renderer, one swapped pair of keys.
I found the three by writing the measurement rather than by noticing them, which is the argument for writing the measurement. My first count was ten sprites and one offender, because I had reused a filter from the test suite that also excludes sprites with an outline and tiled ones. That filter is there for a different question, and borrowing it quietly answered a different question than the one I was asking.
What it took to believe it
The two claims the feature rests on are both about states nobody would ever look at individually, which is the kind of thing that needs a machine to check.
Every state a viewer passes through has to be a legal sprite that renders. And the last state has to be the finished sprite, pixel for pixel, because a preview that converges on something slightly different is worse than no preview at all: the sprite would visibly change at the moment it was declared done, and the thing that changed would be the thing the person had been watching.
So the test replays every spec in the repository, all 332 of them, through the whole pipe. Each one is checked three ways: that every intermediate state renders without throwing, that the final state is pixel-identical to the finished sprite, and that the canvas size is known before anything is drawable to go on it. That is 999 tests in one file.
Two more sit on top. One replays everything at chunk sizes of 1, 3, 64 and 4096 bytes, because a real stream is split by the network wherever it likes and a scanner that only works on tidy boundaries would pass every other test in the file. The other runs the genuine production path, scanning in the Worker, writing each event to the wire, reading the wire back and building from what arrives, which is the only test that catches a mismatch between two halves that both pass their own suites.
There is one more that I like more than it probably deserves. A drawn sprite should only ever gain pixels while it paints, like a printer, so the test walks the states and fails if any pixel that had been placed is ever empty again. It only applies to single-part drawings without an outline, because a sprite with parts over the top of each other is meant to cover its own pixels and one with an outline redraws its edge as the silhouette grows. Both of those are correct and neither is monotonic, so checking them would have been checking the wrong thing.
It is live at bitwright.app. The bit I keep thinking about is not the scanner, which is about three hundred lines and mostly obvious once you stop trying to parse. It is that making the output visible earlier reached back into the format and gave a key order a job it did not have before.