Hello — I’ve been playing interactive fiction since the 1980s; Zork and the Infocom
catalogue are a good part of why I ended up doing this for a living. This is a hobby
project that turned into something I thought this community in particular might enjoy
(and pick apart).
For a while I’ve been building a small pure-functional language called PFCL. I wanted a
proof of concept beyond the usual toy examples — something with real stateful logic but a
simple I/O surface — and the Z-Machine V3 was the obvious choice: a interpreter that
only needs line-based stdin/stdout.
Writing a whole interpreter directly in the functional language was heavy going, so I built
a tiny “intent” language on top: you describe what each function computes — typed inputs,
an output, a few clause forms — and it lowers deterministically to the functional code. Then
I transcribed the Z-Machine Standards Document v1.1 into that language, block by block —
about 228 functions covering the memory model, object tree, ZSCII text, the dictionary, and
save/restore. One function looks like this:
zmachine.op.add
GIVEN state: ZMachineState, operands: List<ZWord>
OUTPUT ZMachineState
WHERE a = list.get(0, operands)
WHERE b = list.get(1, operands)
RETURN zmachine.op.store_result(state, zword.add(a, b))
The nice surprise: the spec turned out to be target-neutral, so the same source generates
not just the functional interpreter but a single-file C one and a single-file Java one too.
All three pass czech (349/349), and the C and Java builds produce transcripts byte-for-byte
identical to dfrotz on Zork I.
Correction, edited in An earlier version of this post claimed Zork I relies on reading locals beyond a routine’s declared count. That’s wrong — thanks to zarf, Mike_G and fredrik in the replies for the catch. What actually happened is that I reached for Frotz while tracking down a bug in my earlier PFCL implementation, and mistook my own bug for a quirk of the game. For the record, the correct behaviour — which the spec implements — is that locals initialise to their header defaults per §6.4.4 and surplus arguments are discarded, exactly as Standard 1.1 specifies; an out-of-range read returns 0 in my interpreter (Frotz hands back frame residue on that path) and nothing in Zork ever reaches it. So the spec was right; only my description here was wrong.
It’s V3 only, and a proof-of-concept terminal interpreter — no Glk, no frills — but it runs
Zork I end to end and passes the V3 conformance suite, all generated from one spec.
What I’d actually like to put up for discussion is the artifact itself: a formal Z-Machine
spec written in Markdown — ordinary prose, citations, and [STD §x] references sitting right
next to machine-checkable function blocks. The Standard is prose, beautifully so, but prose
gets interpreted, which is half of why we have the compatibility quirks we all know. A
formal-but-readable companion buys two things:
- One source, many targets. This spec produced the PFCL, C, and Java interpreters; in
principle another platform is just one more lowering, not a from-scratch reimplementation
that has to re-derive every ZIP quirk. - It could be a conformance oracle for other interpreters — including hand-written ones.
This is the part I’d most like opinions on. You already check interpreters with czech/praxix
and by diffing against dfrotz; a formal, executable spec might add a finer instrument.
Because every opcode is pinned down formally, you can derive per-opcode conformance
vectors from it — a given input state must yield a given output state, checkable opcode by
opcode — rather than only a whole-game pass/fail, or a transcript diff that tells you
something broke but not which opcode. The interpreters here are really just the first
clients of that reference (and how I sanity-checked the spec: czech 349/349 on each,
byte-identical to dfrotz on Zork I). The open question for this group is whether a
per-opcode formal oracle is genuinely useful to someone bringing up a new interpreter, or
whether the existing suites already cover it.
I’m not suggesting this replace the Standard — the prose is the thing, and czech/praxix
already do real work. I’d genuinely like this group’s read on whether a formal, executable
companion is useful as a conformance reference for interpreter authors, or whether it’s been
tried and I’ve reinvented a wheel. And if I’ve mischaracterised the out-of-range-locals
business above, I’d like to be set straight by people who know it far better than I do.
Thanks for reading — and for keeping this whole world alive.