A Specification-Accurate Z-Machine Implementation
Early and Late Infocom + Modern Inform
An interpreter for the Z-Machine, written in Python, with Glulx to follow.
The Z-Machine is the virtual machine Infocom designed in 1979 to run its text adventures, and which the interactive fiction community has used ever since. Voxam reads a compiled story file and executes it, with two guiding commitments: fidelity to the Z-Machine Standard -- every rule the interpreter enforces cites the section it came from -- and reproducibility, so that a recorded play session replays identically, forever.
Voxam is developed against real games. The Zork trilogy, Cutthroats, Deadline, Seastalker, Trinity, A Mind Forever Voyaging, The Hitchhiker's Guide to the Galaxy, and -- filed in triplicate, blood pressure rising -- Bureaucracy have all been played to winning conclusions under Voxam, several across multiple releases and several to perfect scores, alongside modern classics from Colossal Cave to the IF Comp winner All Roads. Twenty-one complete recordings verify those endings end-to-end with the acceptance harness described below, and their annotations double as an archaeology of where the games' published walkthroughs go wrong.
Status
Version 0.x: early, honest, and playable.
Works today: story file versions 1 through 5 -- Infocom's whole main-line catalog and the modern Inform and PunyInform games built on version 5. That includes the full parser and object machinery, a seeded random number generator for reproducible sessions, the screen model (split windows, single-keystroke input), custom alphabet tables, the accented extra characters, and the Standard 1.1 Unicode extras -- custom translation tables and print_unicode -- so the interpreter declares revision 1.1 in every header it touches. SAVE, RESTORE, and RESTART speak the standard Quetzal format, auxiliary files cover the games that save fragments of themselves, UNDO is multi-level, and an acceptance-script harness records, replays, and probes whole playthroughs.
At a real terminal, Voxam paints the screen: the blessed frontend (an optional extra, named for both its temperament and the blessed package behind it) renders the §8 screen model live -- a reverse-video status line that holds the top of the screen, split windows, character-input menus like Zork's InvisiClues browsed by single keypresses, bold, italic, and the §8.3.1 colours. Timed input runs on the real wall clock there, so a game like Z-Tornado plays in genuine real time. The architecture keeps a strict split between a pure screen model -- a grid of attributed cells held to §8 by golden-grid tests -- and a thin painter that only repaints what changed, so the screen is as testable as the machine beneath it.
Input runs deeper than lines. A scripted line reaches single-keystroke reads one character at a time, which is how cursor-driven forms -- up to and including Bureaucracy's Software Licence Application -- fill in correctly from a recording. In recorded sessions timed input runs on a virtual clock instead: the "patient typist" lets one interrupt interval elapse before each input arrives, which keeps timed games replayable while recordings stay deterministic. And sampled sounds pass in the conforming silence of an interpreter that declares none -- The Lurking Horror and Sherlock play on, exactly as they were shipped to -- while the two built-in bleeps remain the machine's whole orchestra.
Voxam is verified against the community's interpreter test suites: CZECH (versions 3, 4, and 5), Praxix -- its Standard 1.1 section included -- TerpEtude, and Strict Z Test all pass clean, and every remaining gap halts loudly with a citation instead of guessing.
Not yet: sound playback (the Blorb era), font 3's character graphics (the road to Beyond Zork, which gets a release of its own), version 6, and Glulx. For recorded sessions, seeds substitute for saves: a script replays a whole game in moments.
Installation
Voxam requires Python 3.12 or later.
pip install voxam
or, as an isolated tool:
pipx install voxam # or: uv tool install voxam
The painted screen frontend rides in the screen extra:
pip install "voxam[screen]" # or: uv tool install "voxam[screen]"
Without the extra, Voxam plays as a plain text stream -- every game still works; the status line simply stays imaginary.
Voxam ships no story files. Bring your own: the IF Archive hosts thousands of freely available games, and story files you own from commercial collections work as-is.
Playing stories
Point Voxam at a story file and play at the terminal:
voxam path/to/story.z3
At a terminal with the screen extra installed, the painted
frontend takes over automatically -- status line, windows, menus,
real-time input. Pass --plain to keep the classic stream
instead; pipes and scripted replays always use the stream, which
is what keeps recordings deterministic.
Add --seed to make the dice reproducible: the same seed and the
same commands produce the same session, every time.
voxam --seed 1137 path/to/story.z3
Typing save in a game writes a Quetzal file beside the story --
zork1.z3 saves to zork1.sav -- and restore reads it back.
Quetzal is the standard interchange format, so saves travel between
Voxam and other interpreters.
Acceptance scripts
A play session can be saved as an acceptance script and replayed:
voxam --accept some-session.accept
An acceptance script is a plain text file of typed commands plus a few directives:
! SEED=99
! GAME=path/to/story.z1
# Comments annotate the session; blank lines are ignored.
x me. x mailbox # inline comments start at whitespace + #
> open mailbox # the > prefix is optional transcript style
The rules, line by line:
! KEY=VALUEis a directive:GAMEnames the story file to run, andSEEDfixes the dice (a--seedargument overrides it). A relativeGAMEpath counts from the script's own directory, and forward slashes work on every platform.#at the start of a line is a comment.- An inline comment begins at whitespace followed by
#. - A leading
>is optional and stripped; it also escapes the rare command that genuinely begins with#or!. A>alone types an empty line. - A line starting with three backticks is a fence: everything until the next fence is skipped entirely, directives included. Text after the backticks labels the fence, and an unclosed fence skips the rest of the file -- handy while working out a section that a seed change will invalidate, or to replay only the start of a longer script.
- Anything else is typed into the game exactly as written.
- When the commands run out, the session ends as if the player had reached end of input.
While recording a longer session, --replay types the script and
then leaves you at the prompt instead of ending, so a
work-in-progress script catches you up to where you left off:
voxam --replay some-session.accept
Refusal warnings
During a replay, Voxam listens for the parser's refusal dialect -- responses like "You can't see any statuette here!" or "You should close it first" that mean a recorded command did not do what it said. Each one is reported with the script line that drew it:
voxam: line 31: 'lock door' looks refused: You should close it first.
Refusals scroll past a human reader without registering, and the missing side effect may not surface until dozens of turns later. The warning points at the moment it happened, which turns the most common recording bug from an archaeology expedition into a one-line fix.
Probing a recording
When a recording goes wrong -- a death that survives every retry, a
walkthrough command the game will not speak -- the fix is empirical,
and voxam.probe is the instrument. A seeded script is a
deterministic timeline, so a probe can replay the recorded prefix
exactly and then ask "what would happen if...?", as many times as it
takes:
from voxam.probe import Probe
probe = Probe.load("acceptance/advent.accept")
run = probe.attempt(["ne", "give eggs to troll", "ne"], drop_last=2)
for step in run.steps:
line = step.response.strip().splitlines()[0] if step.response.strip() else ""
flag = f" <<< {step.refusal}" if step.refusal else ""
print(f"[{step.command}] {line}{flag}")
Each step pairs a typed command with everything the story said
back, and flags any response spoken in the refusal dialect --
which turns a hundred-command stretch into a one-screen diagnosis.
attempt replays the script and tries a variant tail
(drop_last re-tries the ending without editing the file);
run takes the whole command list for surgery the prefix cannot
express, such as inserting turns mid-timeline; and the returned
machine is left standing for post-mortem reads of globals,
object parents, or the clock. Every run boots fresh from the seed,
so no experiment can contaminate another.
Probe scripts themselves are throwaways -- write one in a scratch
file, find the answer, record the fix in the .accept annotations,
and delete it. The harness is the part worth keeping.
Development
Working on Voxam itself needs uv for dependency and environment management:
git clone https://github.com/jeffnyman/voxam.git
cd voxam
uv sync --all-groups
All commands below assume that environment.
| Task | Command |
|---|---|
| Run the test suite | uv run pytest |
| Run tests without coverage | uv run pytest --no-cov |
| Lint | uv run ruff check . |
| Lint and autofix | uv run ruff check --fix . |
| Format | uv run ruff format . |
| Check formatting only | uv run ruff format --check . |
| Type check | uv run mypy |
| Build distributions | uv build |
Project conventions
- Layout. Source lives under
src/voxam, tests undertests/. Thesrclayout ensures tests exercise the installed package rather than the working directory. - Typing.
mypyruns in strict mode over bothsrcandtests, and the package ships apy.typedmarker so downstream consumers get its types. - Coverage. The suite is gated at 100% branch coverage. This is deliberate
for a project of this size; adjust
fail_underinpyproject.tomlif it stops being useful. - Spec citations. The
§references in code, docstrings, and output follow the HTML rendering of the Z-Machine Standard 1.1 vendored atentharion/z-machine-standard/. Other renderings of the same Standard, including the PDF beside it, number some paragraphs differently. - Line endings. LF everywhere except Windows script files, enforced by both
.gitattributesand.editorconfig. - Recordings. Complete playthroughs live under
acceptance/in the repository (they are not part of the installed package). They reference games under the optionalentharionsubmodule, so they replay locally rather than in CI -- and they double as the project's archaeology notebook, annotating where the games' published walkthroughs go wrong.
Pre-commit hooks
Install the hooks once, after which lint, format, and type checks run on every commit, and commit messages are validated:
uv run pre-commit install
Every hook is a repo: local entry that runs its tool out of the project
environment via uv run, so pre-commit never clones hook repositories or
builds cached environments under ~/.cache/pre-commit. Tool versions have a
single source of truth: uv.lock.
To run every hook against the whole tree:
uv run pre-commit run --all-files
Commit messages
Commit messages follow Conventional Commits,
enforced at commit time by commitizen
through the commit-msg hook installed above:
feat: add object table parsing
fix(memory): reject story files shorter than the header
docs: explain the save file format
To check a message by hand, or to compose one interactively:
uv run cz check -m "feat: add object table parsing"
uv run cz commit
Because the history is machine-readable, commitizen derives the next version, tags it, and updates the changelog:
uv run cz bump
Reference Material (optional)
An entharion submodule holds the specifications and story files this
project is developed against. These are not required as part of building
and deploying Voxam, but they help during development. Voxam does not
depend on anything under entharion/. It is not needed to install the
project and CI does not fetch it. Git leaves submodules empty unless
asked, so a plain clone simply skips it.
If you want this reference material and if this is your first time checking out the repo, run this command:
git submodule update --init --recursive
That will fetch the primary repository as well as its submodules:
The latter is my own recomposed version of the Z-Machine Standard document, made a little easier for me to read and consume. You can see this deployed here:
To discard it again, freeing the disk space without affecting the project:
git submodule deinit --all
Dependabot tracks the pinned commit and opens a PR when upstream moves. To move the pin by hand instead:
git submodule update --remote entharion
git add entharion
git commit -m "chore(deps): update entharion submodule"
🪄 The Name
The name VΘXΔM draws from two sources of inspiration:
- From Latin, vox means "voice," evoking the idea of turning a player's command into action, like voice into magic.
- In Zork: Grand Inquisitor, voxam was a spell meaning "to separate the energies of different magics." That maps well to the process of parsing, breaking down a command into meaningful parts, isolating intent from raw text.
So whether seen as linguistic alchemy or parser sorcery, VΘXΔM stands at the intersection of command and consequence; of input and invocation.
In terms of a few more historical details, the VOXAM spell has a hilarious relevance in Zork: Grand Inquisitor: it's a complete joke and serves absolutely no functional purpose in the main game. When you first receive your spellbook from Y'Gael at the bottom of the well, VOXAM is one of the three starting spells written inside (alongside REZROV and IGRAM). According to the in-game lore, it belongs to the class of High Magic and, as stated earlier, is defined as a spell to "separate the energies of different magics."
Its actual relevance breaks down into two categories:
- In the Main Game: Pure Flavor & Trolling. While you use REZROV to open the very first locked door and IGRAM to turn purple things invisible later on, VOXAM can't be cast successfully on anything.
- The Developer Joke: The developers included it purely as world-building flavor to pad out your initial spellbook and to trick players into trying it on various magical anomalies throughout the Great Underground Empire.
Also worth mentioning is the "Booznik" System. Later in the game, you discover that the Grand Inquisitor has "Boozniked" (reversed) all magic. If you were theoretically able to reverse VOXAM, it would mean "conjoin the energies of different magics," but the spell remains entirely useless to your inventory.
So: a spell defined as separating the energies of different magics, that generations of players cast hopefully at every anomaly in the Great Underground Empire, and that never once worked on anything -- until now. Point this one at a story file and it separates raw Z-code into opcodes, operands, and intent, exactly as advertised.
Twenty-nine years later, the spell finally works on something.
Chris McDonald, in Techno History, wrote:
"Humans are ceaseless borrowers and copiers. Perhaps, contra Ecclesiastes, there is an occasional new thing under the sun, but certainly humans think no new thoughts ex nihilo. And yet we are also ceaseless inventors. We combine existing ideas in new ways or place them in new surroundings, and suddenly the old becomes new, in a wonderful alchemy of the mind."
A borrowed machine, a borrowed spell, a borrowed voice -- combined in new surroundings until the old became new. VΘXΔM is that alchemy, practiced on Z-code.
👨💻 Author
Made with 🤍 by Jeff Nyman
☦️ Doxazein (δοξάζειν)
חֶסֶד וֶאֱמֶת אַל־יַעַזְבֻךָ קָשְׁרֵם עַל־גַּרְגְּרֹתֶיךָ כָּתְבֵם עַל־לוּחַ לִבֶּךָ
"Let not mercy and truth forsake thee:
bind them about thy neck;
write them upon the table of thine heart."
Proverbs 3:3
🕹️ Acknowledgements
This project stands on the shoulders of the team at Infocom, the MIT-born company that invented the Z-Machine to let Zork, and everything that followed, run unmodified across nearly every computer of its era. Particular thanks go to Marc Blank and Joel Berez, who designed the Z-Machine's virtual architecture, and to Tim Anderson, Bruce Daniels, and Dave Lebling, whose work on Zork at MIT gave the format a reason to exist. Thanks also to Graham Nelson, whose Inform language and Z-Machine Standards Document kept the format alive and well-documented long after Infocom itself was gone, making implementations like this one possible.
⚖️ License
The code used in this project is licensed under the MIT license.
Note: This license applies only to the code in this repository. The original Z-Machine concept, design, and any original assets belong to their respective copyright holders.
✨ Long live the classics.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voxam-0.5.0.tar.gz.
File metadata
- Download URL: voxam-0.5.0.tar.gz
- Upload date:
- Size: 98.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c292c48c80805f28f819a0c4dba69cad8ce1805898497ac06198f92b1c10db7
|
|
| MD5 |
c86da0cb1f3a4a64a2e3699239db83cd
|
|
| BLAKE2b-256 |
21240fa647ba4b34278db0a585ee37c43821a1cac7a528788f8692b87fb68d86
|
Provenance
The following attestation bundles were made for voxam-0.5.0.tar.gz:
Publisher:
release.yml on jeffnyman/voxam
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voxam-0.5.0.tar.gz -
Subject digest:
5c292c48c80805f28f819a0c4dba69cad8ce1805898497ac06198f92b1c10db7 - Sigstore transparency entry: 2462243377
- Sigstore integration time:
-
Permalink:
jeffnyman/voxam@811b3a8ec3ca7da0b5d8cb91a5582515ccf3db78 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/jeffnyman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@811b3a8ec3ca7da0b5d8cb91a5582515ccf3db78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file voxam-0.5.0-py3-none-any.whl.
File metadata
- Download URL: voxam-0.5.0-py3-none-any.whl
- Upload date:
- Size: 102.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41c2e74fb488db50e00cc5e62220ed5df78589b8285d567d1985cafa594e24f4
|
|
| MD5 |
8f8a29d044cedd056df2a12ca7aa53fc
|
|
| BLAKE2b-256 |
18b214a1fda8f10a6aba7fd0935506b6947a4e55af6e3a823741ff2620aba9f5
|
Provenance
The following attestation bundles were made for voxam-0.5.0-py3-none-any.whl:
Publisher:
release.yml on jeffnyman/voxam
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voxam-0.5.0-py3-none-any.whl -
Subject digest:
41c2e74fb488db50e00cc5e62220ed5df78589b8285d567d1985cafa594e24f4 - Sigstore transparency entry: 2462243740
- Sigstore integration time:
-
Permalink:
jeffnyman/voxam@811b3a8ec3ca7da0b5d8cb91a5582515ccf3db78 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/jeffnyman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@811b3a8ec3ca7da0b5d8cb91a5582515ccf3db78 -
Trigger Event:
push
-
Statement type: