Debugging v8 support in my interpreter; I'm out of ideas

I’m slowly improving my terp, Status Line for the Pico-8, to handle v5+ games. Step by step making progress, but I’m hitting a wall where (almost) every game I try exhibits some unique weird behavior. That’s on me.

However, this one really stumps me.
Lost Pig v8 won’t display an input caret. The game works, accepts input, and has an input cursor ready to go. It plays (hallelujah).

The particularly odd element I can’t get my head around is that in viewing opcode dumps, I don’t even receive a request to draw the caret (or new lines before it). The game just doesn’t even make the attempt; no opcodes for printing are received (except for status line) after the first flavor text. In Yazmin, the caret drawing is called between flavor text display and status line drawing. Mine just jumps straight to status line.

I thought maybe the header settings was suppressing this, but I tried setting macOS Yazmin to the same screen size (32 chars x 21 lines) and it works perfectly. I set the peripheral flags to match Yazmin, and still no dice.

I’m trying to step through opcodes in both my terp and Yazmin, and they do differ slightly sometimes, which might be why their output differs? :person_shrugging: But I can’t figure out what would control this particular behavior to know what I’m looking for.

Here you can see Yazmin and Status line set to the same peripheral values, as well as interpreter and number/version, just to try and rule those factors out as culprits.

Screenshot 2024-12-12 at 13.44.17

Most likely won’t change anything, but have you tried other IF (written in other languages) to see if the issue persists? Can you enter commands even without the caret displaying? Are you actually asking the interpreter to draw a caret (who knows, maybe you forgot)?

Otherwise I’m no longer competent enough in any programming language to help you more specifically.

1 Like

Thank you for the feedback, and it definitely will help to clarify some matters.

  1. Of the z5+ games I’ve tried, this is the only one exhibiting the behavior. This includes Border Zone, Spider and Web, Advent.z5 and Advent.z8, Photopia, and more. Those all call the print routine with a “>”.
  2. Yes, I can enter commands and play the game.
  3. In z4+ games, the caret is not the responsibility of the interpreter (per se). The game must make an explicit print request for the “>”.
  4. Here’s Anchorhead.z8 doing the right thing (for the caret anyway). It is sending the system a print request to display that input prompt.
    status_line_0

Poking through the opcodes in Lost Pig, the request to print “>” is prefaced by a HUGE list of variable checks with jumps. Dozens and dozens, and I’m sure those are likewise prefaced with dozens of additional checks. I guess one of those checks is failing somewhere along the line and short-circuiting the chain of events that leads to the draw routine. I can’t visualize the choice or check it could possibly make that would decide, “Let’s keep playing but don’t show the input prompt.”

Hmm…Spider and Web was also Inform 6 so it’s not the language. I’m not experienced enough to help you further (sorry!) so I’ll leave this to someone with more expertise.

1 Like

I would think you have a problem with some opcode not behaving correctly. Does the interpreter pass the standard test suites, such as Czech, Etude and Praxix?

1 Like

That is another part of the puzzle.
Czech, Etude, and Praxix all pass tests (for supported features) 100%.
I’ll run them again tonight, in case there’s a regression or “FAIL” message I didn’t catch :thinking:

1 Like

Some reasons why specific games have failed at various points during the development of Ozmoo:

  • Dynamic memory is larger than a certain threshold
  • Something which is usually in the first 32 KB is higher up in this game, so a 16-bit address has to be considered as unsigned. Maybe globals?
  • The game uses a rarely used feature of the Z-machine which may not be adequately tested in the test suites, e.g. custom dictionaries, reading the cursor position, or reading the number of arguments to the current routine.

One thing that makes some Infocom interpreters fail in weird ways, is if they call a routine with more parameters than the routine has local variables. This was clearly illegal to do in Infocom games, but it’s legal according to the modern day Z-machine standard. Some Infocom interpreters would silently change the value of a local variable in the calling routine instead, often causing the game to behave weird, and it’s hard to figure out why.

2 Likes

To debug the issue at hand, I would probably:

Disassemble the game.

Make the interpreter pause when it hits the code that checks conditions that may stop it before printing the caret.

Follow step by step until it decides to exit or jump past the print statement.

See which global, if any, has a value that made this happen.

Read the code, try to figure out what the global does, and which values it can be expected to have.

Set the interpreter to pause whenever the global changes value. Try to see when it changes value in a surprising way. Also note of course, that it may change because of code changing the value of a certain byte in memory, e.g. a storew opcode, or because of broken code in the interpreter.

1 Like

Thinking back to early days of buggy interpreters… Maybe the output handling got screwed up? Like if there was nested memory streams and your interpreter didn’t pop all the way back out, but the @read call reset things.

I guess that doesn’t fit with the print opcode being skipped. But it’s worth thinking about.

2 Likes

Thanks @fredrik and @zarf for the input and feedback. :bowing_man:

@fredrik : Yes, I’ve done the disassembly and have made at least a first attempt to trace back from the print statement to see where the decision to show it begins. It is a lengthy list of 30?-ish chained JE statements and finding the root of that, and understanding each and every value in that chain has proven… daunting. But I’ll push through it. :muscle:

But you have quickly keyed in on two things that have been nagging at me: signed/unsigned handling and globals. Pico-8 is not a great environment for debugging, which makes the research 100x harder than it needs to be (in fairness that system was not really designed for the level of development the community has thrown at it over the years); so I’ll need to roll my own “breakpoint” system. Weekend project!

@zarf Output handing (per se) doesn’t sound to me like the issue going on here, as I think if output handling was that screwed up I’d see it manifest in v3/4 games as well. But even v5 games are outputting correctly (advent.z5, Border Zone), so I dunno. I don’t quite follow what you mean by “nested memory stream” though. That sounds plausible, but I don’t quite follow the specific condition you’re describing.

All of the above having been said, there are definitely strange issues occuring in other z5 games, I have since discovered. Nothing like this specific one with Lost Pig, but definitely of a “that’s definitely not right” variety. It may be that there is a root cause which manifests in fun, original ways for each game… which points me back to those globals again :thinking:

1 Like

I meant calling @output_stream 3 twice in a row without deactivating stream 3 in between. I suspect that’s not legal, but it’s the sort of thing a complicated game might do by accident and not notice if the popular interpreters all behave the same way.

All things considered, a signed/unsigned bug sounds more plausible.

1 Like

Another fun thing that has happened in Ozmoo:

Trying to print something in the lower window, when the lower window is 0 lines high, and we didn’t check for this, so instead we printed in memory just after screen RAM… and that’s where the jump table was, pointing out where all Z-code instruction routines begin. One of them got altered. Nothing happened immediately, but one Z-code instruction was now corrupt, and the next time it was used, we made a jump to a rather random location in the code. Oh the fun! :smiley: Took me an evening to find it.

Good luck with the debugging!

2 Likes

It is legal:

7.1.2.1.1

***[1.0] It is possible for stream 3 to be selected while it is already on. If this happens, the previous table address is remembered and the previous table is resumed when the new one is finished. This nesting can reach a depth of up to 16: if stream 3 is opened for a seventeenth time, the interpreter should halt with an error message.

2 Likes