I’m happy with my Z-machine’s support for version 3 of the Infocom Z-machine. It lets me play lots of games - Infidel, Hitchhikers, Wishbringer. This was enough to ship the first version of my perfect client project. Quick plug: you can try it out at Planedrift.app.
But to play later Infocom games and modern IF compiled by Inform, I need to support v5 and even v8 of the machine where things are different. Some of these differences are just extra opcodes or configurable wordlengths but others really affect the gameplay that I want to support.
The biggest snag for me is that in v3, the 0th location in the globals table is a pointer to the current room, possibly just by convention but it’s stable across games. In v5 there is no such pointer, meaning clients can’t really know what room they’re in. Since Planedrift saves when you change a room that’s a problem.
So, what signals can I grab to figure out if the player has moved room?
First attempt. It’s relatively easy to work out when the VM is writing to the status bar, and it usually writes the room name there. I can intercept the print_addr and print_obj opcodes while it’s doing so and check if the object/address corresponds to a room object. But it turns out that it’s tricky and game-specific to work out if an object is a room. And, some games use alternative print routines with bare strings - for example there may be a routine that checks if a room is lit and never prints the room object description. (Looking at you, Sherlock.) So, mostly it works but not always.
Second idea: we can fuzzy match on the name of the room in the status bar, but that fails when rooms have the same name and that happens often, especially in mazes. And almost every game has some kind of maze.
Third idea: when the player moves to a room, the player object gets inserted into the room so we can intercept the insert_obj opcode. Great, but all we’ve really done is gone from needing to know which object is the room to knowing which object is the player - and we don’t know this. This was still not reliable enough.
I chased a lot of other dead ends and finally, I gave up and decided to launch Planedrift with support for v3 Infocom games only. Did I mention you could try it here?
But I wasn’t done, in my head I knew there must be a way to do this.
More opcode spelunking and it turns out that all the v5 Infocom games insert the player into the first room at start up. This seems promising. We don’t know, however, which object is the player nor what objects are rooms. The best we can do is, during start-up, create a set of candidates for the player object.
If we could use this to figure out the global player location we’d be golden, even if the player object itself changes. This happens in some games when you take over a robot or an NPC. One thing we know is that the global referencing the player object is frequently accessed during the game and during the start-up. Adding access frequency counters to the global variables gives us a bit more information. Amazingly, this belt and braces (belt and suspenders for my US readers) approach works.
Now there’s a catch. More modern games compiled with Inform don’t run insert_obj player room at start up - the player is statically placed in the room at compile time. But those games are more reliable in accessing object names during status display, so we can go back to original attempts.
I’ve ended up with a layered heuristic that seems to work well enough -
- If the player is inserted into the room at start-up, we use frequency analysis on the global variable table to identify the global that references the player. Thereafter we intercept
insert_objand can identify room changes. - If that fails, we look at the first
print_objcall during the status window build and assume that’s the location.
There are wrinkles in this scheme - the frequency detection is a bit ad hoc and we don’t get signals before the game runs. There are only a handful of Infocom v5 games so maybe I should have just hardcoded it for each of them! Maybe I still will.
The code for all this lives with the rest of the Z-machine on github. But if all you want to do is play Zork - give Planedrift.app a try.