ZVM Version 3: works for Inform but not ZIL

I’ve been having trouble with my ZVM interpeter for version 3. After double checking everything and comparing to several other implementations, I still can’t figure out why the parser is having trouble. It works for Inform games (such as the z3 version of Curses) but not for ZIL games, whether Infocom or ZILF (examples: minizork.z3 and advent.z3).

I would greatly appreciate another pair of eyes looking over my code, or if any other interpreter authors have any advice.

Potentially relevant functions:

  • parse_dict(): parses and caches the dictionary
  • read()
  • handle_line_input(): adds the zero terminator and stores the text buffer, then calls tokenise
  • tokenise(): splits the text buffer into words, finds the relevant dictionary entries, and then fills the parse buffer

From logging the results of these functions at various points it seems to me like the text and parse buffers are both being correctly filled. This makes me wonder if perhaps the bug is somewhere else entirely. Does anyone know if the ZIL library might be using some opcode which Inform never does?

Lastly, if you have node, you can install the ifvms package globally, and then run it with the zvm command:

npm install -g ifvms zvm advent.z3

What I have found helpful for debugging other interpreters is using my own ZORKMID interpreter to trace execution and then compare with the other one. (I used this to debug JSZM (another implementation in JavaScript; maybe it will help you too if you compare, I don’t know). Also, when I wrote ZORKMID I did not intend to be used in this way, but I did!)

I don’t know much about the ZIL library nor about Inform library though, so that doesn’t help (although debugging like I mentioned above might help to figure out if this is the case). Another think to try is ZIPTEST if you have it (I have found it useful, but this file seems to be rare).

I have briefly looked at the functions you linked to. I cannot see anything wrong, although I haven’t looked very closely and they may not explain so much in isolation.

While Z-machine with or without Inform extensions are somewhat different, although this shouldn’t affect any of the story files Infocom published, so this seem not to be the problem.

You’re right that ZIL games use some opcodes Inform games generally don’t: inc_chk, dec_chk, and test (a.k.a. IGRTR?, DLESS?, and BTST).

Some observations:

  • The bug isn’t specific to V3. I see the same thing in a V5 build of Advent.
  • It isn’t specific to two-word commands. “inventory” and “take inventory” both result in “I didn’t expect the word ‘inventory’ there.”

With parser tracing turned on in Advent, here’s the first bit of output under Windows Frotz:

> take inventory 2 words: take(V) invent(PV)
The letters in parens are part of speech flags: take is a verb, and invent[ory] is a preposition and a verb.

Under ZVM:

> take inventory 2 words: take(BPDAVO) invent()
Trying other words reveals a pattern: it thinks every word either has all parts of speech (BPDAVO) or none ().

ZILF’s library uses BTST to test those flags. Here’s the assembly code for the routine that prints each word above:


With a patch to print the value of FL, I see output like this:

8 words: take(65:BPDAVO) axe(193:BPDAVO) cross(97:BPDAVO) north(19:BPDAVO) from(8:) fum(72:) gate(128:) giant(34:)

So it’s reading FL out of the dictionary correctly, but BTST seems to only check whether FL is odd or even. Your code for the test opcode looks OK to me, so maybe there’s some JavaScript quirk involving bitwise operations I’m overlooking, or maybe the second operand is always being set to 1 somehow.

This is the code I used (it is public domain, so you can use it if you want to): case 7: // BTST predicate((op0&op1)==op1); break;

What I know about JavaScript bitwise operation is it work only with signed 32-bit numbers; that is the JavaScript “quirk” involving bitwise operation.

In case it is needed, here is the other code (also public domain): case 4: // DLESS? xstore(op0,x=xfetch(op0)-1); predicate(x<op1); break; case 5: // IGRTR? xstore(op0,x=xfetch(op0)+1); predicate(x>op1); break;

Huge thanks to both of you!

I won’t have time to test this until tonight, but I think I’ve found the problem: equality is higher precedence than bitwise AND, which means that my test function is interpreted as “bitmap & (flag === flag)” => “bitmap & 1”. (It’s the same precedence in C and C#, so not a JS quirk.) I’ll have to add a test to Praxix too.

Ah, tricky!

(C# enforces the distinction between int and bool, so this would be a compile-time error there.)