The Z-machine's @set_font ... who gets it right?

First, a simple program:

[Main i j k l;
  @set_font 4 -> i;
  @set_font 1 -> j;
  @set_font 0 -> k;
  @set_font 1 -> l;
  print i, " ", j, " ", k, " ", l, "^";
];

I tried this in 10 interpreters (including my own WIP) and I got literally 10 different results.

Of course, part of that difference is that interpreters might not support font 4. That leaves six that return 1 for the first call and still disagree on the rest. I think it’s obvious that, if the first call succeeded, the second should return 4. So, in an interpreter that supports fixed fonts, i should be 1 and j should be 4.

The real issue is @set_font 0. The standard (8.1.2) says that “font ID 0 means ‘the previous font’.” However, some interpreters treat @set_font 0 as a no-op and just return the current font. While useful, in that you can find out what the current font is, I can’t see how this is standard behavior. I would expect @set_font 0 to switch back to fixed, which was the previous font, and return 1. A number of interpreters return 0, indicating that font 0 is unavailable, but I suspect this can’t be right either, because they apparently had no problem setting font 4 (or 1, for that matter, which must be available).

Finally, the last call I would expect to return 4, since I expect @set_font 0 to have switched to font 4. At any rate I would not expect 0 to be returned, because font 1 must be available, and a couple of interpreters return 0 (probably because the 0 from @set_font was stored on the previous call).

The real burning question: should multiple calls to @set_font 0 alternate between the previous and current fonts, or should it simply return the current font over and over?

So another way of framing the question: is the “previous font” the font in effect before the call to @set_font, or the font in effect before the immediately preceding call to @set_font?

In the first case, I would expect @set_font 0 to make no change to the active font. In the second case, I would expect it to alternate between the active font and the preceding font.

Furthermore, is the “previous font” returned by the call to @set_font the font in effect before the call to @set_font, the font in effect before the immediately preceding call to @set_font, or the most recent font bar one after @set_font does its work?

In the first case, I would expect @set_font 0 to store the value of the active font. In the second case, I would expect it to store the value of the preceding font after switching to the preceding font. In the third case, I would expect it to store the value of the formerly active font after switching to the preceding font.

Here’s what I implemented in glkscreen.c.

For the example program, I believe it would print:

1 4 4 4

In other words, for the questions above I decided that “previous font” meant the font in effect before the immediately preceding call to @set_font, in both cases - setting the font and storing a value.

Well, with so many possibilities it’s no wonder interpreters disagree!

In my collection of Infocom games, there’s exactly one place where @set_font is called with a variable, not a constant: in Journey the following happens:

4df1:  JZ              Gb9 [TRUE] 4df9
4df4:  SET_FONT        #04 -> L02
4df9:  LOADB           #00,#26 -> G89
4dfd:  LOADB           #00,#27 -> G92
4e01:  JZ              Gb9 [TRUE] 4e09
4e04:  SET_FONT        L02 -> -(SP)
4e09:  LOADW           #00,#11 -> -(SP)

Assume Gb9 is not zero (it gets set to one or zero based on the interpreter number).

By one interpretation, these @set_font calls appear to do nothing: if @set_font returns the current font (at the time of the @set_font call), this will switch to 4, store current in L02, load a couple of header values into globals, then switch back to L02: whatever was current at the time of the first call. As far as I can tell, the value pushed onto the stack by the second call is never used.

The first @set_font call here is also the first @set_font call in the entire program. So if the previous font is not the current font (i.e. the previous font after the call), what would it mean? There could have been no previous font to the initial font.

OK, I’m confused now.

In the computer world, “set and return the previous value (before this call)” is a common pattern. (The POSIX umask() function works this way, for example.) It makes it easy to change a value and then restore it.

“Set and return the previous value (before the previous call)” is weird and, as far as I can tell, useless. I can’t believe that it’s what Infocom intended.

The Journey code you quote makes sense if the intent is to measure the font metrics. Header bytes $26 and $27 are “font width/height”. Obviously that’s measuring the current font. So this is

if (interpreter_supports_font_change)
    set font 4;
global_font_width = current_font_width;
global_font_height = current_font_height;
if (interpreter_supports_font_change)
    restore font to previous value;

I feel like I’ve either misunderstood you or vice versa.

In the “set and return the previous value (before this call)” case, the previous font would be the one in effect before the @set_font call under consideration, in other words the font that would be used if this @set_font call was omitted altogether and never took place. If font 1 is on before, then font 1 is on after. If 4, then 4. The call is only useful if you want to know what font is being used, because it stores that as its return value.

By “set and return the previous value (before the previous call)” I meant something along the lines of what I think umask means by “leave the state… the same as its state before the first call.”

This would allow you to toggle back and forth between states. Yet it’s also the interpretation that you call weird and useless?

I assume it’s normal all the way down. In other words, if you don’t have state information to the contrary, then treat it as though the normal font were active. I suppose you could also store 0 to say that it wasn’t available, but that creates issues across a restore / restart boundary. (Maybe those issues are there in any case - ugh.)

The drawback of storing the value of the previous font after the call does its work is that the game can’t tell which font is now in use. @set_font 0 as no-op and @set_font 0 as toggle that returns the toggled value both provide this data. @set_font 1 and @set_font 4 do not, but in those cases you know which font is in use because you passed it as a parameter.

I dunno. I admit I’m not persuaded by my own logic but that’s because the spec is not framing the behavior in a clear and logical way, leaving us to guess at the author’s intent.

I wouldn’t call it ‘useless’ but FWIW, I too found your interpretation of ‘the previous font’ reference unusual, since I would tend to assume it meant ‘previous to the function call currently under consideration’. I wouldn’t think it meant ‘previous to the previous function call’ unless it specifically spelled that out. Just another POV.

Paul.

Let me rephrase:

I think @set_font should be implemented as if it were this pseudo-code:

Global current_font;
opcode set_font(newval) {
  temp = current_font;
  current_font = newval;
  return temp;
}

You can then change and restore the font by doing:

@setfont 4 val;
@setfont val dummy;

…which is exactly what the sample code does. That’s why I think I’m right.

To toggle between two values, you could do @setfont val val, I guess.

But I’m talking about the case where the argument is not zero! Which is what the Journey code is doing, right?

OK, this makes perfect sense. Being that I always just set $26 and $27 to 1, I missed the following in §8.1.1: “[T]he height and width of the current font … should be written to bytes $27 and $26 of the header, respectively.”

I can’t see how zarf’s take is not correct.

…if the argument is zero, then we don’t have any sample code at all. (By what cas posted.) Do we have any Infocom code where they used @set_font 0?

I think I got off on the wrong foot by talking about the use of “previous font” in the set_font paragraph in section 15, whereas the original question was about 8.1.2. The least confusing interpretation is that they have the same meaning. I.e. I should rewrite my pseudocode to say:

Global current_font;
opcode set_font(newval) {
  if (newval == 0) { newval = current_font; }
  temp = current_font;
  current_font = newval;
  return temp;
}

But I don’t know that that’s right; it’s just how I read the spec. The spec is either confusing or wrong in 8.1.2, but I have no way to tell which off-hand.

Yes, that’s right. The only way it could ever call @set_font with 0 is if @set_font 4 were to return 0. Under the current standard I think it could do this if an interpreter does not (or claims to not) support fixed fonts. Journey does not use the fixed-font header bit to determine whether a fixed font is available (it checks the flag, but does not use its result in this code from what I can see); rather, it “knows” which interpreters support fixed font and only calls @set_font 4 on those that do. So I suspect Journey was not equipped to handle @set_font 0.

I haven’t found any instances of @set_font 0 in my Infocom stories.

If we go by the definition in 8.1.2, font ID 0 means “the previous font.” If this in fact means “the font in place prior to the function call currently under consideration”, it would be considerably less opaque to term it “the current font.” Current is not ambiguous; it means the font that would be used if the game were to print text on the screen at this moment.

By using the term “the previous font”, ambiguities arise because we must answer the question, previous to what? To the call we are making now or to the last most recent call we made? If the first answer is intended, 8.1.2 should state that font ID 0 means “the current font” and remove the ambiguity. If the second, it is worded appropriately but there are further ambiguities surrounding the return value.

Again, I don’t know the right answer. I like zarf’s pseudocode better than my actual code, though. :slight_smile:

I would have thought the pseudocode would look like

Global current_font;
Global previous_font;

opcode set_font(newval) {
   if (newval == 0) { newval = previous_font; }

   previous_font = current_font;
   current_font = newval;
   return previous_font;
}

which would allow set_font 0 to switch to the font that was in use before the current font, while returning the font that just was changed out…

Well, you could do it that way. But it’s making the interpreter more complicated to support a feature that the game can perfectly well handle on its own. The history of the font register (as opposed to its current value) doesn’t seem like the sort of thing that the original implementors or the spec authors would bother with. Nothing else in the system retains a history like that, and the only reason it seems to have come up in this case is the wording of the spec – nothing architectural.

FWIW, here’s the wording from Infocom’s own spec, which seems no less ambiguous:

Has anyone checked the behavior of Infocom’s interpreters?