How this was made.

The starting point was a single 200 KB file, disc.ssd: a disk image for the BBC Micro, a British home computer from 1981. This page covers how it became the game on this site. The game was treated as unknown: everything about how it plays was worked out from the code on the disk.

The ground rules. The request was to turn the disk into a game playable in the browser with sound, using the data provided. Any outside knowledge had to be agreed first. Before going further I asked three questions:

So the rules of the game described below were all read out of the program, not recalled.

1Opening the disk

An .ssd file is a byte-for-byte copy of a single-sided floppy in Acorn's DFS format. It holds 80 tracks of 10 sectors of 256 bytes. The first two sectors are the catalogue: file names in one sector, and each file's load address, run address, length and start sector in the other. Decoding it gave three files:

FileLoads atRuns atBytesWhat it turned out to be
!BOOT––48Startup commands: switch to BASIC, set memory, run CHUCKIE
CHUCKIE&1900–3,536A BBC BASIC program: the loader
CH_EGG&1100&29AB9,9846502 machine code: the game

(The BBC Micro writes hexadecimal with an & prefix; this page does too.)

2The BASIC loader

BBC BASIC stores programs "tokenised": keywords such as PRINT are saved as single bytes. Turning those bytes back into keywords gave a readable listing. This was the only place the game's identity appeared:

REM C H U C K I E   E G G   by  Doug Anderson
REM Copyright (C) A & F Software 1983

The loader draws an animated logo, defines sound envelopes, and shows a Teletext instructions page. It explains that 1 to 4 players use direction and jump keys to "collect a dozen eggs", and that the keys can be redefined. Then it runs the real program with *RUN "CH_EGG". It also writes six bytes into memory at &3BF–&3CA, an area normally used for cassette loading. Nothing in CH_EGG refers to them.

3Taking the machine code apart

CH_EGG is raw 6502 machine code: no symbols or comments, with data mixed in among the instructions. A disassembler that just reads straight through would decode data as nonsense instructions. Instead, mine started from the run address and followed every jump, call and branch, marking which bytes are really code. Everything else is data.

The first routine it reached explained the memory layout. The program copies its top 2 KB (&3000–&37FF) down to &0900, then switches the screen to MODE 2. That mode shows 160×256 pixels in 16 colours and uses &3000 upwards as screen memory, so drawing the first frame destroys the original copy. With that relocated block included, the disassembly came to about 3,050 instructions in 66 subroutines.

The most useful discovery was what the program does not do. It never touches the hardware directly. It goes through the operating system for a handful of things:

OS callUsed for
OSWRCHText, palette, clearing the screen and positioning (the "VDU" codes)
OSBYTE &81Is a particular key held down?
OSBYTE &79 / &7AWhich key is pressed? (for redefining keys)
OSWORD 7 / 8SOUND and ENVELOPE
OSWORD 3 / 4Read and reset the 100 Hz interval timer (frame pacing)
OSWORD 0Read a line of text (high-score names)

All graphics during play are written straight into screen memory by the game's own code. So with these few calls understood, the whole machine was accounted for.

4A throwaway emulator, used as a microscope

Reading 3,000 instructions cold is slow and error-prone, so I built a tool: a 6502 processor emulator, plus stand-ins for exactly the OS calls above. It runs the original CH_EGG unmodified and saves its screen as a picture. It isn't part of the game; its jobs were to show what the code does and, later, to check the rewrite.

The first run showed a high-score table and a "Press S to start" prompt. Simulating S, then 1 for one player, showed the first level: platforms, ladders, eggs, birds and a large caged bird in the corner. Now each routine could be matched to something visible.

The emulator also answered a timing question. Each frame of play ends by waiting until the timer shows at least 3 centiseconds since the last frame, so the game runs at most 33.3 frames a second. If a real BBC Micro sometimes took longer than that, the game would slow down there. Counting the processor cycles of every instruction (the machine runs at 2 MHz) settled it:

60,000cycles available per 3 cs frame
~18,000cycles a typical frame needs
0.164 sone unit of the game's busy-wait delay loop

Play therefore ran at a steady 3 cs per frame, and a fixed 30 ms tick reproduces it exactly. Pauses outside play are busy-wait loops whose length depends on processor speed, so their real durations were measured the same way.

5Reading the code

Graphics

A table at &1100 lists 54 sprites as width, height and data address. Each sprite is 1 bit per pixel. The drawing routine doesn't paint pixels; it flips them with exclusive-OR. Drawing a sprite twice in the same place therefore erases it, so moving an object is just "draw at old position, draw at new position".

All 54 sprites from the disc: platform, ladder, egg, grain, lift, the player's walking and climbing frames, a large bird, a cage open and closed, small birds walking and pecking, digits 0 to 9, status labels and large title letters.
All 54 sprites, decoded from the disk (colours here are just to tell them apart). From top left: platform, ladder, egg, a small pile of grain, the lift, nine player frames, four big-bird frames, two cages, ten small-bird frames, digits, status labels and the letters of the title.

Each kind of object flips a different bit of the 4-bit colour value: platforms, ladders, eggs, the player and birds each have their own. The palette, set by 16 VDU 19 commands at start-up, then maps the 16 combinations to on-screen colours. Any combination that includes the player's bit shows yellow, and any with a bird's bit (but not the player's) shows cyan. Overlapping objects therefore still look right, with no masking and no redrawing of what's behind.

Levels

A table at &0CC0 points to 8 level layouts. Each starts with five counts: platforms, ladders, whether there's a lift, piles of grain, and birds. Then come lists of coordinates on a 20×25 grid of 8×8-pixel cells:

header     platforms, ladders, lift?, grain, birds
platforms  row, first column, last column     (repeated)
ladders    column, first row, last row        (repeated)
lift       column                             (if present)
eggs       column, row                        (always 12)
grain      column, row                        (repeated)
birds      column, row                        (always 5 starting places)

Parsing all eight this way ended each one exactly where the next began. Strong evidence the format was right.

The eight level layouts side by side, each with green platforms, magenta ladders, yellow eggs, cyan birds, and the caged big bird top left; four of them have lift platforms.
The eight layouts, drawn by the finished JavaScript version at the start of play.

As a level is drawn, the game also builds a 20×25 map in memory at &0600. Each cell's low bits say "platform", "ladder", "egg" or "grain". For eggs and grain, the top four bits record which one, so collecting it can be remembered per player. Every collision test in the game is a lookup in this map.

The rules, as the code tells them

The rest was a matter of tracing the main loop and naming every variable. Each frame it reads the keys, moves the player, plays footsteps, moves the lift, moves birds or ticks the clock, checks for an extra life, then checks for collisions. The rules came out of that code:

6Writing the JavaScript version

The rewrite follows the original routine by routine. Comments carry the original addresses (e.g. // &2276) so every function can be checked against the disassembly. Three choices kept it faithful:

FileRole
src/game.jsAll the game logic
src/screen.js160×256 screen of colour numbers, EOR sprite drawing, and the text features the game uses
src/sound.jsThe OS sound system and a sound-chip synthesiser
src/scheduler.jsThe 100 Hz clock that drives the game and the sound
src/input.js, main.jsKeyboard, canvas and start-up
src/data.jsGenerated from the disk

Sound

The game makes sound only through the OS SOUND and ENVELOPE commands. So the rewrite reimplements that part of the OS and issues exactly the same commands as the original:

The egg and grain sounds use the noise channel, footsteps and jumps use short tones that rise and fall with the jump, and losing a life plays a 16-note tune.

7Proving it matches

The emulator from step 4 became the test. tests/compare.test.js runs the original machine code and the JavaScript version side by side and gives both the same key presses, frame by frame. The key presses are random but plausible: walk for a while, jump, climb. After every frame it compares:

Runs start on each of the 8 layouts and on later, harder rounds. Some deliberately end a level every 150 frames, to exercise the bonus count, the switch to the next level and extra lives. Together they reached every player state: walking, climbing, jumping, falling and riding the lift.

Level 1 as drawn by the original 6502 program running in the emulator.
The original 6502 code, emulated, at frame 300 of level 1.
Level 1 as drawn by the JavaScript version at the same frame; identical to the original.
The JavaScript version at the same frame. 0 of 40,960 pixels differ.

Every run matches on every frame, including through deaths, game over and up to level 38. A few slips in translation were caught by checking the JavaScript against the disassembly before the test existed. One was a branch that checks whether a number is negative, used the wrong way round in the wall check. The test's own early failures were all in the test itself, mostly about at exactly which point in a frame to compare the two.

8Where it isn't the original

9Added since