Finding the current room in v5 zcode games

I’ve been working on a Z-machine in Elm and moving from v3 to v5 support has been a bit complicated. I think there isn’t a standard way to tell what the player’s current room is - specifically I want to be able to detect when the room changes.

Does anyone know of a better way than intercepting insert_obj calls and heuristically figuring out which call represents insert_obj player room . What I’ve done seems to work but I can’t help thinking there must be a better way.

I wrote a blog about about my current approach - on whitebeard-dot-blog (april 17th post) - which goes into more details about my thinking.

1 Like

The Z-Machine is a general purpose computer. There might not even be any rooms in the code, nor a player character!

Basically all Z-machine parser systems keep the current room in a global—it’s a lot more time-efficient than trawling through the object tree every time, and any systems that support Z3 need that global for the status line anyway. So searching for that global will probably be a lot easier than tracing the insert_obj calls.

On ZIL, I believe it’s the first global unless the author specifies otherwise. Likewise in default Inform 6 and PunyInform because that way the library can work for both Z3 and Z5. In Dialog, though, it’ll be buried somewhere in the middle of the globals.

In Z3 it’s global 0, but in Z5 it can be any global.

Likewise in default Inform 6 and PunyInform because that way the library can work for both Z3 and Z5.

This is historic for I6, which hasn’t supported Z3 in a long time. It’s possible for an I6 game to define some globals before the library’s location.

For that matter, there’s no real requirement for the player’s location to be handled by the library variable. The game could define some special case in which the player’s “location” is managed by some other mechanism. The interpreter can only make assumptions.

Maybe I should have been clearer that my interest is in running games with rooms and player characters. But, come on! I don’t think your comment is in good faith.

You’re quite right, of course, there’s no way to support something like this generally because it just doesn’t make sense for all Z-machine programs. I think asking the question still makes sense?

You could intercept writes to the upper window and extract the room name from the left side of the status bar, or watch for calls to print_obj while the upper window is active. If the game draws the status bar by printing an object’s name out of a global variable, that variable very likely holds the player’s location.

Z3 has a standard location, but for Z5 there’s no such thing. Even conventional Inform games can do things out of order as the other comments have said. You could use a specific storyfile’s compiler debug file for that specific storyfile, but not for an arbitrary one.

What is your purpose that you’re trying to accomplish here? Understanding that might help us know if there’s any other way of doing it. For example Tara’s suggestion of looking at changes to the status window. Though that’s not at all equivalent to changes in the room - you could change rooms to another with the same name, or change the name of the current room.

I didn’t take the comment as being in bad faith; just pointing out that there’s no surefire solution. Only heuristics.

I believe Inform now always prints the short_name property of the room (if it exists) instead of using print_obj for these things.

The best heuristic I can think of is:

  • Enter a bunch of blank lines to get past any starting screen
  • Take note of any variables that have changed exactly once during the startup sequence (generally the location variable is calculated from the object tree, not vice versa)
  • Try N, NE, E, SE, S…in the hopes that one or more of them works
  • Take note of any variables that changed during that process
  • Try Z, Z, Z, Z, Z…
  • Take note of any variables that didn’t change in that process (turn count etc)

Does it access the properties of any other object to draw the status bar? If not, you could watch for calls to get_prop too.

Won’t work for every game, of course, but nothing will.

Depends on the game, but I think you’re right: by default, it only prints the room name (meaning it needs to look at the location’s property table) and the score and moves (globals). So probably the only “get property address” (I forget the Inform name) instruction during the status line printing is for the location. I like that system!

Thanks for the helpful responses. Let me excerpt a bit from the blog post that maybe clarifies what I’m trying to do.

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.

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_obj and can identify room changes.

  • If that fails, we look at the first print_obj call 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.

1 Like

Can you relax this behaviour? The other IF interpreters which do autosaves typically save every single turn (right before the player is asked for input).

1 Like

Yes, I could do this, in fact early versions did do this. I think it’s useful to only save when players move between rooms for a few reasons - not least making the history much easier to navigate and understand. But there are other reasons I want a definite roomchange trigger in my client to support some future things I have planned.

Notably, though, this means the autosave is useless for a one-room game.

3 Likes

In ZIL games, you can identify rooms because they’re all children of the ROOMS object, and you can identify ROOMS because it has no parent(*), it has lots of children and grandchildren, and it has very few properties (zero to two)..

(*) True for all Infocom games except the samplers.

As long as the game doesn’t start the player in darkness, you can use a heuristic to find the variable containing the player’s location on the first turn, then check that variable thenceforth.

1 Like

A good point, thanks.

I was just reading this week through the Inform std library and saw that, despite the comments in that code, there was at least one global before the location was defined, so for lots of z5 games, it won’t be the first global. (I got bit by this when I was still trying to compile Our Lady Of Thorns for z3 and didn’t realize that I absolutely could define no globals before the library, so that it was guaranteed to be the first global).

Has anyone played Suspended? :wink:

1 Like

I always find these type discussions over my mechanics grade, but I like to read them to see what the boffins are talking quietly about, and glean a certain general conversation understanding from.

Rob

4 Likes