Lack of randomness sometimes when compiling for Glulx

There’s another compiler issue with I7 Version 10+, in that inclusions of the form:

To decide which number is todays number: (- random(1,2,3,4,5,6,7,8,9) -).

fox the I6->Inter compiler, producing the error

inform7: this inv of !random should have 1 argument(s), but has 9

Conversely,

Include (-
[ TodaysNumber;
return random(1,2,3,4,5,6,7,8,9);
];
-).

compiles, although as noted previously it’s unhelpfully modified ‘in transit’ from I6->Inter->I6 to have just one parameter:

Include (-
[ TodaysNumber;
return random(1);
];
-).

One could strongly argue that the former behaviour (i.e. throwing a compiler error) is the ‘correct’ one, if the form random(a, b, …, n) is to be disallowed.

Either way, that does make it slightly simpler as no one is currently making use of the multi arg form. Unless they were in 6M62 and just don’t realise they’re getting a distorted result now?

You’ve probably seen this.

It highlights two of the difficulties of substituting for the inbuilt random(a1, a2, a3, …aN) function for the Z-machine:

i) the Z-machine appears not to have a good way of knowing how many arguments a function has been passed
ii) the compiler treats the inbuilt random() specially by allowing it more than 7 arguments

Fortunately, as far as we know the extant Z-machine interpreters don’t have a RNG problem, so the inbuilt random() function can be left as is for Z-machine compilation.

Q. would the Glulx compiler allow for an indefinitely large number of arguments to be provided for a replacement random() function?

No, I’d never seen that library!

You should be able to use @check_arg_count in a loop to determine how many args a function was called with.

Z-Machine terps may have some kind of RNG issue, based on the legacy priming I7 does. I don’t think anyone’s ever bothered checking whether we could safely remove that now.

Yes, I don’t think there are any limits to how many arguments can be given to Glulx functions.

Going back to the interpreter question!

I now have Glulxe set up this way:

For the seeded case (nonzero seed), we use “xoshiro128**” (via here). We convert the 32-bit Glulx seed to a 128-bit xoshiro seed using “SplitMix32” code that I dug out of, oh, somewhere on the Internet; I’m not proud. Or tired.

For the seedless case:

  • Windows: use rand_s() (thanks David).
  • Mac: use arc4random() (this is always available on MacOS, and it’s the recommended API for this purpose. Apple updates the algorithm to keep up with the state of the art).
  • Unix: here we have to rely on #defines.
    • #define UNIX_RAND_ARC4 calls arc4random(), available on BSD Unixes.
    • #define UNIX_RAND_GETRANDOM uses “xoshiro128**” seeded from a getrandom() call, which is available on modern Linux.
    • otherwise we use “xoshiro128**” seeded from a time(NULL) call.

This should give us good results on the vast majority of platforms.

This is branch https://github.com/erkyrath/glulxe/tree/new-rng. Unless somebody sees a problem, I’m going to merge that to main, let people do a bit of field testing, and then set up a new Glulxe release with it.

I am also thinking about adjusting Quixe to use the same “xoshiro128**” generator for the seeded case. (It already makes the same seeded/nonseeded distinction that I’ve added to Glulxe.) But, separate task.

6 Likes

Attached is a Windows test build with Zarf’s latest code, in case that helps anyone testing.
glulxe_new_rng.zip (976.2 KB)

2 Likes

My question was more specific: does the maximum number of arguments have to be known at compile-time, or can it be left open-ended (as seems to be the case with the inbuilt random() function)?

EDIT: I know that it’s possible to call a function with more arguments than it’s expecting, but I think any ‘extras’ are simply discarded

Vararg functions can have any number of arguments passed on the stack.

2 Likes

If you haven’t been obsessively following Github (and the new Github “feed” makes that painful) then the news is that there are new releases of Glulxe and Git with the xoshiro128** RNG algorithm, and both have also been updated in Windows Glulxe and Windows Git.

Today I’ve also updated Emglken with the new VMs and published it at npm. Parchment also has the new VMs, though it uses Quixe by default so they won’t make a difference for typical users.

2 Likes

Ah, thanks – I always forget to announce these things.

I’m preparing a release of Quixe that uses the same algorithm. (I got it working last week, but Dannii pointed out a improvement which I haven’t gotten to yet. Maybe today.)

The hard part will be updating my hands-free Adventure walkthrough, which was written to rely on the old RNG. :) (That is, it’s got GET AXE / THROW AXE AT DWARF commands at exactly the points when the old RNG tossed a dwarf at you.)

@DavidK No rush, but any chance you could build a suitable replacement for the Glulxe.exe in the Inform Interpreters folder?

I’ve rebuilt Glulxe as used in the Windows front-end, and attach it here. Just unzip it and drop it into the Interpreters directory.

Updated on 16th October: Peter has pointed out to me that the original attachment to this message didn’t actually work. This was does, I hope: windows_inform7_glulxe.zip (232.2 KB)

2 Likes

Perhaps this should be a new thread, but what is the preferred way to do deterministic non-randomness in Glulxe? When randomness is switched off in the interpreter, how do I make sure that the sequence of “random” numbers is the same every time I restore a game?

What you’d have to do is replace the built-in I6 random() function, with one that had a global counter incorporated into it that logged the number of calls to the RNG as the game progressed. That count would then be saved when a game was saved and restored when it was subsequently restored. After a restore, you would need to seed the RNG with your original seed then call the RNG the number of times in the counter to ‘bring it up to date’ then set the counter back again to where it was at the point of restore.

1 Like

That wouldn’t work with current, existing games and saves, would it? It is explicitly stated in the Glulx specification that the random number generator’s internal state is not included in the save-game format. (By the way, I think that random seed and the number of random calls really should be included in the standard Quetzal save-game format, as it is very useful for testing and tracking down bugs.)

Currently, I do what you suggest in Spatterlight for autosaves. I’ve hacked Glulxe to keep track of the random seed used and the number of calls to glulx_random() since the setting of the seed. I save these in the autorestore state, and on autorestore I seed the generator with the saved seed and call glulx_random() the saved number of times. However, it would be nice if there was an “official”, less hacky way to do this.

I don’t think you could usefully include RNG state in the save file unless the spec were to also prescribe the use of a particular RNG algorithm. You’d wind up with save files that aren’t fully portable between interpreters/systems.

(In theory, anyways. In practice, Zarf controls the specs as well as all three relevant implementations, so he could just make it so if the demand is there.)

1 Like

No. I doubt there is a way of doing that without hacking the interpreter, like you have done.

1 Like

That doesn’t really matter, as long as they are useful on one particular system and/or interpreter. Ideally, all that happens if you move the save file to an incompatible system is that the game is no longer deterministic. It would still be an improvement on the current state of things.

If you need it to be consistent you’d need to implement your own RNG system, like the Xorshift extension.

1 Like

I just noticed that the Xorshift seed system never made it into a release of Kerkerkruip. That’s so sad!