[Suggestion] Switch to an O(log n) algo complexity to compute the distance between 2 PRNG states for LCGs.
StarfBerry opened this issue · comments
It's possible to calculate the distance between 2 LCG states in O(log n) complexity.
Here the source https://math.stackexchange.com/questions/2008585/computing-the-distance-between-two-linear-congruential-generator-states and an implementation in Python: https://github.com/Parzival5/Python-Scripts/blob/main/LCG.py#L50.
According to the person who found this algo, it should work with all main LCGs used in Pokémon games (mult = 1 mod 4, add is odd and the period is 2^32 or 2^64).
I tried the algo with several billion of PRNG states and different LCGs and I always get the correct result.
With that we can compute advances almost instantly with just the initial seed or with the previous prng state.
The Initial Seed Bot for FRLG can be optimized using this algo, I was able to get my target seed in an advances range < 10M in less than 15 seconds.
We can also lift the limit of 10k advances for Gen 4 games (the user will rarely reach such advancements but I think that's a good thing).
It's also possible to improve the savestate support like with RS on VBA where lua scripts can take a bunch of time to calculate advances when we reload states with different initial seeds.
Here is an application of the algo on Ruby using 5 savestates with different initial seeds for each:
As you can see we keep the initial seed from the session where lua script was executed but info displayed are coherent and we don't have freezes between savestate loadings.
There's probably more applications that we can do with this algo.
An implementation of the algo for the LCRNG in lua if you want to try:
local rshift = bit.rshift
local bxor = bit.bxor
local band = bit.band
function mult32(a, b)
local c = rshift(a, 16)
local d = a % 0x10000
local e = rshift(b, 16)
local f = b % 0x10000
local g = (c * f + d * e) % 0x10000
local h = d * f
local i = g * 0x10000 + h
return i
end
pmult = {
0x41C64E6D, 0xC2A29A69, 0xEE067F11, 0xCFDDDF21, 0x5F748241, 0x8B2E1481, 0x76006901,
0x1711D201, 0xBE67A401, 0xDDDF4801, 0x3FFE9001, 0x90FD2001, 0x65FA4001, 0xDBF48001,
0xF7E90001, 0xEFD20001, 0xDFA40001, 0xBF480001, 0x7E900001, 0xFD200001, 0xFA400001,
0xF4800001, 0xE9000001, 0xD2000001, 0xA4000001, 0x48000001, 0x90000001, 0x20000001,
0x40000001, 0x80000001, 0x00000001, 0x00000001}
padd = {
0x00006073, 0xE97E7B6A, 0x31B0DDE4, 0x67DBB608, 0xCBA72510, 0x1D29AE20, 0xBA84EC40,
0x79F01880, 0x08793100, 0x6B566200, 0x803CC400, 0xA6B98800, 0xE6731000, 0x30E62000,
0xF1CC4000, 0x23988000, 0x47310000, 0x8E620000, 0x1CC40000, 0x39880000, 0x73100000,
0xE6200000, 0xCC400000, 0x98800000, 0x31000000, 0x62000000, 0xC4000000, 0x88000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000}
-- Calculate the distance between 2 LCRNG states in O(log n) complexity.
-- Credit: https://math.stackexchange.com/questions/2008585/computing-the-distance-between-two-linear-congruential-generator-states
function calcDistance(seed1, seed2)
p, i, d = 1, 1, 0
while seed1 ~= seed2 do
if band(bxor(seed1, seed2), p) ~= 0 then
seed1 = (mult32(seed1, pmult[i]) + padd[i]) % 4294967296
d = d + p
end
i = i + 1
p = p * 2
end
return d
end
print(calcDistance(0xC0CAC01A, 0))
print(calcDistance(0xDEADBEEF, 0xBA0BAB))
Ok i completely missed this D:
This seems great, i'll take a look at it soon!
The gen4 10k limit has been already removed but i didn't push yet. The problem wasn't the distance, but the weather changing the init state