dirkwhoffmann / virtualc64

VirtualC64 is a cycle-accurate C64 emulator for macOS
https://dirkwhoffmann.github.io/virtualc64
Other
342 stars 33 forks source link

Replace all calls to rand() by calls to a custom randomizer #788

Closed dirkwhoffmann closed 1 month ago

dirkwhoffmann commented 4 months ago

VirtualC64 calls rand() at multiple places. Although the returned sequences are deterministic, this causes issues in run-ahead mode, since both instances would query different numbers and diverge.

TODO: Implement a new Randomizer class that returns a deterministic pseudo-random sequence and instantiate this class in both the main instance and the run-ahead instance.

Fun fact: Bugs related to run-ahead mode result in funny behavior due to diverging emulator instances. In Barry McGuigan World Championship Boxing, I've thought to have knocked out my opponent.

Bildschirmfoto 2024-03-05 um 17 38 20

However, this only happened in the run-ahead instance. After the next resync, I've been lying on the ground and lost the fight.

dirkwhoffmann commented 4 months ago

An easy solution is to derive the pseudo-number directly from the current CPU cycle:

u32
C64::random()
{
    return random(u32(cpu.clock));
}

u32
C64::random(u32 seed)
{
    // Parameters for the Linear Congruential Generator (LCG)
    u64 a = 1664525;
    u64 c = 1013904223;
    u64 m = 1LL << 32;

    // Apply the LCG formula
    return u32((a * seed + c) % m);
}

random() can be used as long as there is only one call per cycle. Otherwise, the same value would be returned twice.

Arrays such as colorRam can be initialized as follows:

        // Initialize color RAM with random numbers
        u32 seed = 0;
        for (isize i = 0; i < isizeof(colorRam); i++) {

            seed = c64.random(seed);
            colorRam[i] = u8(seed);
        }
dirkwhoffmann commented 1 month ago

Fixed in v5.0b1