Yeah, it was wrong. But now it isn’t any more!
So the random number generator in EF2000 is pretty simple. It has a state of 2×16 bits:
struct RNG {
uint16_t a;
uint16_t b;
};
It is usually initialized with π as a binary-coded decimal:
RNG foo = { 0x3141, 0x5926 };
The function to generate the next number is extremely easy in assembler – just two instructions:
ADD rng.a, rng.b
ADC rng.b, rng.a
This is, however, add-with-carry-on-overflow on 16-bit numbers, so it’s hard to write in C (or any other language for that matter). Let’s define a function
bool addWithCarry(uint16_t * result, uint16_t a, uint16_t b, bool carryIn). It
- adds a and b
- adds 1 if carryIn == true (i.e. carry flag set on entry)
- stores the sum in result and
- returns true iff the addition was overflowing (i.e. the carry flag is set on exit).
Then the function body becomes
// Returns the next pseudorandom number.
uint16_t next(RNG * rng) {
uint16_t a = rng->a;
uint16_t b = rng->b;
addWithCarry(&b, b, a, addWithCarry(&a, a, b, false));
rng->a = a;
rng->b = b;
if(0 == (a | b)) { // Burnt through all entropy? Reset!
rng->a = 0x3141;
rng->b = 0x5926;
}
return a;
}
The implementation of
addWithCarry() can be as simple and inefficient or as complex and efficient as you want it to. I use the
_addcarry_u16() intrinsic on MSVC and the code actually compiles to the same
ADD-ADC chain found in the disassembly.
Here is a test case for verification: After
next(foo), the state should have changed to
{ 0x8a67, 0xe38d } and then to
{ 0x6df4, 0x5182 }.
I compared the result with one of damson’s gameplay videos and the little islands now match perfectly.
Of note: This pseudorandom number generator is also used by TAW to encrypt the pilot log, and by ADF to encrypt the Quick Combat high score. And it is used in many more places.
If we search old EXEs for the magic numbers
0x3141 and
0x5926, I’m sure we will find many more uses of it.
Needless to say, this will be in the next update.