Bug in Inform 6's "box" statement

This was discovered by salty-horse and reported on the Gargoyle bug tracker.

I was going to report this to the Inform 7 bug tracker, but it doesn’t appear to be working at the moment.

box (the Z-machine version) can generate @set_cursor calls that try to reposition the cursor outside of the window, in violation of the Standard. This happens if the box is wide and/or the screen is narrow. The offending code is in veneer.c, and distilled it looks like so:

w = 0->33;
w2 = (w - maxw)/2;
@sub w2 2 -> w;
@set_cursor 4 w;
@set_cursor line w2;

0->33 is the width of the screen, and maxw looks to be the width of the longest line in the box. So if the box is wider than the screen, w2 becomes negative, then so does w, and they’re passed to @set_cursor. Since this is illegal (according to §8.7.2.3 of the Standard), interpreters vary in how they handle this.

The following is a quick patch (against 6.40) to “fix” it, by simply pulling the cursor back to the left side of the screen. The quote gets cut off (so maybe it’s not the best solution), but at least it’s consistent.

diff -ru inform-6.40-r1/src/veneer.c inform-6.40-r1.new/src/veneer.c
--- inform-6.40-r1/src/veneer.c	2022-07-14 12:40:15.000000000 -0700
+++ inform-6.40-r1.new/src/veneer.c	2022-07-17 20:20:16.963137636 -0700
@@ -111,8 +111,10 @@
          w = 0 -> 33;\
          if (w == 0) w=80;\
          w2 = (w - maxw)/2;\
+         if (w2 < 1) w2 = 1;\
          style reverse;\
          @sub w2 2 -> w;\
+         if (w < 1) w = 1;\
          line = 5;\
          lc = 1;\
          @set_cursor 4 w;\
2 Likes

Inform 6 bugs can be filed at https://github.com/DavidKinder/Inform6/issues.

However, I’d like some more eyes on this. I don’t remember enough about Z-code behavior to say what’s best.

Is the “negative” argument to @set_cursor interpreted as signed or unsigned?
Someone must have run into this situation twenty years ago – what did authors expect to happen back then?
How do existing interpreters deal with it?
Are there other holes in how Box__Routine() works?
Would it be better to deal with this by replacing Box__Routine() with a better Z-code implementation at the library level? (Glulx already has a library implementation.)

I don’t think it matters (unless your screen is somehow 32K characters wide):

§8.7.2.3: This position is given in characters in the form (row, column), with (1,1) at the top left. … It is illegal to move the cursor outside the current size of the upper window.

ZVM ignores any calls to @set_cursor that are outside the screen width (it does increase the screen height if the row is too large though.) On my phone the screen is only 56 characters wide, resulting in this title page for Aisle.

Making it clip rather than wrap to the next line is something I am considering changing, based on this discussion.

If you’re working with a quite narrow screen, this happens all the time.

For Ozmoo, we decided the least ugly solution is to always keep track of the cursor x position even when it’s outside the screen. The cursor x position is expected to be in the range -128 to 127, where position 1-40 is visible on the C64 screen. When a character is printed outside the screen, nothing happens except the cursor is moved one step to the right. This makes sure everything that is printed is on the line where it’s supposed to be, and text that should be centered is centered. Among other things, this makes menus work as intended even if some of the options don’t fit entirely on screen.

1 Like

Ok, thanks for the pointer.

I agree with Dannii here: either way it’s going to be off the screen. Interpreters are supposed to write their screen width into an 8-bit header entry, so I’m not sure anybody ever dreamed of a width larger than 255. I don’t think the Standard mentions signedness at all apart from the first argument in V6 being allowed to be -1 or -2.

I was hoping somebody would pipe up with insight here… I suspect most quote boxes aren’t too wide, and it just wasn’t noticed, but that’s speculation.

  • Zoom - Moves the cursor all the way to the left, no wrapping
  • Fizmo - Blank screen
  • Frotz (including Windows Frotz) - Cursor to the left, no wrapping
  • Nitfol - Appears to roughly behave the same way as in Danni’s screenshot of ZVM, while reporting lots of errors about invalid cursors/writing beyond the end of the window
  • Filfre - Blank screen
  • ZLR - Cursor to the left, no wrapping
  • Bocfel (Gargoyle) - First “line” is printed (it’s always just blank/spaces, so looks like an empty status bar), then nothing else, as the cursor has reached the end and the cursor repositioning fails due to being outside of the bounds of the window

Nothing I’m aware of at this level (violating the Standard).

On a minor note, the box is always shifted one character to the left (not centered), apparently because it assumes @set_cursor is 0-based, when it’s 1-based.

I’m not familiar with Inform’s internals at all, but naively it seems to me that taking hard-coded Inform code out of the C source and putting it into an Inform library can only be a good thing, and would make tweaking this code a bit easier.

2 Likes

Seems like no interpreters are doing this, but I just thought of another potential option: make the window wider and horizontally scrollable. But users wouldn’t expect this and might not even notice that it is scrollable, so would it really even be any better?

@fredrik So if I understand correctly, Ozmoo does allow for negative and too high x co-ords, it just doesn’t print anything that’s off-screen? Interesting idea…

Indeed. If a game wants to print a text that is 60 characters wide, centered on a 40 column screen, it will typically move the cursor to (40 - 60) / 2 = -10. Ozmoo will then silently ignore the first 11 characters that are printed, print the next 40 characters on screen, then silently ignore the last 9 characters. It’s not perfect, but I think it works reasonably well.

1 Like

Considering that the index of the leftmost column is 1, I think this should be:

          w2 = (w - maxw) / 2 + 1;\

I agree (this is what I meant with the box being shifted to the left). My patch was solely for the out-of-bounds bug, but I’m in favor of fixing the box location as well.

1 Like

By default, all integers on the Z-machine are treated as signed, so that’s what I would assume here. However, the Standard doesn’t commit one way or the other; as far as I can tell it tries to specify for each operation individually whether it takes signed or unsigned arguments, and then sometimes forgets.

Not that it really matters here, of course.

I agree it probably doesn’t matter. But I suspect that an interpreter’s reaction to X<1 is probably going to be “adjust X to 1”, whereas X>width might be handled as “adjust X to 1 and move to the next line”. This might be affect the other answers.

it seems to me that taking hard-coded Inform code out of the C source and putting it into an Inform library can only be a good thing, and would make tweaking this code a bit easier.

I definitely agree with this. The Inform library is updated more often and is easier for authors to mess with.

It’s still probably worth fixing this in the Inform veneer code. But I’d prefer to think of the veneer version as a minimum-viable fallback implementation.