Updating the Z-Machine Standard Documents

This is why I feel like the z-machine needs a high resolution delay opcode divorced from timed input. Although gratuitous use of timed text isn’t something I am a fan of, I can imagine an author wanting delays for other reasons: like animation. The current 1/10 second resolution for timed input works for that, but is crude at best. Lacking a new opcode, allowing a value other than ‘1’ for the input device on read_char could be leveraged to allow greater timing resolution.

2 Likes

Not the topic of this thread, but updating the ZIL compiler to output to Glulx wouldn’t be impossible, since Glulx and the Z-machine are intentionally designed to be very similar. It offers more freedom in various ways, but authors/languages/systems don’t need to make use of it.

1 Like

Yes, that seems like a fine change to make. (The question is just whether interpreters will be updated for it at this point.)

I hear that a remake of Fabularium is being made, so if the new one is in development, you could contact them to make sure they’ve got it updated to the new standards. Parchment could be done as well, I guess. And Gargoyle? I don’t know.

I used to be one of the people most pushing for adding features to the Z-Machine - I proposed the gestalt opcode after all! But since the 14 years since then, there’s been almost no real interest for it. Sure people propose things every now and then, but it’s usually stuff that could be handled already (or more easily) in Glulx.

At this point, the Z-Machine as a target for new games is basically solely for those interested in some way in retrogaming. And that’s an interest that is mostly incompatible with expansions to the format, as they either won’t be supported in interpreters for retro platforms, or it wouldn’t be possible to implement them in a reasonable manner for those platforms. If someone wants to share that continuity with the 80s era Z-Machine, then great! But if you want to expand the format, then use the format from the late 90s that was designed for it. Honestly I’m even considering removing the vestigial gestalt support that I added to Parchment’s ZVM. No one is using it.

Updating ZILF to support Glulx is very feasible, and logistically much easier than trying to get a wide enough cross-section of Z-Machine interpreters to add something new.

5 Likes

I feel like most of the Z-machine activity now is people targeting physical 8-bit hardware. What kind of high-resolution timing are you going to get on a 6502 CPU?

1 Like

I’m sure someone here (that isn’t me) knows what older hardware is capable of.
Being able to specify intervals in milliseconds would bring it in line with most modern usage. Falling short of that, hundreths of a second would at least be better than tenths. It may not be possible on some hardware, but are all z-machine features equally available on older hardware? Perhaps it could be part of a version 9 if enough other features were proposed and wanted.

This isn’t something that I’m like “We need this now!”, more like “Here’s a thing that could be useful.”

I’m also not ready to say the z-machine should remain stagnant. Why can’t it be incrementally improved alongside other systems?

1 Like

My inclination would be to keep it in tenths of a second, but let the first argument of @read_char be changed to specify a different input device (or none at all).

1 Like

§6.2

Global variables (variable numbers $10 to $ff) are stored in a table in the Z-machine’s dynamic memory, at a byte address given in word 6 of the header. The table consists of 240 2-byte words and the initial values of the global variables are the values initially contained in the table. (It is legal for a program to alter the table’s contents directly in play, though not for it to change the table’s address.)

In short, there isn’t always 240 globals. See discussion here.

Zilf make the globals table as big as the actual used globals. Infocom did the same from 1987-.

List of Infocom games (from The Obsessively Complete Infocom Catalog with an array less than 240 globals.

abyss-r1-s890320.z6
arthur-r40-s890502.z6
arthur-r41-s890504.z6
arthur-r54-s890606.z6
arthur-r63-s890622.z6
arthur-r74-s890714.z6
beyondzork-beta-r1-s870715.z5
beyondzork-r47-s870915.z5
beyondzork-r49-s870917.z5
beyondzork-r51-s870923.z5
beyondzork-r57-s871221.z5
beyondzork-r60-s880610.z5
borderzone-r9-s871008.z5
hitchhiker-invclues-r31-s871119.z5
journey-dev-r142-s890205.z6
journey-dev-r46-s880603.z5
journey-r10-s890313.z6
journey-r11-s890304.z6
journey-r2-s890303.z6
journey-r26-s890316.z6
journey-r3-s890310.z6
journey-r30-s890322.z6
journey-r5-s890310.z6
journey-r51-s890522.z6
journey-r54-s890526.z6
journey-r76-s890615.z6
journey-r77-s890616.z6
journey-r79-s890627.z6
journey-r83-s890706.z6
leathergoddesses-invclues-r4-s880405.z5
minizork-r34-s871124.z3
minizork2-r2-s871123.z3
planetfall-r39-s880501.z3
plunderedhearts-r26-s870730.z3
restaurant-r15-s880512.z5
restaurant-r184-s890412.z6
sherlock-dev-r97-s871026.z5
shogun-r278-s890209.z6
shogun-r278-s890211.z6
shogun-r279-s890217.z6
shogun-r280-s890217.z6
shogun-r281-s890222.z6
shogun-r282-s890224.z6
shogun-r283-s890228.z6
shogun-r284-s890302.z6
shogun-r286-s890306.z6
shogun-r288-s890308.z6
shogun-r289-s890309.z6
shogun-r290-s890311.z6
shogun-r291-s890313.z6
shogun-r292-s890314.z6
shogun-r295-s890321.z6
shogun-r311-s890510.z6
shogun-r320-s890627.z6
shogun-r321-s890629.z6
shogun-r322-s890706.z6
wishbringer-invclues-r23-s880706.z5
zork0-alpha-r96-s880224.z5
zork0-beta-r242-s880830.z6
zork0-demo-r366-s890323.z6
zork0-demo-r393-s890714.z6
zork0-ibm-r392-s890714.z6
zork0-oldparser-r1-s871030.z5
zork0-prealpha-r74-s880114.z5
zork0-r153-s880510.z5
zork0-r242-s880901.z6
zork0-r296-s881019.z6
zork0-r343-s890217.z6
zork0-r366-s890323.z6
zork0-r383-s890602.z6
zork0-r387-s890612.z6
zork0-r393-s890714.z6
zork0-r66-s890111.z6
zork1-german-beta-r3-s880113.z5
zork1-german-r15-s890613.z6
zork1-invclues-r52-s871125.z5
zork1-r119-s880429.z3
3 Likes

Maybe that’s the wrong way to look at it. From the interpreter’s point of view, globals haven’t changed. What’s going on is that the game is accessing the globals segment using @loadw / @storew (or @loadb, etc). Then the compiler uses that approach to store some array data over top of the later globals.

The current spec says that the destination of @storew must be “must lie in dynamic memory”, which the globals are. So this might not even be a spec change.

The only question might be if the interpreter “hoists” the globals into a native array for fast access. (Writing them back for a SAVE, obviously.) Then this pattern (I6 code) might be a problem:

Global glob;
[ func val;
  #g$glob --> 0 = val;
  return glob;
];

But I don’t think any interpreters hoist like that. I suspect there’s already code out there that uses that pattern, anyhow.

This is one of those areas where our standard conflicts with Infocom’s. The wording is the same in all Infocom docs:

This table contains a one-word slot for each global that will be used by the program with its starting value.

Making it pretty clear you don’t an entry for all 240 possible variables. Compare to the Z-Machine Standards Document::

Global variables (variable numbers $10 to $ff) are stored in a table in the Z-machine’s dynamic memory, at a byte address given in word 6 of the header. The table consists of 240 2-byte words and the initial values of the global variables are the values initially contained in the table.

I’m sure that if Infocom had ever started static memory somewhere within the 480 bytes pointed to by the global table, the modern standard would allow fewer than 240 globals; but since no game does that, it was probably not something that was even considered.

1 Like

I filed an issue at the Standard 1.2 page. I think the discussion there boiled down to maybe add a note that overlap of the global area is known from Infocom’s games from 1987 onward.

What would you suggest for an interpreter that exposes all 240 globals e.g. for debugging? Just display those memory location contents anyway? Without a count of globals used, there’s no way to tell not all of them are actually globals.

I think you have to.

1 Like

You could still consider the

Technically, they could still be considered globals, but globals which this game isn’t using as globals.

1 Like

As I’m understanding this, interpreters can freely assume there are always 240 variables, games can treat writeable memory as though the global table is smaller, and compilers need to ensure writeable memory is large enough to contain all 240 globals even if the game doesn’t use them.

5 Likes

Theoretically the debugging interpreter could keep track of highest used global when it is reading or writing to the globals.

I don’t now if this is something that qualifies, but some of Infocom’s first, alpha and beta gamefiles has header 0x18 (Abbreviation location) set to 0, This semms to mean (naturally) that no abbreviation table is available and no table is in the gamefile either.

Change contents for 0x18 in the table in §11.1 to something like “Location of abbreviations table (byte address). 0 means that no table is available.”?

Files from obsessive that have 0 at 0x18.

unz -m amfv-first-r1-s841226.z3
unz -m amfv-prealpha-r47-s850313.z4
unz -m generic-r3-s851007.z3
unz -m generic-r5-s870612.z3
unz -m hollywoodhijinx-gamma-r235-s861118.z3
unz -m hypochondriac-r1-s840427.z3
unz -m hypochondriac-r10-s840826.z3
unz -m hypochondriac-r11-s870225.z3
unz -m hypochondriac-r2-s840505.z3
unz -m leathergoddesses-first-r1-s851008.z3
unz -m sampler-1b-r8-s870119.z3
unz -m ziptest-r12-s890607.z6
unz -m ziptest-r13-s890619.z6
unz -m ziptest-r40-s840613.z3
unz -m zork0-first-r0-s870831.z5
2 Likes

The descriptions of loadb, loadw, storeb, and storew should be updated to reflect that the first operand is an unsigned address, while the second is a signed offset. This is apparently the accepted behavior as tested by Praxix.

Edit: If wrapping is done as described below - whether or not the operands are viewed as signed or unsigned is completely irrelevant.

I’d recommend that the loadb, loadw, storeb, and storew opcode descriptions be clarified to indicate the multiplication (in the case of loadw and storew) and the addition always be unconditionally wrapped to a 16-bit unsigned final value. Therefore access beyond 64K is not possible, and the only possible errors resulting from these opcodes are if the final computed address lies outside dynamic memory for storeb and storew, or outside dynamic and static memory for loadb and loadw (which only possible if the story file itself is smaller than 64k).

While the standard could be more explicit, my take is that the “which must lie in static or dynamic memory” requirement effectively prescribes the consensus behaviour already.