ToonTalk is a programming language disguised as a video game. Children fly a helicopter over a city, land, walk into a house, and sit down at a desk. There they train robots by showing them what to do, once, with their hands. Numbers are pads you drop on other pads. Messages travel by bird to a nest. A truck builds a new house that runs a new process. A bomb blows the house up and stops it.
None of that is metaphor laid over code. In ToonTalk the picture is the program, and the animation of a bird flying to its nest is the running of the thing.
The program was written in C++ for Windows, and its source carries dated comments running from the late 1990s to 2005. It talks to the machine through the interfaces of that era: DirectDraw for the screen, DirectSound for audio, DirectInput for the mouse, Windows GDI for text, Microsoft's XML parser for saved worlds. Every one of those has since been superseded or removed. A program can be perfectly correct and still become unrunnable, simply because everything it stands on has been taken away.
The port puts the floor back.
The approach
Not a rewrite
The tempting thing would be to rebuild ToonTalk from scratch in modern web technology. That gets you something that resembles ToonTalk. It does not get you ToonTalk β because twenty years of decisions, corrections and second thoughts live in that code, most of them undocumented and many of them load-bearing. The comments are full of them: rewritten on 100603, condition new on 290404 since no need to emulate the mouse if not in full-screen mode. Nobody could reconstruct that from a description.
So the C++ was kept, all of it β roughly 210,000 lines β and compiled to WebAssembly, the format that lets a browser run code written in languages like C++. What was replaced is only the floor underneath: hand-written stand-ins that accept the same calls the old Windows libraries did and carry them out using what a browser has. Drawing goes to a canvas. Sound goes to Web Audio. The file system becomes a bundle of files loaded into memory before the program starts.
This gives the work an unusual and rather clarifying rule: the original is the specification. There is no debate about how anything should behave, because the behaviour is sitting right there in the source. Anything the port does differently is, by definition, a bug in the port. That rule settled a great many arguments before they started.
What went wrong
The world upside down
The most memorable failures were the ones where the program was working perfectly and the picture was a lie.
Early on, the helicopter came in to land by descending upward. Parked, it sat at the top of the screen on grass instead of down on the street. Houses moved when they shouldn't. All of it was one disagreement about which end of the screen memory is the top. The old graphics interface and the browser canvas number their rows in opposite directions, and the mismatch mirrored every sprite about the middle of the screen β while leaving the backgrounds, which took a different path through the code, looking entirely correct. That combination is what made it so confusing: half the world was right.
βThe helicopter sounds got stuck on repeat.β
From the test reportsAnother: the helicopter climbed until it left the city and hung over open ocean, with the controls dead. Each rotor animation frame applied a small correction to its position; at high altitude the numbers in that correction grew past the size the arithmetic could hold, wrapped around, and came back with the wrong sign. So every beat of the rotor ratcheted the machine a little further up. The original had a guard against exactly this overflow, but it only checked one direction.
A text pad β the thing you type into β once rendered as a row of question marks and nothing else. The routine that walks along a string of text was stepping two bytes at a time through characters that were one byte wide, so it read the gaps between letters and found nothing it recognised.
And in the Pong example, the ball would leave the screen and never be served again. ToonTalk does exact arithmetic β proper fractions, numbers of unlimited size β using a mathematics library that could not be linked into the new build. Fifty-four of its functions had quietly become stubs that returned zero. Whole numbers still worked, because they take a different path internally, so nothing looked wrong until a fraction appeared. Pong's serve depends on a fraction.
The pattern behind the bugs
Stubs that lie
That last one is worth dwelling on, because it turned out to be the shape of the whole project.
When a program is built out of many pieces, the usual protection is that a missing piece stops the build. You cannot ship something that calls a function nobody wrote. This port deliberately switches that protection off β it has to, because hundreds of small Windows functions are called and only some of them matter. The ones that don't get an automatic stand-in that does nothing and returns zero.
The trouble is that in a great many programming conventions, zero means success, and zero also means no error. So a stand-in that does nothing does not announce itself. It reports, cheerfully and in perfect good faith, that it has done the job.
The clearest case came right at the end. ToonTalk's time-travel controls β the row of buttons that let you rewind a session β were invisible for weeks. Several plausible explanations were chased and fixed along the way, all of them real problems in their own right. The actual cause was that the routine which measures an image file was one of these stand-ins. Asked how big the button artwork was, it answered βno error, zero by zero.β The program believed it, and dutifully laid out, drew, and refreshed seven buttons at one unit across. They had been there the whole time, painted every frame, far too small to see.
Where the work went wrong about itself
Three honest mistakes
Not every failure was in the code. Some were in the diagnosis, and they cost more time than the bugs did.
Believing a number that meant something else
A crash was being chased through a long demo. The test harness printed a count of loop iterations; the engine printed its own frame number. These are not the same thing, and the first runs far ahead of the second. Reading one as the other made it look as though a run had sailed past the crash point and the fix had worked. It had not. The run trapped at exactly the same place as before. The lesson was narrow and useful: when comparing two runs, compare the same measurement.
Reading a still picture as a stopped program
Twice, a paused scene was reported as a frozen program. A ToonTalk world with nothing moving in it looks identical to a ToonTalk world that has stopped dead. The fix was to stop looking and start asking: the program now prints a steady heartbeat, so liveness is something you read rather than something you infer.
The confident wrong diagnosis
The invisible buttons produced a measurement that was completely sound β with the demo frozen, moving the mouse changed not a single pixel on screen β attached to a conclusion that was completely wrong. It was written up, committed, and reported as βthe screen never repaints while paused,β along with a claim that a second unrelated bug shared the same cause. Neither was true. The screen repaints fine; it was skipping because a piece of artwork was missing, so the code responsible bailed out on its first line. Retracting that took one commit. Noticing it required someone asking, in so many words, what exactly was so hard about this.
Where it stands
What works now
ToonTalk runs in a browser tab. Not a demonstration of a fragment of it β the program, with its city, its houses, its robots, its notebooks of examples.
- All ten of the recorded demonstrations that shipped with ToonTalk replay from beginning to end, with their original narration, subtitles and sound effects.
- The artwork is the real artwork, read from an original installation, drawn through the same 256-colour palette the program has always used.
- Sound works β effects and the recorded narration both β routed to the browser's audio engine.
- Exact arithmetic is genuinely exact again, on unlimited-size numbers and fractions.
- Full screen reproduces the original's mouse behaviour, which needs the browser to hand over the pointer entirely.
- Time travel works: the buttons appear when you move the mouse and drift away when you stop, Escape freezes a demo and leaves the controls up, and a second Escape offers to resume, hand you the controls, or leave.
- Marty, the Martian who gives advice, speaks aloud again β through the browser's speech synthesiser, standing in for the one the original used.
- Time travel is complete: sessions record themselves into checkpoints as you play, the tape-recorder buttons jump between them and replay from any point, you can take over from the past, and a whole session can be saved as a demo file and replayed later.
- Fractions display as fractions again β a three over a bar over a two β which turned out to be the best story of the port, told below.
- And Marty can now be given a mind: connect an AI of your choice and ask him anything, out loud if you like. That story is below too.
- You can take your work out of the tab: save what you are holding, or a whole city, to a file on your own disk β and open one again.
- A 2005 research study runs again. Eight activities on the sizes of infinity sit beside a live ToonTalk, with the robots each one needs a click away.
It is public. Anyone with a desktop browser can play at toontalk.github.io/tt-wasm.
One thing was deliberately allowed to differ from the original: subtitles now step aside for the time-travel controls instead of being painted over by them. In the original the two simply overlap, and that was only tolerable because the buttons hide themselves after a few seconds. Marty's voice is not such a divergence β he was synthesised in the original too, so the port swaps one speech synthesiser for another.
Still open
What isn't finished
An honest list, because a restoration is never quite done. Everything on the previous version of this list has since been closed β there is an opening screen now, the button letter fits, the vibration is gone β and the current one reads:
- After jumping to a time-travel checkpoint, the first frame shown can be a stale or half-drawn one until play resumes.
- Once, after time travel ended, a bird drew at many times her proper size; it has not been reproduced since.
- Resuming recording after browsing the past cuts the next checkpoint shorter than it should be β faithful to the original's arithmetic, possibly, which is its own kind of question.
- Subtitles in full screen still look coarser than they should.
- The room interior has small fidelity gaps against the original β floor texture noise, a dark pad.
- In Hilbert's hotel, the little box a bird carries to you draws with its label cut off.
And one boundary worth stating plainly: the eight infinity activities have been checked as far as loading and running their materials goes, but only the first has been solved end to end by a person. Solving the rest means training robots by hand, which is exactly the thing a program cannot fake on your behalf.
The port's own worst bug
The feature that ate its recordings
Time travel produced the one genuine data-loss bug of the project, and it was not in the twenty-year-old code. It was in the port's newest addition: the button that saves your session as a file. Saving is supposed to close the current recording segment and open the next one. But if you had jumped back in time first, βthe next segmentβ was one that already existed β and saving quietly wrote over it. A tester lost the very stretch of play he was trying to keep: the numbers in the console showed a thirteen-kilobyte checkpoint replaced by a five-kilobyte one, restamped with the previous segment's clock.
The fix is one honest condition β only advance the recording when you are standing at its newest point β and the verification was pleasingly brutal: record nine segments, jump back to seven, save, and then check every byte of all nine against what the console reported when they were written. Nothing was touched. But the episode earned a permanent place in the project's notes, because it is the cautionary tale about restorations: the original code had decades to have its mistakes found. The port's additions have had weeks.
An enhancement, kept at arm's length
Marty gets a mind
Marty the Martian has always been the helper β he floats beside you, comments on what you pick up, and answers the help key with canned advice. The port now lets you connect a real language model to him: Claude, OpenAI, Gemini, or the small model built into desktop Chrome, which needs no account at all. You type to him, or talk to him, and he answers in a couple of spoken sentences.
Two decisions shaped it. First, the faithful page stays faithful: the AI lives on a separate page, generated from the same source, so the restoration and the enhancement can never contaminate each other. Second, Marty reads the manual rather than the internet: his knowledge is compiled from the documentation that shipped in the box β two hundred odd pages of it β plus the engine's own help strings, so he speaks about ToonTalk in ToonTalk's own words. And because he lives inside the program, the engine tells him where you are and what you are holding. Ask what's in my pocket? and he knows, because the same code that describes things in his speech balloons describes them to the AI.
βcan you tell what's in my pocket?β β βYou have a truck in your pocket. If you want, I can tell you what a truck does in ToonTalk.β
First live exchange, 11 AugustWhen a model answers wrongly β one insisted a robot must be trained before a truck will accept it, which is not true β the correction goes into a rulings file that outranks everything else Marty reads. The author red-pens the AI the way he red-pens everything.
The best bug
The mistake that was load-bearing
Fractions came out wrong: the number squashed onto the number below it, the dividing bar struck through both. The layout code looked fine. The font code looked fine. Measurement settled it β the engine was drawing 36-pixel digits on lines it had spaced 22 pixels apart.
The cause was twenty years old. When the original asks Windows for a font, it converts its measurements into points, the printer's unit β but the interface it calls actually wants pixels. So for the program's whole life, every font came out three-quarters of the intended size. Nobody ever noticed, because the code measures the text it gets and lays everything out from the measurement. The mistake was invisible β and every margin, every fudge factor, every stacked fraction was quietly tuned around it.
The port's font code, written carefully from the documentation, performed the conversion correctly β and that is what broke the fractions. Being right where the original was wrong is a porting bug. The fix was to reproduce the original's misunderstanding, at which point the self-measuring layout snapped back into the shape it had held since the nineties: numerator, clear bar, denominator, with the original's own margins.
It is the purest example of the project's founding rule. The original is the specification β including its accidents.
A different kind of mistake
The safety net that became the bug
A number pad in ToonTalk grows to fit its number. Raise three-halves to the hundredth power and you get a fraction forty-eight digits over thirty-one, and the pad is supposed to become as wide as the digits need β wider than the screen, if that is what it takes.
It did not. The face stayed pinned while the digits went strange: stretched, crowded, spilling past the edge of the thing that was meant to contain them. Photographs of the original running on Windows settled what should happen, and they disagreed with the port.
The cause was not in the old code. Weeks earlier the port had added a guard β don't let a pad grow wider than the floor β while a containment bug was making wide pads escape their surroundings. The very same change had also fixed the containment bug. The guard was never needed after the day it was written, and it sat there, commented and reasonable-looking, quietly being the divergence.
This is the hardest kind of bug to find in a restoration, because everything about it looks deliberate. It also only reproduced if you built the number the way a person would: take the three-halves out of the notebook, drop a typed 100^ on it. A pad created fresh with the same value looked perfect, because the fault lay in the pad's history β it had sized its font while it still said 3/2. Replaying what the user actually did, rather than the state they ended up in, was what made it visible.
A study, brought back
Children, infinity, and 464 files in an archive
In 2004 and 2005, as part of a European project called WebLabs, children aged nine to thirteen in England and Bulgaria used ToonTalk to build infinite sequences β the natural numbers, the evens, the integers, all the fractions between nought and one β and to argue about whether one infinity can be larger than another. They trained robots called Add 1, Doubler, Merge, Match Maker, and one team that walks Cantor's diagonal. A paper came out of it in 2011.
The worksheets survived in the author's own files β eight activities, in a version tested in Oxford and Sofia, together with the teachers' guide and a city that is Hilbert's hotel. The children's programs survived somewhere less expected: 464 of them are still sitting in the Internet Archive, where a long-dead project website was crawled. They still download. And because a ToonTalk program file is just its own saved description, the port opens them with the same code the original used when you double-clicked one. Seven were tried at random β a Match Maker, a diagonal team, a Bulgarian boy's sequence box β and every one came up on the floor, robots and birds and nests intact, twenty-one years later.
So the activities are back, as a page where the worksheet sits beside a running ToonTalk. Each activity offers the robots it needs, delivered into the world you are already in rather than restarting it. The boxes on the worksheets that were ruled space to write an answer on paper have become prompts to think about instead. And Marty has read the whole activity sequence, so he can play the part the researchers played: what would happen if the robots ran forever?
Sharing
Taking your work with you
ToonTalk could always write what you were holding to a file, or save your whole city. In the browser those files landed in a place with no door to the outside: a private filesystem inside the tab. Now the same two commands hand their bytes to the browser as a download, and a third reads one back. Hold something, press Escape, save it; mail it to someone; they open it and it lands on their floor.
It is a small feature that closes a large gap. The 2011 paper's claim that a computation could be saved and resumed on another machine β that a child's infinite process need not die with the computer it started on β is true again.
Why bother
Software of this kind is unusually easy to lose. It is not a document you can migrate; it is a machine, and it only works while the machinery beneath it is kept alive. When that goes, the thing is gone β not corrupted, not deleted, simply unrunnable, which amounts to the same thing for anyone who wanted to see it.
What makes the browser worth the trouble is that it is the one platform that has consistently declined to break old things. A page that works today has a fair chance of working in fifteen years, which is a claim almost nothing else in computing can make.
So the goal was never to modernise ToonTalk. It was to move it somewhere it can go on being itself, with its own artwork, its own timing, its own twenty-year-old decisions intact β including the ones nobody remembers making.
For anyone curious about the cost: the port took about eighty-seven hours of active work spread over five weeks, in 287 commits, with the author sending 450 messages and reading a great many screenshots. The engineering notes carry the full accounting.