mupen64plus / mupen64plus-core

Core module of the Mupen64Plus project
1.32k stars 258 forks source link

RNG Broken #361

Open AmbientMalice opened 7 years ago

AmbientMalice commented 7 years ago

This is essentially the same issue that Project 64 has, as pointed out here https://github.com/project64/project64/issues/1290 But it only occurs in PJ64 with Fixed Audio Timing.

I've tested mupen64plus, and in SSB64, the Master Hand pulls the same character out of the box every time, and in Episode I - Racer, the intro always shows Sebulba instead of Anakin. Changing CF settings and switching between interpreter and recompiler does seem to have some impact, but only in the choice of character in SSB64. The character will be the same every time, but changing the settings seems to affect the character.

loganmc10 commented 7 years ago

This is probably easily fixed. Do you know (or is there any documentation) on how the RNG is calculated in SSB64? Like is it using the value of AI_LEN_REG or something?

When we figure out what value it's using for the RNG, we can probably just rand() it a little bit to create the RNG effect. On a real N64, the speed of the DMA would vary a bit every time, just like a file copy in Windows isn't going to take the exact same time every attempt. But in the emulator, it is emulating a "perfect" DMA each time, so the RNG is broken.

loganmc10 commented 7 years ago

So yeah, changing the length of the interrupt does affect the character that comes out of the box in SSB64.

To really get this right, we might need some help from some ROM hackers or the TAS community. We need to know what it's looking for. If I make it "a little random", then it'll switch (randomly) between Fox and Pikachu. If I make it a little more random, then Samus and Yoshi will appear sometimes. I thought other characters could come out like DK, but I never saw him. We can't just make it wildly random. We'll need to figure out what the "random range" should be.

loganmc10 commented 7 years ago

@AmbientMalice do you know the best place to get in contact with someone that might be able to figure out (or already knows) what Smash Bros uses to pick the character? I'd really like to know for sure what it is doing before I try to come up with a solution.

@Isotarge perhaps you know? Or know someone? I'd like to know what input Smash Bros is using to determine which character to pull out of the box in the opening sequence. We know it's related to the audio, but I'd like to know how it is using the audio information.

Isotarge commented 7 years ago

I did some research to make a start on this, you can change which character comes out of the box on the US version by modifying the 32 bit value at 80134CF8 and change the second character by modifying the 32 bit value at 80134CFC. In the emulator I'm using (BizHawk) Mario is always pulled from the box as far as I can tell and Kirby appears second.

The important write to this value is 80134300: SW v0, 0x4CF8(at), if we set a breakpoint slightly before that we see a call to 80134270 which in turn calls 80018A30. According to this research by @abitalive the function located at 80018A30 is RandomInt. Stepping through this code it appears to read from the COUNT register and then perform some arithmetic to boil that value down to a random int to write to 80134CF8.

I hope this is enough for someone with more experience with N64 hardware to start investigating things.

loganmc10 commented 7 years ago

Thanks @Isotarge that's actually really helpful. I had MFC0 add a random number between 0-1000 to the value the COUNT register when it was read (but didn't modify the actual value, so the interrupts will still be timed correctly). With this change, I got every character to come out of the box! In messing with the audio timing, I could never get every character to appear, plus, if I made the range too wide, the audio would start to crackle.

It didn't seem to harm anything in Super Smash, but in DK64, the "Nintendo" logo at the beginning only shows up for a split second, where it should show up for longer than that. I tried a random number between -500 and 500 (instead of 0-1000), and DK64 wouldn't even boot most the time (Smash still seemed fine).

I'd rather not end up with a game specific hack for this, and it seems like messing with the COUNT register is probably a no-go given how many issues it would probably create. I'm hoping somewhere else in the arithmetic is a value we can possibly modify. If the COUNT register is the only input variable it uses in the equation, it might be hard to solve.

At the very least, if we knew a good "random range" for the COUNT register, we could potentially add a per-game setting for the games that use it, after doing a fair bit of testing in those games to make sure no issues arise.

EDIT: Or maybe if we just start the COUNT register off at a random value, and then just carry on normally after that.... I'll have to do some more testing over the next few days.

loganmc10 commented 7 years ago

I haven't forgotten about this.. I tested seeding the COUNT register with a random value, it doesn't work. I think the game might wait for a certain COUNT value or something, like I can seed it to 5000 or 50000, but it doesn't seem to make a difference. I assume the game might have some kind of "wait until COUNT = 500000" loop or something, so it doesn't matter what you seed it to.

People have always assumed that you need random audio for this to work, but that's not the case. You just need random DMA timing in general. Randomising the AI or VI interrupt timing doesn't seem like a good idea, since it can affect the audio quality.

I've been testing randomising the SI interrupt timing with great success. I can get random players out of the box in Smash Bros without any issues with audio quality. I need to do a lot more testing, but I think randomising the SI interrupt seems like the safest way to generate randomness in the COUNT register

loganmc10 commented 7 years ago

I can also get both Sebulba and Anakin in the Epi I Racer intro, though Sebulba seems far more likely. I'll probably need to do a lot of tweaking to try and get the distribution to be truly random

Isotarge commented 7 years ago

Does this solution break core determinism? If so, it would be great to have a toggle for this timing randomization to keep the core viable for netplay & TAS purposes.

loganmc10 commented 7 years ago

Yeah it does, we'll probably add a core option. You can still have randomness and netplay, you just need to use the same seed. Right now it uses the current time, so that doesn't work for netplay, but you can just share a seed and then use that for netplay.

TAS purposes.

Like I said, I'm sure we'll add an option, but a real N64 is random, shouldn't TAS runs be able to deal with the randomness?

Isotarge commented 7 years ago

You’re absolutely right that TAS should be able to deal with some degree of randomness, but the only source of randomness that we can reliably deal with are software PRNG implementations running on deterministic cores.

Attempting to deal with every possible cause of non-determinism in a system eg. corroded contacts, dirty power, RF interference, temperature, or solar flares is somewhat outside of our scope when creating the runs in an emulated system.

loganmc10 commented 7 years ago

This should be resolved now, the build on https://m64p.github.io has been updated

loganmc10 commented 7 years ago

I tested EP 1 Racer and Smash Bros, it would be nice if somebody could test multiplayer Mario Kart. I heard that had some RNG issues as well but I don't know exactly what to test or what the expected outcome is