Z9/Z11 - proposal for a new z-machine format [an intellectual exercise]

True, but you could do this with a routine consisting of a single print_ret as well.

The advantage of always having them be routines is they can contain arbitrary additional logic. The disadvantage of course is the two bytes of overhead will lead to additional padding waste part of the time.

Dave

print_ret adds a newline at the end though.

2 Likes

an XOR opcode would be great to have.

I’d like some support for graphics.

For making larger games, I suggest having the multiplier for routines and strings stored in the header (e.g. repurpose bytes at $38..$3F for new info). Perhaps require routine/string values to have their top bit set, or the top two bits distinguish the kind of value.

2 Likes

Is this because it is to much work or too much overhead to do it when there is no built in operator?

XOR could be handy to make certain algorithms faster on retro hardware.

Whether it’s useful enough to include here? I’m not sure. Just one of those things that to me feels like it should be there.

Agreed – with a real xor it would be a lot easier to explicitly code the random function and be able to optionally guarantee consistency between interpreters. My own interpreters (6502 and C++) use the same algorithm that was recommended in some forum thread here years ago.

random code

static uint16_t random_seed = 1;

uint16_t randomNumber(uint16_t &state) {
/* WARNING: State must never be initialized to 0 */
state ^= state << 7;
state ^= state >> 9;
state ^= state << 8;
return state;
}

uint16_t rangedRandom(uint16_t range) {
uint16_t value = (randomNumber(random_seed) * range) >> 16;
return value + 1;
}

True. So you’d need a third byte for an rfalse. Unless, of course, it was the fairly common case where you did want a newline there anyway :slight_smile:

-Dave

Come to think of it, we wouldn’t even need an unsigned comparison opcode if the compiler put packed strings at the very end (for example). For a V8 game smaller than 256k, @jg would work to disambiguate routines from packed strings, and @jl with a negative value would work for games larger than 256k.

(This depends on where the split point between routines and packed strings really is though; there could be corner cases where packed strings “straddle” the point where a positive address becomes negative, and that would need some extra handling. Or, just insert some padding in the story in that case, I guess)

PunyInform handles this just about as efficiently as possible. Search for e.g. IsAString in puny.h if you’re interested.

Still, having assembly instructions for unsigned compare would be even better.

Maybe remodel the size and number part of the properties table so it can 1, 2, 3 or maybe more bytes. So we can:

  • have more properties per object
  • have more data per property
  • indicate that the data of this property is stored in static memory

Having three dictionaries with 2, 4 and 6 bytes for the word wouldn’t add to much complexity when matching because I guess the input would indicate what dictionary to use to search for match, right?

Another alternative could be to use hash values for words instead of the letters, like NAIL does. I don’t know what size is needed for hash values to avoid different words having the same hash value, though.

I’m wary of using hash codes, particularly on larger games, but it’s clear they can be made to work.

For multiple dictionaries, for the worse case, you’d invoke tokenise multiple times across multiple dictionaries:

If a non-zero dictionary is supplied, it is used (if not, the ordinary game dictionary is). If the flag is set, unrecognised words are not written into the parse buffer and their slots are left unchanged: this is presumably so that if several tokenise instructions are performed in a row, each fills in more slots without wiping those filled by the others.

The interpreter would also have to be smart enough to handle different sized dictionarie, perhaps based on the “entry length” (normally the dictionary size is a property of the story file).

Having said that… it’s probably a lot of complexity for little gain unless your vocabulary stretched into thousands of words.

If tokenize in itself can handle a dictionary with three sections of different word sizes, the complexity is only added in the interpreter.

So if we end up with a 1.2 standard (which I imagine we will, now that multiple interpreters support arc_image), it sounds like we want opcodes to:

  • Protect a block of memory from restart/restore/undo
  • Swap the value of two variables by reference
  • Compare numbers unsigned (jgu, jlu)
  • Deal with non-BMP Unicode characters
  • Jump to an absolute computed address
  • XOR
  • Draw images (already in the works)

Plus a few little improvements to other areas of the Z-machine:

  • Make the interpreter reprint the input text after a timeout, not the game
  • Let the Unicode translation table use non-BMP characters (via a different header extension word number, that’s probably the easiest way)
  • Allow longer property data by repurposing bit 6

These seem like things we’d want in versions 3 through 8, not just 11.

Then, the things that actually need a fundamental change to the memory model, meaning a Z11 instead of a standard 1.2:

  • A second block of “extended RAM” accessed via different opcodes
  • A larger shift value for strings and routines (customizable, stored in the header?), or, alternately
  • Replacing the packed address system with a jump table

Does this sound like an accurate summary?

1 Like

A long-term lesson I’ve learned: proposed Z-machine expansions sink or swim at the point of terp implementation. The vast majority of ideas are met with “It’s a mature format, any expansions will only run on a subset of Z-machines (if any), so fragmenting the ecosystem is a bigger harm than not having a XOR opcode.” etc.

Conversely, a proposal becomes Standard when a critical mass of terp developers actually want to support it. z8 memory model? Unicode? Lots of the early terp developers were on board, so it worked.

arc_image is a recent oddity that proves the rule. Lots of people have said “I wish z5 had images”; nothing happened until Stefan Vogt (1) came up with a well-thought-out proposal, (2) released multiple terps that supported it, (3) released a whole compiler that emitted it, and (4) got two of the most active terp developers on board (Chris Spiegel, of the flagship Bocfel, and Shawn Sijnstra, of… like… every Z80 retro platform ever made).

TL;DR intellectual exercises aside, the future is actually set by Developers and Lobbyists.

2 Likes

I’m wary of this idea. Those opcodes have very specific and expected behavior. Subverting them seems problematic. Save/restore can already be used separately on a block of memory written to a separate file.

It seems you could get the same effect by just launching the new game and having it read the saved data (not a full save state) from the previous game.

Likewise, xor can be implemented (in C syntax) via (a & ~b) | (b & ~a) which is five Z machine instructions, so maybe that one isn’t really worth the trouble?

EDIT - Read the wikipedia article on xor a bit more carefully, and it can actually be done in four instructions - return (a | b) & ~(a & b);

I agree with this logic, I think. More widespread support for the extended save/restore opcodes is probably safer.

Or distinct distinct file writing opcodes that are more flexible.

How about something like an option where the save and restore logic is limited to a “family” of games, and the filename is generated from all but (say) the last two digits of the IFID? Likewise, any other filenames are encoded against the full IFID. That way it’s basically impossible for filenames between different games in the same directory to conflict, but it’s still possible to (optionally) share data between multiple games.

My worry, being, of course, that if interpreters gain the ability to read and write “arbitrary” files things are going to need to be heavily sandboxed to avoid security issues. But also, we still want the ability for related games to share data.