Fred's game tech articles

Reverse engineering, retro game archeology and speedrun research

Home

Random Number Generators

Last edited: 2024-06-14

Randomness in games is an important part of making every playthrough different and keeping the player on their toes. But where does it come from? Let's take a look at some algorithms and methods games employ to create randomness.

I will say up-front that I am in no way an expert on randomness, I have just stumbled upon the subject many times while researching games for speedrun and TAS purposes. With that out of the way, let's get to it!

PRNG

Most of these generators are Pseudo-Random Number Generators, or PRNGs for short. What this means is that they are deterministic; they are not truly random. The PRNG state will be self-contained most of the time, but it's also not an uncommon practice to to mix in non-deterministic data to attempt to randomize the state more. Such data might be player input, modifying the state in different ways on value requests, using leftover results from other functions to alter the state, etc...

Quality of randomness

Almost all of these generators are going to fare badly in tests - they simply weren't made for applications like scientific simulations or cryptography. In many cases it was simply not feasible to spend more processing power on randomness because the rest of the game had to run as well, all within a 60th of a second.
Having a high quality source of randomness is of course great, but as long as the source is decent enough and used in clever ways you'd never know the randomness is of "low quality".

There are many ways to test the quality of PRNGs but we're going to keep it simple and just run our generators through PractRand: it's easy to use and prints out a number of tests passed for us to look at. We'll test 226 bytes (64 MBs).

Non-deterministic generators won't have a PractRand result. Generators that have both deterministic and non-deterministic parts (hybrid) are tested without their non-deterministic part.

Randograms

The images below are made by generating pairs of random numbers from the PRNG in question, and using them as coordinates into a black image. The brightness is then increased at the selected coordinate. The images aren't really indicative of randomness quality, but we can still look at them and see if they look about right or if they have clear (non-random looking) patterns.


This one does some fancy rotates and adds, making good use of the carry flag. Decent results for 24 bits of state.

array [u8, 3] state = [0, 0, 0]

u8 Get_prng_value()
{
    //LFSR-style shift
    bool feedback = (state[0] ^ state[1]) & 2
    state >>= 1                //shift entire array right by 1 bit
    state[0] |= feedback << 7  //feedback from taps

    u16 temp = state[0] + 0x47 //u16 to get the carry value in bit 8

    //rotate right with carry
    bool rot_carry = temp & 2
    temp = (temp >> 2) | (temp << 7)

    state[1] = (temp ^ state[1]) + state[2] + rot_carry
    return state[1]
}

Period: 5668926
PractRand tests passed: 48

Games known to use this PRNG:

  • Harvest Moon (1996-08-09)

    Called every frame and on demand.

A 24-bit constant gets added to the 24-bit state, then each byte of the state gets added together. The resulting value gets XOR'ed with another state byte (here called output) and then returned. The carry flag is saved across runs.

There is also a counter involved... maybe the intention was to get the carry flag from the counter? In any case, it doesn't affect the rest of the RNG calculations, and it also doesn't appear to get used by other code as far as I can tell.

The result is pretty good, but... considering that it takes up 48 bits in memory, utilizes 33 and produces a period that fits within 20 bits... well, maybe it could be improved!

This generator generates 50083 values before entering a stable repeating state.

array[u8, 3] state = [0, 0, 0]
u8 counter = 0 //unused!
u8 output = 0
bool state_carry = false

u8 get_prng_value()
{
    counter += 1 //unused: doesn't affect output!

    carrying_add(state[0], 0x25, state_carry)
    carrying_add(state[1], 0x33, state_carry)
    carrying_add(state[2], 0x53, state_carry)

    u8 temp = state[2]
    carrying_add(temp, state_bytes[0], state_carry)
    carrying_add(temp, state_bytes[1], state_carry)

    output ^= temp

    return output
}

void carrying_add(u8 &lhs, u8 rhs, bool &carry)
{
    u16 result = lhs + rhs + carry

    lhs = result & 0xFF
    carry = result >> 8
}

Period: 673294
PractRand tests passed: 27

Games known to use this PRNG:

  • Dungeon Explorer (1989-03-04)

This method has four state variables that are increased at their own rate every frame. When requesting a value, it cycles to the next state variable and adds a constant to it.

The carry flag, which is used for the ADC operation, is set differently every time the Frame_update() function is called. This plus the added request constant makes this generator non-deterministic.

This image was generated by always setting the carry flag to false and simulating one PRNG request per frame.

u16 frame_counter = 0
array[u8, 4] rng_state = (0, 0, 0, 0)
u8 slot = 0
bool carry //cpu carry flag. indeterminate state when entering functions!


u8 Get_prng_value()
{
    slot = (slot + 1) & 3 //increment slot and limit to 0-3
    ADC(rng_state[slot], 0x13)
    return rng_state[slot]
}


void Frame_update() Called every frame
{
    ADC(rng_state[0], 1)
    ADC(rng_state[1], 3)
    ADC(rng_state[2], 5)
    ADC(rng_state[3], 7)

    if(frame_counter & 0xFF == 0) //every 256th frame
    {
        ADC(rng_state[2], rng_state[3])
        ADC(rng_state[1], rng_state[2])
        ADC(rng_state[0], rng_state[1])
    }

    frame_counter += 1
}


void ADC(u8 &dst, u8 src)  //emulating the add-with-carry cpu instruction
{
    u16 result = dst + src + carry
    carry = result & 0x100 //carry flag set if result is bigger than 0xFF
    dst = result           //store 8-bit result in the first operand
}

Period: 91463029 (w/o any Get_prng_value() calls & carry always false on start of update function)
PractRand tests passed: 40

Games known to use this PRNG:

  • Final Fight - SNES (1990-12-21)
  • Breath of Fire (1993-04-03)

This one shares some similarity with their earlier arcade PRNG (and to Quintet's PRNG). A LFSR-style shift is performed on the first element, followed by a series of adds throughout the state (while adding in a unique constant per element).

As far as I can tell, every function that needs randomness has a hardcoded slot it picks a number from, and then adds a constant back to that slot. It's different for every function so it's possible that there are functions that do other types of scrambling, akin to Mega Man 5 (change other slots, combine slot values, etc).

Since this PRNG has unpredictable updates, the image and the PractRand result do not reflect the actual usage so well. The image was generated by calling update_state() and getting the lower byte of rng_state[7] (no constants added on getting the value). There are two PractRand results: one for the lower byte of rng_state[7] (more likely what the game would use) and one for the upper byte (less likely to be used by the game, but should score better in tests).

array rng_state = [u16, 8] initialized to all 0s

void update_state() called every frame
{
    if rng_state[0] == 0
    {
        rng_state[0] = 0x2472
    }
    
    u8 feedback = (rng_state[0] >> 8) ^ self.state[0]) & 2
    rng_state[0] >>= 1
    rng_state[0] |= feedback << 14

    u16 sum = 0

    for(int x = 0; x < 8; ++x)
    {
        constants = [1, 3, 5, 7, 11, 13, 17, 19];

        sum = sum + rng_state[x]
        sum = (sum & 0xFF00) | ((sum + constants[x]) & 0x00FF)
        rng_state[x] = sum
    }
}

u16 get_rng(u8 slot, u16 constant)
{
    this seems to be the most common form of get_rng but can't guarantee it's the only one.
    u16 value = rng_state[slot]
    rng_state[slot] += constant
    return value
}

Period: Unknown. Has 128 bits of state so it's probably long
PractRand tests passed: 31(lower) / 92(upper)

Games known to use this PRNG:

  • Dungeons & Dragons: Tower of Doom (1994-01-13)

Many early Capcom games on the snes started out with a LCG random number generator with m=216, c=0. This setup (c=0) is also known as a Lehmer RNG.
Nothing fancy, but it gets the job done. Note the uniform diagonal patterns in the generated image.

u16 prng_state = 0x01C3

u8 Get_prng_value()
{
    rng_state *= 259
    return rng_state >> 8
}

Period: 214
PractRand tests passed: 0

Games known to use this PRNG:

  • Black Dragon / Black Tiger (1987-08-??)

    Updates every frame.
    Both halves of the state appear to be used.

  • Super Ghouls 'n Ghosts (1991-10-04)

    Called on demand only.
    Used heavily in stage 1, which will "seed" the state.

  • The Magical Quest Starring Mickey Mouse (1992-11-20)

    Called on demand only.
    No attempts at seeding the state.
    Resets the state at every new stage.

  • Aladdin (1993-11-21)

    Called on demand only.
    Frequent prng usage early to "seed" the state.
    Dying resets the state.

This time we have an LCG with m=216, c=15329.

u16 prng_state = 0

u8 Get_prng_value()
{
    rng_state = rng_state * 137 + 15329;
    return rngState >> 8;
}

Period: 216
PractRand tests passed: 1

Games known to use this PRNG:

  • Final Fight 2 (1993-05-22)

    Called on demand only.

At some point, Capcom decided to switch up their PRNG algorithm, but something clearly went wrong here! The algorithm itself is fine - it's actually an improvement over their previous LCG. They just used the wrong half of the rng state as a return value!

u16 prng_state = 0x01C3

u8 Get_prng_value()
{
    x = (rng_state * 3) & 0xFF00
    rng_state = x | u8((x >> 8) + rng_state)
    return rng_state >> 8
}

Period: 43534
PractRand tests passed: 17

Games known to use this PRNG:

  • Super Pang (1992-08-07)

    Called on demand only.

  • Goof Troop (1993-07-11)

    Called on demand only.

  • The Great Circus Mystery Starring Mickey & Minnie (1994-10-??)

    Called on demand only.

Luckily Capcom realized something wasn't quite right and fixed the return value issue. The initial state was also updated. The generated image has much less apparent symmetry compared to the earlier LCG.

u16 state = 0x0D37

u8 get_prng_value()
{
    x = (state * 3) & 0xFF00
    state = x | u8((x >> 8) + state)
    return state
}

Period: 43534
PractRand tests passed: 22

Games known to use this PRNG:

  • Mega Man X (1993-12-17)

    Called every frame and on demand.

  • Demon's Crest (1994-10-21)

    Called every frame and on demand.

  • Mega Man X2 (1994-12-16)

    Called every frame and on demand.

  • Mega Man 7 (1995-03-24)

    Called every frame and on demand.

  • Mega Man X3 (1995-12-01)

    Called every frame and on demand.

While the period is a bit north of 231, it really doesn't perform that well!

Perhaps the biggest issue is that some sequences of numbers are impossible to get.
This is the reason the infamous Howitzer A chip (MMBN1) is so hard to obtain: two numbers (N1 & N2) are generated back-to-back when picking up mystery data and the required values for getting the chip is (N1 % 2 = 0) and (N2 % 16 = 5), which cannot happen!

This algorithm is used in all six main Mega Man Battle Network games from what I've heard, though I've only looked at the first game personally.

u32 state = 0xA338244F

s32 get_prng_value() {
    update_state()
    return state >> 1 //shift out left-most bit: positive signed int
}

void update_state() {
    state = (state << 1) | (state >> 31) rotate left once
    state += 1
    state ^= 0x873CA9E5
}

Period: 2794329874
PractRand tests passed: 50

Games known to use this PRNG:

  • Mega Man Battle Network (2001-03-21)

At first I thought I had stumbled upon another intricate homebrew PRNG, but no! All that complicated-looking assembly code boiled down to a simple LFSR that gets updated several times per call. It allows for setting an upper bound of the returned value, at least!

Reducing the amount of LFSR shifts per call (from 11 to 9) makes this prng pass more PractRand tests, funnily enough.

u16 state = 0x7777

u8 Get_prng_value(u8 bound)
{
    for(x = 0; x < 11; x++)
    {
        bool feedback = (state ^ (state >> 1) ^ (state >> 15) ^ 1) & 1
        state = (state << 1) | feedback
    }

    if bound == 0
    {
        return state as u8 //return lower byte as-is (0 - 255)
    }
    else
    {
        return (bound * (state & 0xFF)) >> 8 //value is capped (0 - bound-1)
    }
}

Period: 216-2
PractRand tests passed: 10

Games known to use this PRNG:

  • Alcahest (1993-12-17)

    Only called on demand.

This appears to be a (Fibonacci) Linear-Feedback Shift Register, or LFSR for short. The taps are bits 57 and 49; bit 0 is not a tap, oddly enough. The period (the amount of updates until the initial state is reached again) is a measly 32767, which could be achieved with a quarter of the bits used and some better chosen taps. Case in point: Mega Man 5 uses the same exact method but with a 32-bit state instead of 64 - the period is identical!

array[u8, 8] rng_state = (1, 0, 0, 0, 0, 0, 0, 0)

void Update_rng() //called many times every other frame
{
    rng_state = rng_state >> 1 //shift entire array right as if it was one 64-bit value
    rng_state[0] |= (rng_state[0] ^ rng_state[1]) << 7 //feedback from taps
}

Period: 215-1
PractRand tests passed: 8

Games known to use this PRNG:

  • Mega Man 5 (1992-12-04)

    4-byte state (32 bits): [0x88, 0, 0, 0] is the initial state, the taps are bits 25 and 17.
    Typically combines different parts of the state on use (for example: result = state[0] + state[1].
    In some cases, the state gets updated with different methods on use (like adding one state byte to another, or even adding outside sources like the frame counter).

  • SOS (1993-05-28)

    From what I've seen, the game appears to mainly read bytes 0-3, sometimes 4-5 and never 6-7.

When a random number is needed, the current drawing position is queried and added to the previous value.

//ppu = picture processing unit
//h_counter = 9-bit value, 0-339
//v_counter = 9-bit value, 0-261 (sometimes 262)

u8 state = 0

u8 rand()
{
    ppu.read_SLHV() //update H/V counters to current drawing position
    u16 temp = state + (ppu.h_counter & 0xFF) //temp bit 8 = carry
    state = temp

    if ppu.v_counter < 256
    {
        state += temp >> 8 //carry from previous add
        state += ppu.v_counter
    }

    return state
}

Games known to use this PRNG:

  • Dragon View (1994-08-26)

    Only called on demand.

When all work is done in a frame, the frame counter gets added continously to the rng state for the rest of the frame. This happens 0-2000 times per frame (roughly) on a NES, depending on the workload.

u8 frame_counter = 0
u8 state = 0

void run_game_frame()
{
    frame_counter += 1

    //game code here...

    //...done running game code for this frame.

    while NMI == false //repeat until a new frame starts
    {
        state += frame_counter
    }
}

Games known to use this PRNG:

  • Castlevania II: Simon's Quest (1987-08-28)

Not sure what this is based on (if anything), but it appears to not have a very long period. It generates 28110 unique values, after which it falls into a stable and short repeating pattern (a period of 9281!). Even then, it looks rather decent considering the short period!

array [u16, 2] rng_state = [0, 0]

void Update_rng()
{
    u32 temp = rng_state[0] << 1
    temp = (temp + rng_state[1] + (temp >> 16)) & 0xFFFF
    temp <<= 1
    temp = (temp & 0xFFFF) + rng_state[0] + (temp >> 16)
    rng_state[0] = temp + 0x3211 + (temp >> 16)
    rng_state[1] ^= rng_state[0]
}

Period: 9281
PractRand tests passed: 24

Games known to use this PRNG:

  • Pocky & Rocky (1992-12-22)
  • Mark Davis' The Fishing Master (1995-06-30)

    Called every frame (except loading frames) and on demand.
    The game appears to mainly (only?) use state[1] for rng: the most significant bit for coinflips, and the least significant bits for most other things.

Quintet employed a quite sophisticated PRNG for the time - the ACORN prng (or bearing a strong resemblance to it anyway). While requiring a bigger state and more execution time to update, the difference certainly shows up on the generated image. Note the black diagonal line: this is due to the zero state prevention that always runs when the PRNG is called.

array[u8, 16] rng_state //16 byte array, initialized to all 0s

u8 Get_prng_value()
{
    bool carry = 0

    for(int x = 15; x > 0; --x)
    {
        u16 result = rng_state[x - 1] + rng_state[x] + carry
        rng_state[x - 1] = result
        carry = result >> 8
    }

    for(int x = 16; x > 0; --x) //zero state prevention
    {
        if(++rng_state[x - 1])
        {
            break
        }
    }

    return rng_state[0] //original code returns a 16 bit value but i strongly suspect it's only using 8 bits
}

Period: Too high to compute, possibly 2128?
PractRand tests passed: 81

Games known to use this PRNG:

  • ActRaiser 1 (1990-12-16)

    Called on demand only.
    Room transitions write the frame counter to state[0-1].

  • Soul Blazer (1992-01-31)

    Called on demand only.

  • ActRaiser 2 (1993-10-29)

    Called on demand only.
    First intended stage "seeds" the rng (player is free to choose any starting stage however).

  • Robotrek (1994-07-08)

    Called on demand only.
    First town frequently calls the prng.
    Screen transitions writes the frame counter to state[2-3].

This generator has a big pre-generated table of values - four runs of 0-255 scrambled together. Rather than just returning a value from the table, it gets multiplied with a value the calling code supplies. This value acts as an upper limit: for example, Get_rng_value(0x67) would return a value in the range 0x00-0x66. While certainly convenient, the quality isn't great.

This image was generated by setting a limit of 192 (0-191) to clearly demonstrate what the limiting value does. Setting the limit lower will make a smaller but denser box, and the opposite with a higher value.

array [u8, 1024] rng_table =
[
    0xA9, 0x39, 0x25, 0xCF, 0xCC, 0x20, 0x80, 0x8E, 0x1F, 0x77, 0x8B, 0x3D, 0xA2, 0x3E, 0x46, 0xDC,
    0x55, 0x75, 0xB1, 0x6B, 0x38, 0x1C, 0xCC, 0xEA, 0x4B, 0x33, 0x97, 0x59, 0x8E, 0xBA, 0x12, 0xB8,
    0x01, 0xB1, 0x3D, 0x07, 0xA4, 0x18, 0x18, 0x46, 0x77, 0xEF, 0xA3, 0x75, 0x7A, 0x36, 0xDE, 0x94,
    0xAD, 0xED, 0xC9, 0xA3, 0x10, 0x14, 0x64, 0xA2, 0xA3, 0xAB, 0xAF, 0x91, 0x66, 0xB2, 0xAA, 0x70,
    0x59, 0x29, 0x55, 0x3F, 0x7C, 0x10, 0xB0, 0xFE, 0xCF, 0x67, 0xBB, 0xAD, 0x52, 0x2E, 0x76, 0x4C,
    0x05, 0x65, 0xE1, 0xDB, 0xE8, 0x0C, 0xFC, 0x5A, 0xFB, 0x23, 0xC7, 0xC9, 0x3E, 0xAA, 0x42, 0x28,
    0xB1, 0xA1, 0x6D, 0x77, 0x54, 0x08, 0x48, 0xB6, 0x27, 0xDF, 0xD3, 0xE5, 0x2A, 0x26, 0x0E, 0x04,
    0x5D, 0xDD, 0xF9, 0x13, 0xC0, 0x04, 0x94, 0x12, 0x53, 0x9B, 0xDF, 0x01, 0x16, 0xA2, 0xDA, 0xE0,
    0x09, 0x19, 0x85, 0xAF, 0x2C, 0x00, 0xE0, 0x6E, 0x7F, 0x57, 0xEB, 0x1D, 0x02, 0x1E, 0xA6, 0xBC,
    0xB5, 0x55, 0x11, 0x4B, 0x98, 0xFC, 0x2C, 0xCA, 0xAB, 0x13, 0xF7, 0x39, 0xEE, 0x9A, 0x72, 0x98,
    0x61, 0x91, 0x9D, 0xE7, 0x04, 0xF8, 0x78, 0x26, 0xD7, 0xCF, 0x03, 0x55, 0xDA, 0x16, 0x3E, 0x74,
    0x0D, 0xCD, 0x29, 0x83, 0x70, 0xF4, 0xC4, 0x82, 0x03, 0x8B, 0x0F, 0x71, 0xC6, 0x92, 0x0A, 0x50,
    0xB9, 0x09, 0xB5, 0x1F, 0xDC, 0xF0, 0x10, 0xDE, 0x2F, 0x47, 0x1B, 0x8D, 0xB2, 0x0E, 0xD6, 0x2C,
    0x65, 0x45, 0x41, 0xBB, 0x48, 0xEC, 0x5C, 0x3A, 0x5B, 0x03, 0x27, 0xA9, 0x9E, 0x8A, 0xA2, 0x08,
    0x11, 0x81, 0xCD, 0x57, 0xB4, 0xE8, 0xA8, 0x96, 0x87, 0xBF, 0x33, 0xC5, 0x8A, 0x06, 0x6E, 0xE4,
    0xBD, 0xBD, 0x59, 0xF3, 0x20, 0xE4, 0xF4, 0xF2, 0xB3, 0x7B, 0x3F, 0xE1, 0x76, 0x82, 0x3A, 0xC0,
    0x69, 0xF9, 0xE5, 0x8F, 0x8C, 0xE0, 0x40, 0x4E, 0xDF, 0x37, 0x4B, 0xFD, 0x62, 0xFE, 0x06, 0x9C,
    0x15, 0x35, 0x71, 0x2B, 0xF8, 0xDC, 0x8C, 0xAA, 0x0B, 0xF3, 0x57, 0x19, 0x4E, 0x7A, 0xD2, 0x78,
    0xC1, 0x71, 0xFD, 0xC7, 0x64, 0xD8, 0xD8, 0x06, 0x37, 0xAF, 0x63, 0x35, 0x3A, 0xF6, 0x9E, 0x54,
    0x6D, 0xAD, 0x89, 0x63, 0xD0, 0xD4, 0x24, 0x62, 0x63, 0x6B, 0x6F, 0x51, 0x26, 0x72, 0x6A, 0x30,
    0x19, 0xE9, 0x15, 0xFF, 0x3C, 0xD0, 0x70, 0xBE, 0x8F, 0x27, 0x7B, 0x6D, 0x12, 0xEE, 0x36, 0x0C,
    0xC5, 0x25, 0xA1, 0x9B, 0xA8, 0xCC, 0xBC, 0x1A, 0xBB, 0xE3, 0x87, 0x89, 0xFE, 0x6A, 0x02, 0xE8,
    0x71, 0x61, 0x2D, 0x37, 0x14, 0xC8, 0x08, 0x76, 0xE7, 0x9F, 0x93, 0xA5, 0xEA, 0xE6, 0xCE, 0xC4,
    0x1D, 0x9D, 0xB9, 0xD3, 0x80, 0xC4, 0x54, 0xD2, 0x13, 0x5B, 0x9F, 0xC1, 0xD6, 0x62, 0x9A, 0xA0,
    0xC9, 0xD9, 0x45, 0x6F, 0xEC, 0xC0, 0xA0, 0x2E, 0x3F, 0x17, 0xAB, 0xDD, 0xC2, 0xDE, 0x66, 0x7C,
    0x75, 0x15, 0xD1, 0x0B, 0x58, 0xBC, 0xEC, 0x8A, 0x6B, 0xD3, 0xB7, 0xF9, 0xAE, 0x5A, 0x32, 0x58,
    0x21, 0x51, 0x5D, 0xA7, 0xC4, 0xB8, 0x38, 0xE6, 0x97, 0x8F, 0xC3, 0x15, 0x9A, 0xD6, 0xFE, 0x34,
    0xCD, 0x8D, 0xE9, 0x43, 0x30, 0xB4, 0x84, 0x42, 0xC3, 0x4B, 0xCF, 0x31, 0x86, 0x52, 0xCA, 0x10,
    0x79, 0xC9, 0x75, 0xDF, 0x9C, 0xB0, 0xD0, 0x9E, 0xEF, 0x07, 0xDB, 0x4D, 0x72, 0xCE, 0x96, 0xEC,
    0x25, 0x05, 0x01, 0x7B, 0x08, 0xAC, 0x1C, 0xFA, 0x1B, 0xC3, 0xE7, 0x69, 0x5E, 0x4A, 0x62, 0xC8,
    0xD1, 0x41, 0x8D, 0x17, 0x74, 0xA8, 0x68, 0x56, 0x47, 0x7F, 0xF3, 0x85, 0x4A, 0xC6, 0x2E, 0xA4,
    0x7D, 0x7D, 0x19, 0xB3, 0xE0, 0xA4, 0xB4, 0xB2, 0x73, 0x3B, 0xFF, 0xA1, 0x36, 0x42, 0xFA, 0x80,
    0x29, 0xB9, 0xA5, 0x4F, 0x4C, 0xA0, 0x00, 0x0E, 0x9F, 0xF7, 0x0B, 0xBD, 0x22, 0xBE, 0xC6, 0x5C,
    0xD5, 0xF5, 0x31, 0xEB, 0xB8, 0x9C, 0x4C, 0x6A, 0xCB, 0xB3, 0x17, 0xD9, 0x0E, 0x3A, 0x92, 0x38,
    0x81, 0x31, 0xBD, 0x87, 0x24, 0x98, 0x98, 0xC6, 0xF7, 0x6F, 0x23, 0xF5, 0xFA, 0xB6, 0x5E, 0x14,
    0x2D, 0x6D, 0x49, 0x23, 0x90, 0x94, 0xE4, 0x22, 0x23, 0x2B, 0x2F, 0x11, 0xE6, 0x32, 0x2A, 0xF0,
    0xD9, 0xA9, 0xD5, 0xBF, 0xFC, 0x90, 0x30, 0x7E, 0x4F, 0xE7, 0x3B, 0x2D, 0xD2, 0xAE, 0xF6, 0xCC,
    0x85, 0xE5, 0x61, 0x5B, 0x68, 0x8C, 0x7C, 0xDA, 0x7B, 0xA3, 0x47, 0x49, 0xBE, 0x2A, 0xC2, 0xA8,
    0x31, 0x21, 0xED, 0xF7, 0xD4, 0x88, 0xC8, 0x36, 0xA7, 0x5F, 0x53, 0x65, 0xAA, 0xA6, 0x8E, 0x84,
    0xDD, 0x5D, 0x79, 0x93, 0x40, 0x84, 0x14, 0x92, 0xD3, 0x1B, 0x5F, 0x81, 0x96, 0x22, 0x5A, 0x60,
    0x89, 0x99, 0x05, 0x2F, 0xAC, 0x80, 0x60, 0xEE, 0xFF, 0xD7, 0x6B, 0x9D, 0x82, 0x9E, 0x26, 0x3C,
    0x35, 0xD5, 0x91, 0xCB, 0x18, 0x7C, 0xAC, 0x4A, 0x2B, 0x93, 0x77, 0xB9, 0x6E, 0x1A, 0xF2, 0x18,
    0xE1, 0x11, 0x1D, 0x67, 0x84, 0x78, 0xF8, 0xA6, 0x57, 0x4F, 0x83, 0xD5, 0x5A, 0x96, 0xBE, 0xF4,
    0x8D, 0x4D, 0xA9, 0x03, 0xF0, 0x74, 0x44, 0x02, 0x83, 0x0B, 0x8F, 0xF1, 0x46, 0x12, 0x8A, 0xD0,
    0x39, 0x89, 0x35, 0x9F, 0x5C, 0x70, 0x90, 0x5E, 0xAF, 0xC7, 0x9B, 0x0D, 0x32, 0x8E, 0x56, 0xAC,
    0xE5, 0xC5, 0xC1, 0x3B, 0xC8, 0x6C, 0xDC, 0xBA, 0xDB, 0x83, 0xA7, 0x29, 0x1E, 0x0A, 0x22, 0x88,
    0x91, 0x01, 0x4D, 0xD7, 0x34, 0x68, 0x28, 0x16, 0x07, 0x3F, 0xB3, 0x45, 0x0A, 0x86, 0xEE, 0x64,
    0x3D, 0x3D, 0xD9, 0x73, 0xA0, 0x64, 0x74, 0x72, 0x33, 0xFB, 0xBF, 0x61, 0xF6, 0x02, 0xBA, 0x40,
    0xE9, 0x79, 0x65, 0x0F, 0x0C, 0x60, 0xC0, 0xCE, 0x5F, 0xB7, 0xCB, 0x7D, 0xE2, 0x7E, 0x86, 0x1C,
    0x95, 0xB5, 0xF1, 0xAB, 0x78, 0x5C, 0x0C, 0x2A, 0x8B, 0x73, 0xD7, 0x99, 0xCE, 0xFA, 0x52, 0xF8,
    0x41, 0xF1, 0x7D, 0x47, 0xE4, 0x58, 0x58, 0x86, 0xB7, 0x2F, 0xE3, 0xB5, 0xBA, 0x76, 0x1E, 0xD4,
    0xED, 0x2D, 0x09, 0xE3, 0x50, 0x54, 0xA4, 0xE2, 0xE3, 0xEB, 0xEF, 0xD1, 0xA6, 0xF2, 0xEA, 0xB0,
    0x99, 0x69, 0x95, 0x7F, 0xBC, 0x50, 0xF0, 0x3E, 0x0F, 0xA7, 0xFB, 0xED, 0x92, 0x6E, 0xB6, 0x8C,
    0x45, 0xA5, 0x21, 0x1B, 0x28, 0x4C, 0x3C, 0x9A, 0x3B, 0x63, 0x07, 0x09, 0x7E, 0xEA, 0x82, 0x68,
    0xF1, 0xE1, 0xAD, 0xB7, 0x94, 0x48, 0x88, 0xF6, 0x67, 0x1F, 0x13, 0x25, 0x6A, 0x66, 0x4E, 0x44,
    0x9D, 0x1D, 0x39, 0x53, 0x00, 0x44, 0xD4, 0x52, 0x93, 0xDB, 0x1F, 0x41, 0x56, 0xE2, 0x1A, 0x20,
    0x49, 0x59, 0xC5, 0xEF, 0x6C, 0x40, 0x20, 0xAE, 0xBF, 0x97, 0x2B, 0x5D, 0x42, 0x5E, 0xE6, 0xFC,
    0xF5, 0x95, 0x51, 0x8B, 0xD8, 0x3C, 0x6C, 0x0A, 0xEB, 0x53, 0x37, 0x79, 0x2E, 0xDA, 0xB2, 0xD8,
    0xA1, 0xD1, 0xDD, 0x27, 0x44, 0x38, 0xB8, 0x66, 0x17, 0x0F, 0x43, 0x95, 0x1A, 0x56, 0x7E, 0xB4,
    0x4D, 0x0D, 0x69, 0xC3, 0xB0, 0x34, 0x04, 0xC2, 0x43, 0xCB, 0x4F, 0xB1, 0x06, 0xD2, 0x4A, 0x90,
    0xF9, 0x49, 0xF5, 0x5F, 0x1C, 0x30, 0x50, 0x1E, 0x6F, 0x87, 0x5B, 0xCD, 0xF2, 0x4E, 0x16, 0x6C,
    0xA5, 0x85, 0x81, 0xFB, 0x88, 0x2C, 0x9C, 0x7A, 0x9B, 0x43, 0x67, 0xE9, 0xDE, 0xCA, 0xE2, 0x48,
    0x51, 0xC1, 0x0D, 0x97, 0xF4, 0x28, 0xE8, 0xD6, 0xC7, 0xFF, 0x73, 0x05, 0xCA, 0x46, 0xAE, 0x24,
    0xFD, 0xFD, 0x99, 0x33, 0x60, 0x24, 0x34, 0x32, 0xF3, 0xBB, 0x7F, 0x21, 0xB6, 0xC2, 0x7A, 0x00,
]

u16 offset = 0

u8 Get_rng_value(u8 multiplicand)
{
    offset = offset + 1 & 0x03FF
    return (multiplicand * rng_table[offset]) >> 8
}

Period: 210
PractRand tests passed: 0

Games known to use this PRNG:

  • Syvalion - SNES (1992-07-24)

    Updates the offset every frame in the menu.

Perhaps not the best PRNG, but it sure creates a funky image!

array [u8, 2] state = [0, 0]

u8 Get_prng_value()
{
    state[0] = rotate_left(state[0], 1)
    u16 temp = state[0] + 0x4E + (state[0] & 1) //add with carry
    state[0] = temp & 0xFF                      //^
    carry = temp & 0x100                        //^
    state[0] ^= 0x3A
    state[1] = (state[0] ^ state[1]) + 0xC3 + carry

    return state[1]
}

Period: 12800
PractRand tests passed: 0

Games known to use this PRNG:

  • Super Smash TV (1992-02-??)

As consoles and the tools around them advanced, the need to write everything from scratch decreased. Most games around the PS1 / N64 era were likely written in C for example, which typically gave you access to some form of the C standard library: pre-written general purpose code so you didn't have to write it yourself. And yes, there's a rand() function in there!

While rand() can be implemented in different ways as long as it fills some certain criterias, I think it's fair to say that most implementations, especially at that time were all the same: a LCG with a 231 period. The return value is the upper half of the state (minus the sign bit to stop careless devs from making mistakes, I'm sure), as the quality of the lower bits of a LCG are typically very poor.
By today's standards it would probably not seem so good, but compared to most earlier PRNGs we've seen it is pretty darn good!

As this was (is?) the standard rand() function in most C libraries, this PRNG has probably been used many, many times in countless games.

u32 state = 0

u16 Get_prng_value()
{
    state = state * 1103515245 + 12345;
    return (state >> 16) & 0x7FFF
}

Period: 231
PractRand tests passed: 26

Games known to use this PRNG:

  • Magical Tetris Challenge, PS1 (1999-03-18)

    Called twice during the intro, which effectively sets the starting state to 6.