11 March 2016 - Forum Rules

Main Menu

PS1 (MIPS) random number generation?

Started by weissvulf, February 08, 2015, 07:02:34 PM

Previous topic - Next topic


I'm trying to make a function to generate a random number between 1-100 for a PS1 game. Below is the best I've come up with, but it still seem to return patterned value ranges rather than random.  Does anyone have a better random generator, or advice on fixing this one?

lui r2,0x8001  ;Load seed
lw r2,0x7818(r2)  

li r3, 0x41c64e6d   ;Processing for quasi-random generation
mult r2,r3
mflo r2
addiu r2,r2,0x3039

lui r3,0x8001          ;store new seed
sw r2,0x7818(r3) 

srl r2,r2,0x10        ;More processing

andi r2,r2,0x3FFF ;mask limit to 1023
addiu r3,r0, 11       
divu r2,r3             ;divide by 11 for 0-93 max
jr r31
mflo r2


You can use either a root counter or this somewhere near where you load the RNG seed.


I was indeed going to write the same thing - random number generators are usually based on "something that varies" which typically translates either into a clock or some user interaction counter.


Would be easier to read the code in C, and design and solve the problem in C and then translate to asm. (I never got used to the official mnemonics instead of nocash ones)

I assume the existing RNG in the game is either too bad or you haven't located it?

There is plenty of information googlable related to pseudo RNGs like If it really matters, I'd just experiment in C. Gemini's suggestion may be useful enough, though he leaves the "using" part ambiguous.

Based on the comments in your code, isn't this for numbers 0-93? Shouldn't you divide by 100 and get the remainder+1 for 1-100 (aka modulo)?


Thank you for the replies everyone. This is for the game Master of Monsters which shipped with a totally broken random generator. After about 20 CPU ops, the RNG's end result simply counts up by two each time it's called. Ingame that results in intermittent stretches of "no luck" and then "perfect luck", which makes the game annoying to play.

The page you linked has a wealth of information, Gemini. I think I'll try the root counter method to generate the seed and see what happens. I had never heard of the PS1's Timer. I was able to find a little info on it (I think) in Sony documents, but if I understand how it works correctly, using it as a source for the seed might cause irregularities if it's already in use by the game?

I'll try using the remainder like you say STARWIN and see what happens. I made a logging routine that tallies the random "rolls" in game. After ~500 calls, the routine I posted (with a starting seed of "1") seems to roll within +/-8 of even. But it still seems to favor various spans of values intermittently.

Thank you all again  :beer:


It seems a bit odd to me if the RNG function returns 1-100, but if that's what the original does, then it's fine. Usually a RNG function provides n bits of randomness and the caller has to shape it into the desired value range. One additional risk for irregularities is if the game relies on the RNG being bad in some parts of the code (and if those calls then refer to your improved RNG).

edit: never mind, I read the main thread about this.  :)


It is quite odd. I kept expecting that odd RNG routine had some purpose somewhere else in the game, but if it does, I haven't found it. It doesn't technically return the 1-100, but all the routines that call it (at least the ones I've looked at) process the returned value down to 1-100 and then compare that against the "odds" value by using a slti +bne op pair. I was just giving the simplified version. ;)


You could always try doing what some GBA games do and pre-calculate a set of random numbers which you can select from in that function. That might solve the issue without requiring too many changes to be made to the original code.


I did consider using a table like that. A randomly scrambled value table seems like it would feel random ingame and also have the benefit of giving a perfectly balanced return: every 100 calls it would return an even field without any duplication.

But after some testing I decided to go with the posted routine, fixed as STARWIN suggested to use the remainder (mfhi) to generate the final random. After that change, its returns improved overall 'evenness' to about +/-6 after 600 calls. In the logging routine, the results also filled in much more evenly rather than in intermittent spurts favoring one value range.

I chose not to use the root seed idea because it might lead to "save/load spamming" as players hoped to land a hit or get a successful creature fusion. That is discouraged by the posted routine since it always returns the same results even if you relaod.


Shouldn't the game have the C standard function "rand" included?


I searched for it and don't think it does. But even if I missed it, the game doesn't use it for its vital processes. Is there a trick to finding common routines in a game other than searching for key parts of their code? 


If it doesn't have rand, this is how you implement it:
li t2, 0xA0
jr t2
li t1, 0x2F


Quote from: weissvulf on February 13, 2015, 02:28:05 PM
I searched for it and don't think it does. But even if I missed it, the game doesn't use it for its vital processes. Is there a trick to finding common routines in a game other than searching for key parts of their code? 

Probably not. It's up to the debugger pretty much.. if a debugger did some bookkeeping of most called routines, that'd be the closest thing to a trick I can think of.

Regarding C rand, I suspect the typical PS1 implementation of it uses the magical numbers 0x41C64E6D and 0x3039 (here is why: and I see them in the original post already. You can probably detect the typical implementation by searching for "C6 41 03 3C" and "6D 4E 63 34" in the cd image - if they are close to each other, that's it. And you probably did something like that.

edit: huh? Gemini is right, there is a bios vector that points to an unused (why?) RNG that uses these same magic numbers, but a bit different registers ("C6 41 01 3C" and "6D 4E 21 34" in the BIOS image)


It's included as part of the standard library.  Devs just must have felt it didn't suit their purposes, or some other silly reason.