diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs index 751dac675..70d652ced 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.CompilerServices; namespace PKHeX.Core; @@ -123,4 +124,63 @@ public static class LCRNG seed = Prev(seed); return seed; } + + /// + /// Multiplication constants for jumping 2^(index) frames forward. + /// + private static ReadOnlySpan JumpMult => + [ + 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, + ]; + + /// + /// Addition constants for jumping 2^(index) frames forward. + /// + private static ReadOnlySpan JumpAdd => + [ + 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, + ]; + + /// + /// Computes the amount of advances (distance) between two seeds. + /// + /// Initial seed + /// Final seed + /// Count of advances from to arrive at . + /// + /// To compute the distance, we abuse the fact that a given state bit at index `i` has a periodicity of `2^i`. + /// If the bit is present in the state, we must include that bit in our distance result. + /// The algorithmic complexity is O(log(n)) for finding n advancements. + /// We store a precomputed table of multiply & addition constants (skip 2^n) to avoid computing them on the fly. + /// + public static uint GetDistance(in uint start, in uint end) + { + int i = 0; + uint bit = 1u; + + uint distance = 0u; + uint seed = start; + + // Instead of doing a for loop which always does 32 iterations, check to see if we end up at the end seed. + // If we do, we can return after [0..31] jumps. + // Due to the inputs, we normally have low distance, so normally this won't take more than a few loops. + while (seed != end) + { + // 50:50 odds of this being true. + if (((seed ^ end) & bit) != 0) + { + seed = (seed * JumpMult[i]) + JumpAdd[i]; + distance |= bit; + } + i++; + bit <<= 1; + } + return distance; + } } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs index 2caff8c0d..568085752 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs @@ -284,4 +284,63 @@ public static class XDRNG } return ctr; } + + /// + /// Multiplication constants for jumping 2^(index) frames forward. + /// + private static ReadOnlySpan JumpMult => + [ + 0x000343FD, 0xA9FC6809, 0xDDFF5051, 0xF490B9A1, 0x43BA1741, 0xD290BE81, 0x82E3BD01, 0xBF507A01, + 0xF8C4F401, 0x7A19E801, 0x1673D001, 0xB5E7A001, 0x8FCF4001, 0xAF9E8001, 0x9F3D0001, 0x3E7A0001, + 0x7CF40001, 0xF9E80001, 0xF3D00001, 0xE7A00001, 0xCF400001, 0x9E800001, 0x3D000001, 0x7A000001, + 0xF4000001, 0xE8000001, 0xD0000001, 0xA0000001, 0x40000001, 0x80000001, 0x00000001, 0x00000001, + ]; + + /// + /// Addition constants for jumping 2^(index) frames forward. + /// + private static ReadOnlySpan JumpAdd => + [ + 0x00269EC3, 0x1E278E7A, 0x098520C4, 0x7E1DBEC8, 0x3E314290, 0x824E1920, 0x844E8240, 0xFD864480, + 0xDFB18900, 0xD9F71200, 0x5E3E2400, 0x65BC4800, 0x70789000, 0x74F12000, 0x39E24000, 0xB3C48000, + 0x67890000, 0xCF120000, 0x9E240000, 0x3C480000, 0x78900000, 0xF1200000, 0xE2400000, 0xC4800000, + 0x89000000, 0x12000000, 0x24000000, 0x48000000, 0x90000000, 0x20000000, 0x40000000, 0x80000000, + ]; + + /// + /// Computes the amount of advances (distance) between two seeds. + /// + /// Initial seed + /// Final seed + /// Count of advances from to arrive at . + /// + /// To compute the distance, we abuse the fact that a given state bit at index `i` has a periodicity of `2^i`. + /// If the bit is present in the state, we must include that bit in our distance result. + /// The algorithmic complexity is O(log(n)) for finding n advancements. + /// We store a precomputed table of multiply & addition constants (skip 2^n) to avoid computing them on the fly. + /// + public static uint GetDistance(in uint start, in uint end) + { + int i = 0; + uint bit = 1u; + + uint distance = 0u; + uint seed = start; + + // Instead of doing a for loop which always does 32 iterations, check to see if we end up at the end seed. + // If we do, we can return after [0..31] jumps. + // Due to the inputs, we normally have low distance, so normally this won't take more than a few loops. + while (seed != end) + { + // 50:50 odds of this being true. + if (((seed ^ end) & bit) != 0) + { + seed = (seed * JumpMult[i]) + JumpAdd[i]; + distance |= bit; + } + i++; + bit <<= 1; + } + return distance; + } } diff --git a/Tests/PKHeX.Core.Tests/PKM/LCRNGTest.cs b/Tests/PKHeX.Core.Tests/PKM/LCRNGTest.cs new file mode 100644 index 000000000..9bbe568d4 --- /dev/null +++ b/Tests/PKHeX.Core.Tests/PKM/LCRNGTest.cs @@ -0,0 +1,29 @@ +using FluentAssertions; +using Xunit; + +namespace PKHeX.Core.Tests.PKM; + +public class LCRNGTest +{ + [Theory] + [InlineData(0x12345u, 0xAEA0DF8C, 12345u)] + [InlineData(0xBADC0DED, 0xBADC0DED, 0u)] + [InlineData(0, 0x0A3561A1, uint.MaxValue)] + [InlineData(0x0A3561A1, 0, 1u)] + public void FindFrame(uint start, uint end, uint expect) + { + var distance = LCRNG.GetDistance(start, end); + distance.Should().Be(expect); + } + + [Theory] + [InlineData(8675309, 0x75C29428, 8675309)] + [InlineData(0xBADC0DED, 0xBADC0DED, 0u)] + [InlineData(0, 0xA170F641, uint.MaxValue)] + [InlineData(0xA170F641, 0, 1u)] + public void FindFrameXDRNG(uint start, uint end, uint expect) + { + var distance = XDRNG.GetDistance(start, end); + distance.Should().Be(expect); + } +}