Bringing the '90s screensaver to modern browsers meant recovering the story clock and scene director that made his island feel alive.
In 1992, Dynamix and Sierra On-Line released Johnny Castaway. Your Windows 3.1 PC went idle and Johnny fished, slept, built rafts, and got into trouble on his island. As a young kid, I came across him during a summer holiday trip to my parents' office.
Thirty-four years later, he is hard to visit. Sharing it with my kid means dragging a beige tower out of a closet or faffing about with a virtual machine. That is enough friction that almost nobody bothers, and a far cry from the little moment of joy when Johnny just did his thing on the side.
Now he spends his days in my browser tab.
Rebuilding his island paradise faithfully in JavaScript was only feasible with AI assistance; by hand, it would have been weeks of detailed full-time work. Earlier ports got a long way, and this one builds on them, but as far as I can tell none had been checked tick by tick against the original program (if I've missed one, please tell me).
tl;dr
- Johnny's island combines scripted gags and weighted random choices with an eleven-day story that advances at most one step per calendar day.
- The browser port builds on earlier reverse-engineering work and interprets the original game resources in JavaScript.
- Running the original Windows program under instrumentation provides reference traces, which is what kept AI-suggested interpretations honest. Separate rendering “goldens” catch changes in the port.
Inside a gag, and between gags#
The original files contain artwork, sound, and small programs describing the animations. They use the Dynamix Game Development System (DGDS), the engine Dynamix built for its adventure games. My browser implementation, Bottle DGDS, parses those resources and runs their bytecode instructions in JavaScript.
The scripts operate on two levels:
- ADS (Animation Director Script) files coordinate a gag. They act as the high-level sequencer: starting and stopping scenes, evaluating random branches, and waiting for other actions to finish.
- TTM (Tiny Templated Movie) files perform the animation. They handle per-frame work: loading bitmap sprites, drawing sprite frames, waiting on timer ticks, playing sound samples, and looping.
The “Eight Arm Menace” scene flow shows how they fit together: Johnny gets his pole, casts, reels in a heavy catch, and reaches the octopus gag. Several animation threads run concurrently. Their relative order and timing determine whether the result lands as a coherent joke.
Deciding which gag comes next is a separate problem. The original Windows executable wrapped a scene director around these scripts. It picked an eligible story scene, queued up everyday filler scenes before it, and scheduled Johnny's walks between their starting and ending positions. The browser port rebuilds that scene-selection behaviour outside the script interpreter.
That separation explained why earlier web ports felt incomplete. They could parse and play individual animations, but without the director, ordinary playback never selected them. Decoding a file tells you how to render an animation, but not when Johnny should decide to walk across the beach.
Asking the original what it actually does#
I didn't start from scratch. Alexandre Fontoura's Castaway provided the working JavaScript foundation for decoding resources. Jérémie Guillaume's Johnny Reborn, a C port that already had a working scene scheduler, gave me the first scene catalogue. Hans Milling's C# remake, and ScummVM's DGDS engine supplied further findings and behavioural references. This project owes a great deal to their work.
Still, reverse engineering has an easy trap: an interpretation can look convincing on screen while being wrong underneath. The useful reference is the original 1992 program, running.
To get that, I built capture tooling around Windows 3.1 in a patched DOSBox-X emulator. The harness hooks the 16-bit executable's main script tick function. Each time it runs, roughly every 55 milliseconds on Windows 3.1's timer, the harness records which script threads are live (the “actors”) and their runstates; separate logs capture delay counters and random-number draws. Each capture is then condensed into a timeline.jsonl file. The emulator runs the same game data as the browser port, so a difference can't come from comparing two releases.

Comparing threads rather than pixels was a lesson learned. My first tool compared captured screen frames. It found one real bug, a beach background vanishing behind a stale clock graphic, then lasted less than a day because it was slow and thrown off by palette and capture timing.
Checking the port against the original now comes down to four steps:
- Run a gag in the instrumented emulator several times, so its random branches show up in the captures.
- Distill those logs into reference fingerprints: JSON summaries of which script threads run together, their peak count, and their lifespans.
- Run the browser engine and compare its tick-by-tick timeline against those fingerprints.
- Investigate differences, with visual checks where the traces can't settle the question.
This is how the AI assistance worked in practice. LLMs are good at suggesting what an obscure assembly routine or opcode table might be doing, and just as good at sounding right when they're not. Running the original executable gave those suggestions something to answer to.
Opcode 0x2020 shows both sides. A thought bubble in “John's 1st Message” flashed past in a fraction of a second. Working from the decompiled code, an AI agent proposed a rule for the opcode with high confidence, and two fixes built on it were stopped when the traces flagged regressions. Instrumenting the emulator then turned up a handler the decompiler had missed, which sets a random hold on the frame. That was the fix. An AI hypothesis either matched the emulator trace, or it was wrong.
Other bugs were inherited and looked fine on screen. The code I forked picked random branches with equal odds, so Johnny left a fishing sequence about three times too often; the original weights each outcome. Both versions reached a plausible ending, but the port was taking the wrong path far too frequently. Cross-checking against the decompiled executable caught that one, before the traces existed.
Animation timing had similar traps. How long an animation is allowed to run is separate from how long each frame should pause, and mixing up those clocks changed how actions overlapped even when the drawings were correct. The traces also caught an opcode the inherited code treated as a no-op, which in the original ends the sequence. Ignoring it let a “preen” timer in one gag play four dead frames and run about five times too long. For weeks before that, a filter had papered over the symptoms in nineteen gags, on the theory that the reference captures had started mid-scene. Once the opcode was fixed, the filter had nothing left to do.
A second safety net catches regressions: rendering goldens. These replay fragile sequences with fixed inputs and save compact fingerprints of drawing operations and pixels. A later code change that alters the output fails the check, and the difference gets reviewed. They need the original game data, so they run locally rather than in CI.
The two checks do different jobs. The traces establish what the port should do; the goldens preserve what it does now, including any mistake that made it into the baseline. Neither proves that every pixel, sound, or random path is correct. Even the random numbers match only up to a point: the port can reproduce the original generator and its weighted-choice rule (an opt-in setting; normal playback uses the browser's own random numbers), but not the exact run of choices a live session makes, because that depends on timing. The coverage report records what has been verified and where gaps remain.
The machinery behind a living screensaver#
With the scripts running faithfully, you can see how carefully Dynamix built the thing. Jeff Tunnell, who co-founded Dynamix and helped create Johnny Castaway, summarized the core idea:
“So it generally runs a little loop that is interrupted by random events.”
The variety operates across multiple interlocking clocks. Within a day, the director chooses among eligible everyday scenes and gags using weighted probabilities. Across days, it follows an eleven-day story, and its milestones advance as calendar days pass.

Reaching a day's key scene unlocks the next chapter. When your calendar date changes, the saved state advances one step toward that unlocked day. It avoids skipping ahead if you have been away for weeks, and day eleven wraps back to day one. The browser port keeps this progress in local storage.
Environmental details run on their own schedules. The tide moves through sixteen half-hour phases, an eight-hour cycle with about two hours at low tide; holiday decorations appear on specific calendar dates. The raft's construction progress, the island's position, and day-night lighting shifts all belong to the director's scheduler.
That structure is what kept me digging. Johnny's island is an authored story with random permutations running through it, rather than a stream of sight gags. It is why the island felt alive, and it is a design that still isn't widely understood.
Nostalgia Nerd's retrospective covers the original release and why it caught on with so many of us in the 1990s:
Back on the island#
The browser port offers Classic and Enhanced playback. Classic keeps the original 640×480 scale, static clouds, and the original rolling waves; Enhanced adds responsive scaling, drifting clouds, and a small status display. The original Dynamix and Sierra pixel art, audio cues, and gags remain the center of the experience.
To run it, you supply the original game data. The README links the floppy archive; dropping its ZIP or extracted .ima disk image directly onto the page imports the resources straight into browser storage.
You don't have to wait for the calendar to see everything. Settings (press S) controls the display and can run time of day from the original sequence clock or your local clock. The developer panel (press D) previews holiday themes without touching your system clock, sets or advances the story day, and replays individual scenes. That said, the calendar is worth trusting at least once: visit between October 29 and 31 and the island is decorated for Halloween.
Next, I want to make the runtime leaner and build a way to map Johnny's eleven-day story as it unfolds. Like my recent Mac OS 9 work, this started as an excuse to spend time with vintage software again, and the island held much more engineering than I expected from a screensaver.
