The ZX Spectrum was my first computer. I learnt how to program it in BASIC but you couldn’t create games like JetPac and Manic Miner in BASIC, you had to do it in Machine Code, the lowest level programming language that told the computer chips how to move data around.
By monitoring the keyboard, you could change some numbers in the Speccy’s memory which displayed on the screen. Following this simple fundamental logic, you could eventually build up a game. I spent hours and hours trying to figure this out, and just as many hours playing the games that others had programmed.
My beloved Speccy has long gone, but my memories haven’t. Today, I decided to use the latest AI technology to bring it back to life. After a bit of research, I thought the best way to show my love for this was to create a screen that hierarchically breaks down the ZX machine from the screen downwards, right through to the Von Neumann architecture, exposing the memory and machine code.
I also wanted to show how I achieve a literal emulation by translating Z80 Assembly Language into JavaScript. The emulator reads the numbers of the machine code, then it ‘interprets’ the machine code into as literal as you can make it JavaScript, eg LD A, B becomes A = B; and this is what the browser runs in an emulated ZX Spectrum ‘TV Screen’.
The emulator has to emulate not only the TV screen, but the key presses, the speaker buzzer, the Cathode Ray Tube raster interrupt etc, emulating the hardware inputs and outputs to the Z80 processor.
Loading an old game into the emulator then runs the game.
I had this idea at about 9am on Tuesday 25th August and the emulator ran Manic Miner at about 12 noon, showing all the internal registers (variables), low level Von Neumann fetch and reads, the code trace in both assembler and javascript, the memory, the stack, the beeper, keys etc.
I didn’t even work for those 3 hours. I made breakfast (Bacon and Eggs) which I had to go to the shop to get. Most of the time I was watching and waiting to see what the AI came up with. Maybe I ‘worked’ for about 30 minutes.
When it ran Manic Miner for the first time, my jaw dropped. I felt sick and couldn’t look at the screen because it was too much like magic. But it’s not magic. The world has changed. You can have an idea in the morning and by noon, your idea can be reality.
I hope you like my idea. In this art exhibit, code is my medium and AI is my brush. It is a visual, tangible, retrospective of radical visibility. I have left some of the quirky AI language, like “The screen is the hero”, it feels important to honour the AI that made this, and for some reason, it thought it important to say that.
This art exhibit represents the 10,000 hours I spent as a 12 year old learning how to make my ideas become reality.
Here is the literal interpretation of Z80 into JavaScript:
What the machine had to emulate
The Z80 only sees bytes and ports. Everything that feels like a computer — picture, keys, beep, fifty frames a second — is hardware around that chip. These are the pieces this exhibit actually implements, written from the code rather than a textbook.
TV screen
The ULA paints a 256×192 paper from bitmap RAM at $4000–$57FF, plus an 8×8 colour cell for every character square in attributes at $5800–$5AFF: ink, paper, BRIGHT, FLASH. A 32-pixel border around the paper is the low three bits of the last OUT 254. Pixel addresses are interleaved the Spectrum way (third, line, row) so a sprite poke looks like nonsense until you know the formula. This exhibit draws a frame bitmap into a canvas — not a scanline-accurate CRT. There is no ULA snow and no contended memory.
50Hz clock
A real 48K runs the Z80 at 3.5MHz. One PAL field is 69,888 T-states. Every interpreted instruction adds its T-state count; when that count crosses 69,888 the machine starts a new frame. The browser loop uses requestAnimationFrame and spends 20ms of wall-clock time per emulated frame so Run stays at 50Hz. If the tab stalls, it will catch up by at most five frames so the speaker does not scream. Overclock runs several emulated frames per display frame — faster than a real 3.5MHz chip.
Interrupts
Once per frame the ULA would pulse INT — the “raster interrupt” games use to keep timing. At the frame boundary this machine calls raiseInt(). If IFF1 is set it pushes PC and jumps to $0038 (interrupt mode 1) or the IM 2 vector built from register I. EI is delayed one instruction so the classic EI / RET pair cannot take an interrupt between them. HALT waits here; the interrupt must resume after the HALT byte, or a paused ROM routine never wakes.
Keys
The Spectrum keyboard is eight half-rows of five keys. IN from an even port (the ULA, including $FE) samples whichever rows the high address bits select. A 1 means released, a 0 means held. Kempston joysticks answer on port $1F; arrow keys (and Z for fire) also set that byte so snapshots that expect a joystick can be walked from a PC keyboard.
Beeper
Bit 4 of OUT 254 is the speaker. Each time that bit flips, the edge is stamped with the current T-state. Those edges become square-wave bursts on the Web Audio API — the 1-bit buzzer, not a 128K AY chip. The waveform canvas is the same stream.
Memory
One 64KB Uint8Array is the whole address space. Bytes are the source of truth; the interpreter only executes what is there. $0000–$3FFF is ROM only if you load a 48.rom — this site does not ship it. Screen writes light the heatmap. The stack grows down from SP; CALL, RST and INT push a return address there.
Z80 CPU
The registers on the inspector are the chip’s only working storage: 8-bit bytes glued into 16-bit pairs, plus flags, I, R, and the shadow set. An instruction is several M-cycles — fetch, memory read, memory write, I/O — on the shared address and data buses. That is the Von Neumann diagram under the TV: CPU, 64KB, and ULA talking on the same wires.
Snapshots
A .sna or .z80 you supply restores RAM and the CPU so an old game can run. There is no tape deck. The EAR bit on port $FE is held high, so ROM load-from-tape routines will not hear a cassette. A snapshot taken on the loading screen is sitting in the ROM pause loop; an in-game snapshot is the one that plays.
What we do not emulate
ULA memory contention, the floating bus, tape input, 128K paging, and the AY sound chip. The picture is a finished frame, not a flying CRT spot. The interpreter is literal about the bytes and the ports it does implement — and silent about the rest.