Choosing Where It Will Hurt
With the derive family finished, the language had run out of obvious tidying — the core was mature, and building more applications in the domains already explored kept confirming it was fine rather than finding what was wrong. So the next move was not to guess at features but to measure: which capabilities does the language genuinely lack? The answer came from grepping the runtime, not from imagination — and it turned a vague 'build more dogfoods' into a precise list of four tools, each chosen because building it would force one specific missing power into existence.
By this point the language had a comfortable problem: it kept passing its own tests. The self-hosting core was proven, the derived behaviours were complete, and each new application built in a familiar domain — another web service, another command-line filter — confirmed that the language was fine rather than revealing where it wasn’t. That is a pleasant place to be and a dangerous one to stay, because a program can only surface the pain its own shape touches, and four programs from the same country will eventually agree that everything is fine — not because it is, but because they never reach past what they already do. The dogfood method had been the engine of the whole project, but it was idling.
The method turned on itself
The fix was to point the method at a different question. Not “what should I build?” but “where would building something hurt?” — because pain is the signal, and the goal was to provoke it deliberately rather than wait for it. That reframing changes what counts as a good next project. A good one is not the one that produces the most useful tool; it is the one that walks straight into a capability the language does not have, so that finishing it requires growing that capability. The tool is the bait; the missing power is the catch.
Measuring the gaps instead of guessing them
The important discipline was to find those gaps by measurement, not intuition. It would have been easy to sit down and imagine what the language might be missing, and easy to be wrong — to invent a gap that isn’t really there, or to overlook one that is. So instead of imagining, the runtime was interrogated directly: grep for a way to start another process — none. Grep for raw terminal input, a single keypress without waiting for a line — none. Grep for a dedicated bytes type, or any handling of binary data that a string with an embedded zero wouldn’t corrupt — none, only a workaround of encoding bytes as hexadecimal text. Each of these was checked against the actual code rather than assumed, and each came back as a real, confirmed absence. A grep that finds nothing is a more honest map of the frontier than any amount of speculation.
A vague plan becomes a precise list
Out of that measurement fell a short, concrete list, and each item was paired with the one capability it would force. A build tool or task runner cannot exist without the power to run other programs — so it would drag process control into the language. An interactive terminal program — a small game, a text UI — cannot exist without raw keyboard input, so it would force terminal control. A parser for a binary file format — and the sharpest choice here was the compiler’s own WebAssembly output, so the language would be reading the very binaries it emits — cannot exist without honest binary handling. And a browser game, drawn and driven through the frontend bindings, would exercise interactive web input while doubling as something people could actually play. Four tools, four powers, each gap named before a line was written.
Why “hurts differently” is the whole point
The unifying idea across the four was that each was chosen to hurt in a different place from the others and from everything built before. The earlier applications had all lived in one region — string and record work, bound by input and output — and had thoroughly mapped its pains. These four deliberately left that region: one into process control, one into raw terminal I/O, one into bytes, one into the browser. The point of spreading them out was coverage, in the same spirit that the language holds four code-generation backends in agreement. A gap only shows up when a program of the right shape walks into it, so the way to find the gaps is to send programs of deliberately different shapes in deliberately different directions.
What follows in this part is those four tools, one at a time, and the powers each one forced into being. Some of the pain was exactly where it was predicted; some of it was somewhere else entirely, including a few bugs that had been hiding in the compiler for a long time and one self-inflicted wound where a fix to the language quietly broke a program written in it. All of it came from the same decision made here: to stop confirming the language was fine, and go looking for where it wasn’t.