Z-machine
The Z-machine is the portable virtual computer Infocom designed in 1979 so one story file could run on dozens of incompatible home machines. This build is a from-scratch JavaScript interpreter for version 3, the format of the classic text adventures: it decodes the object tree, dictionary and ZSCII text, renders the story and the version-3 status line into a canvas terminal, and takes typed commands through the read opcode. It boots a small public-domain game by default, and the shared emulators.org debugger single-steps the machine one Z-code opcode at a time with breakpoints and watchpoints.
Runs on: Web browser
Z-machine Online Emulator
Play Z-machine using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Tiny Cave | Z-machine | Z-machine | open | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
The interpreter is authored from scratch in plain JavaScript, so every Z-code opcode runs in inspectable code with no wasm heap. It targets version 3 of the Z-machine, the format the classic Infocom adventures shipped in, as defined by the Z-Machine Standards Document: the header, the dynamic / static / high memory split, the stack with call frames and locals, ZSCII text (the A0 / A1 / A2 alphabets and the abbreviation tables), the object table with attributes and properties, the dictionary, and the version-3 status line.
The step primitive. The core exposes zm.step(), which decodes and executes exactly one Z-code instruction and returns the opcode byte (0 after quit, -1 while blocked waiting for a line of input). A Z-machine does not free-run to a frame boundary the way a console does; it runs until it needs a line of input at a read opcode, then blocks. Rather than read input synchronously (impossible in a pausable loop), read parks itself: it sets needInput and yields, and the host stops stepping until the terminal submits a line.
function run(){
for(;;){
if(zm.finished) return;
if(zm.needInput && zm.pendingInput === null) return; // blocked on a command line
if(breakpoints.has(zm.pc)){ pause(); return; }
zm.step(); // exactly one opcode
}
}
| Member | Kind | What it does |
|---|---|---|
zm.step() | method | Execute exactly one Z-code opcode; returns the opcode byte, 0 after quit, -1 when blocked on input. The single-step primitive. |
zm.pc | field | The program counter, a byte address into the story image. Read and settable. |
zm.mem | field | The whole story image, a plain Uint8Array (dynamic + static + high memory). |
zm.stack / zm.frames / zm.locals | field | The evaluation stack, the call-frame list, and the current routine's locals. |
zm.submitInput(line) | method | Hand a finished command line to a parked read; the next step() tokenises it against the dictionary. |
zm.onWrite(addr) | hook | Fires on every story-memory write, used by the debugger for watchpoints. |
Debugger integration
The plug-in z-machine-debug.js reads window.EMU_BOOT and hands the debugger a transport, the Z-machine register set and the story memory. Every debugger feature is a host-side check around step(), with no change to the interpreter's semantics.
- Step one opcode. The Step control calls
step()once, advancing the story by a single Z-code instruction. - Breakpoints. The loop compares
zm.pcagainst aSetof byte addresses before each opcode and pauses on a match. - Watchpoints. The core's
onWritehook flags a write to a watched address; the loop pauses as soon as the opcode that wrote it returns. - Registers. A Z-machine has no A / X / Y file. The debugger shows the program counter, the stack depth (SP) and top-of-stack, the call depth, the current routine's locals, and the three globals the version-3 status line reads: the current room object, the score and the move count — all live, most settable.
- Disassembly. The
zcodedecoder (zcode-cpu.js) reproduces the interpreter's instruction decode — long / short / variable forms, the store byte, the branch offset and inline encoded text — so the disassembly view walks Z-code correctly and lengths line up. - Memory. The story image is one chip, read side-effect-free, viewable as hex or as
zcodedisassembly.
Architecture
The Z-machine is a virtual computer Infocom designed in 1979 so one story file could run on dozens of incompatible home machines. A story is a single image split into three regions: dynamic memory (the header, globals, object tree and anything the game writes), static memory (read-only tables such as the dictionary), and high memory (packed strings and routine code, reachable only through packed addresses).
- CPU. A little-endian, big-endian-in-memory machine with a value stack, call frames each holding up to 15 local variables, and 240 global variables. Instructions come in four forms (2OP, 1OP, 0OP and a variable-operand form) selected by the top bits of the opcode byte; operands are large constants, small constants or variable references.
- Text. Strings are packed three 5-bit Z-characters to a 16-bit word, shifting between three alphabets and expanding abbreviations from a table — the encoding this interpreter decodes for every printed line and the dictionary.
- Objects. A tree of objects, each with 32 attribute flags and a numbered property list; the parser and the world model are built entirely out of it.
- Input. The
readopcode reads a line, lower-cases it, stores it in a text buffer and tokenises it against the dictionary into a parse buffer — the whole of a version-3 game's command handling.
Version 3 adds the one-line status bar drawn automatically before each command, showing the current room and the score and move count from three global variables.