The Visible Zorker: Zork 2

A year ago, I posted the Visible Zorker. I had some free time over the holiday break, so say hello to:

(Subtitle: “The Visible Wizard of Frobozz”. Ok not really but it’s fun to say.)

New features:

  • Dark mode!
  • Map tab!
  • The call stack (Activities tab) now shows function arguments as well as function names!

Also, per Torbjörn’s comment this weekend, there is a cheat feature where you can make the Wizard cast a specific spell on you. (Go to the Timers tab, hit the green button next to I-WIZARD, then select “Test the Wizard’s spells.”)

Is Zork 3 next? Stay tuned! (Probably but I am working on a larger plan.)

26 Likes

Do you realise that you have realised the ultimate IF debugging tool ???

Zarf: The Rock of IF !

Congrats and
Best regards from Italy,
dott. Piergiorgio.

1 Like

Yes, you’re not the first person to think of that. You’d need to adapt it to run off the debug data generated by a ZILF or Inform 6 compile.

3 Likes

Very cool. I’m really appreciating how much work went into this. Do you have to hand-bake intermediate debug symbols (or something like this) for each zil file, or does that stuff already exist for the zork games?

Love the maps. Look really good.

1 Like

It’s a mix of hand-baked symbols and symbols extracted from the source code according to some heuristics. (The key heuristic is that functions in the source code and functions in the disassembled TXD dump occur in the same order! Almost.)

Allen Garvin, Ben Rudiak-Gould, and Ethan Dicks did a lot of the symbol analysis work back in 2007 (see here). However, they didn’t have the ZIL source at that point, so they had to make up all their own symbol names. So I couldn’t just pull their work off the shelf. I had to do a bunch of additional matching-up.

See my blog post last year for more details.

3 Likes

Fascinating read. Seeing the zil alongside the game gives a real appreciation for how those original game authors had to use a great deal of imagination, planning, and ingenuity to make the game feel immersive, and on such tight memory budget of those times.

This excerpt from your blog post answered my next question perfectly, because I was wondering how heavy the lift would be to generalize your work for all the zil games.

Are you going to do the rest of the Infocom games?

I doubt it. It wouldn’t be much fun. I’d have to reconstruct the object numbers and functions addresses and so on for each game, and maybe rewrite some of the display logic as well. (I tried to keep it general, but some things are still hard-coded for this specific Zork release.)

1 Like

You may wonder what changed since I wrote “I doubt it”… The open-source release of Zork 1/2/3 was one source of motivation, of course.

4 Likes

I like the annotations, but is there an easier way to find them than to scroll through all of the source code?

I did notice two things about the Zork I annotations. The comment about the troll (1actions.zil, line 707) says that “we have no version of (MIT) Zork that early in development”. Is that still true? mdlzork/zork_285 at master · heasm66/mdlzork · GitHub suggests that it may have been at least partially recovered.

The comment for ROB-MAZE talks about how the thief will taunt you when he picks up things while you’re both in the maze. I remember reading about that in what may have been the first article I ever read about Zork, back in the eighties in a Swedish computer magazine.

So you can imagine I was slightly disappointed when I finally played Zork I, and I never saw it. Ever. And it turns out there was a reason for that. The thief only robs rooms that haven’t been visited. It would be very unfair otherwise. This is what I-THIEF says:

       <COND (<FSET? .RM ,TOUCHBIT>     ;"Hack the adventurer's belongings"
          <ROB .RM ,THIEF 75>
          <SET FLG
           <COND (<AND <FSET? .RM ,MAZEBIT>
                   <FSET? ,HERE ,MAZEBIT>>
              <ROB-MAZE .RM>)
             (T <STEAL-JUNK .RM>)>>)>)>

But DESCRIBE-ROOM marks all maze rooms as non-visited, probably so that brief mode won’t do you any good to figure out where you’ve already been:

     %<COND (<==? ,ZORK-NUMBER 1>
         '<COND (<FSET? ,HERE ,MAZEBIT>
                 <FCLEAR ,HERE ,TOUCHBIT>)>)
        (T
         '<NULL-F>)>

I was looking at that the other day, thinking “why didn’t they just set V? to true instead. And it turns out that’s exactly what the Solid Gold version does.

Another oddity in Zork I is that if you enter the Dome Room as a ghost, it manually moves you to TORCH-ROOM and sets HERE. I’m guessing that when it was written they didn’t use GOTO because that would have printed the room description twice, or sometehing, but that no longer happens. In fact, you don’t get any room description when you enter:

>EAST
As you enter the dome you feel a strong pull as if from a wind drawing you over
the railing and down.

>

(I’ll probably have Zork II notes later. :slight_smile: )

5 Likes

I have a early version of a debugger (WinZLR ) that uses the Inform6/Zilf debug-files. It only runs on windows, but maybe there are some useful tidbits in there?

I think the most interesting bit is the documentation on how a extraced the source data from the MIT recovered binaries.

1 Like

Go to visizork2/gamedat at master · erkyrath/visizork2 · GitHub and read the commentary file.

Oh no! I had no idea that feature had disappeared from this version. (It’s another message that I clearly remember from 1980.)

The zork_285 directory… I missed that when I was browsing the repository (last year). Thanks!

I’ll add those notes tonight.

4 Likes

Yeah, I was surprised when I finally saw it in another version because at that point I just assumed that I had been reading about a feature in Mainframe Zork. I don’t remember which version I played originally, but it was probably release 88 because I think I remember “diagnose” telling me that “You can strong enough to take several wounds”.

Another note I forgot to mention: You wrote about the TEETH object, but did you also notice that it can be referred to as OVERBOARD? That’s because of how it implements “THROW object OVERBOARD”: The second object in the sentence has to be the teeth.

Unfortunately, it’s one of those cases where they forgot to check that it’s okay to throw the object. So if you’re sitting in the boat…

>THROW PUMP OVERBOARD
Ahoy -- hand-held pump overboard!

>THROW HANDS OVERBOARD
Ahoy -- pair of hands overboard!

>THROW LUNGS OVERBOARD
Ahoy -- blast of air overboard!

>THROW GRUE OVERBOARD
Ahoy -- lurking grue overboard!
5 Likes

This is one of my favorite bugs (along with similar ones). My favorite outcome is “throw me overboard”, which from there on causes this line to appear at that point:

There is a you here. (outside the magic boat)

Also, this can break responses, for instance if you “throw grue overboard” you will no longer be able to get the description of grues anywhere except that section of Frigid River because “You can’t see any grue here!” (Which may, in the most technical sense, not be inaccurate, but still. Also, it’s not like it stops you from being eaten by a grue.)

Also, curiously, while trying this I discovered that >KILL GRUE has the same response as trying to kill any random inanimate object, it says it’s a strange idea. Probably just an oversight in an era where memory was tight.

3 Likes

You can combine the two weird things: THROW TEETH OVERBOARD. Once you are downstream from the teeth, THROW OBJ OVERBOARD stops working.

There is a you here. (outside the magic boat)

One of the odd things I discovered last year (of course many of you discovered it years earlier) is that ZIL maintained separate ME and ADVENTURER objects. The ADVENTURER moves around the map, but can’t be referred to (has the invisible flag). The ME is a global scenery object (always in scope).

I’m not sure what the benefit of this is. The first answer I thought of: to have an abstract “me” referent for cases when you give an NPC a command. But really, when you say “DEMON, GIVE ME THE WAND”, you want “me” to refer to the adventurer.

3 Likes

They neglected to apply the MAZEBIT protection to the coal mine, which is like a miniature version of the maze of twisty little passages all alike in that all its rooms have the same description text. In the coal mine, the first time you enter a room, it will tell you “This is a non-descript part of a coal mine.” Subsequent visits to the same room won’t print that text.

Gas Room
There is a sapphire-encrusted bracelet here.

>e
Coal Mine
This is a non-descript part of a coal mine. ← First time entering this room.

>e
Coal Mine ← The E exit took us back to the same room.

>ne
Coal Mine
This is a non-descript part of a coal mine. ← This is a new room, different from the first.

>n
Coal Mine ← The N exit took us back to one of the two already visited rooms.

>se
Coal Mine
This is a non-descript part of a coal mine. ← This room is new.

5 Likes

The most ridiculous way I’ve found of getting rid of your own hands is probably in Zork II, where it happens if you “DESTROY ROBOT WITH SWORD”. No, really. This is the code for it:

          (<VERB? THROW MUNG>
           <TELL
"The robot falls to the ground and (being of shoddy construction)
disintegrates before your eyes." CR>
           <REMOVE <COND (<VERB? THROW> ,PRSI) (,PRSO)>>)>>

The idea here seems to be to figure out which of PRSI and PRSO refers to the robot. Which is already strange, because why would you ever remove anything else than the robot? But the COND is wrong anyway. If the verb is THROW it returns PRSI which works. But it then checks if there is a PRSO and, if so, apparently returns true by default.

So for “THROW SWORD AT ROBOT”, it will remove the indirect object, i.e. the robot. For “DESTROY ROBOT WITH SWORD” it will remove object 1, which is your hands.

6 Likes

This version has my least favorite unintentional (?) change in Zork II:

>PUT FUSE IN BRICK
Done.

>PUT BRICK IN HOLE
Done.

>LIGHT MATCH
One of the matches start to burn.

>LIGHT FUSE
(Taken)
The string starts to burn.
The match has gone out.

Wile E. Coyote would be proud.

Historically, Zork I has always automatically picked up the object you tried to light. Zork II and III only added it later, presumably when they began to share code.

1 Like

Hm, I had forgotten that was different in earlier versions.

The best I can tell, the grammar wants to differentiate between LIGHT MATCH (similar to LIGHT LAMP, requires no indirect object) and BURN FUSE WITH MATCH (which will infer the indirect object, so BURN FUSE works). BURN has no autotake.

LIGHT FUSE WITH MATCH invokes the BURN verb, so that works too. But this is not at all obvious to the player, of course.

1 Like

Yeah, it’s not game breaking. But it’s certainly annoying when it happens. There are several changes, actually. The syntax, obviously. In mac-r22 it looks like this:

<SYNTAX LIGHT
	OBJECT (FIND LIGHTBIT)
	(HELD CARRIED ON-GROUND IN-ROOM HAVE)
	= V-LAMP-ON ;*>

In r48 it looks like this:

<SYNTAX LIGHT OBJECT (FIND LIGHTBIT)
	(HELD CARRIED ON-GROUND IN-ROOM TAKE HAVE) = V-LAMP-ON>

So the TAKE bit has been added. Furthermore, the semantics for HAVE seem to have changed too. In mac-r22 ITAKE-CHECK does this:

	 <COND (<AND <SET PTR <GET .TBL ,P-MATCHLEN>> <BTST .BITS ,STAKE>>

While in r48 it does this:

	 <COND (<AND <SET PTR <GET .TBL ,P-MATCHLEN>>
		     <OR <BTST .IBITS ,SHAVE>
			 <BTST .IBITS ,STAKE>>>

But those bits are pretty confusing, so I couldn’t say exactly what the implications are. Finally, FUSE-FCN has changed in how it handles LAMP-ON. Here’s mac-r22:

	<COND (<VERB? LAMP-ON>
	       <TELL "You must use a match!" CR>)

And here’s r48:

	<COND (<OR <VERB? BURN>
		   <AND <VERB? LAMP-ON>
			<IN? ,MATCH ,WINNER>
			<FSET? ,MATCH ,ONBIT>>>
	       <TELL "The string starts to burn." CR>

So they tried to make it more helpful, but with some unexpected side effects…

2 Likes

I remember being burned, hah, by LIGHT FUSE.

A couple benefits come to mind: global ME is in scope even when the location is dark, and it keeps working as expected if the player-character is ever switched to someone else.

1 Like