Add LCRNG distance calculating method

Solution known for over a decade, finally reminded myself that it'd be nice to have this available. Probably will use this for displaying Method J/K/H frame info when that branch is more mature.
This commit is contained in:
Kurt 2024-01-30 19:14:04 -08:00
parent f99e4e54f3
commit aba7c800b3
3 changed files with 148 additions and 0 deletions

View file

@ -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;
}
/// <summary>
/// Multiplication constants for jumping 2^(index) frames forward.
/// </summary>
private static ReadOnlySpan<uint> 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,
];
/// <summary>
/// Addition constants for jumping 2^(index) frames forward.
/// </summary>
private static ReadOnlySpan<uint> 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,
];
/// <summary>
/// Computes the amount of advances (distance) between two seeds.
/// </summary>
/// <param name="start">Initial seed</param>
/// <param name="end">Final seed</param>
/// <returns>Count of advances from <see cref="start"/> to arrive at <see cref="end"/>.</returns>
/// <remarks>
/// 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 &amp; addition constants (skip 2^n) to avoid computing them on the fly.
/// </remarks>
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;
}
}

View file

@ -284,4 +284,63 @@ public static class XDRNG
}
return ctr;
}
/// <summary>
/// Multiplication constants for jumping 2^(index) frames forward.
/// </summary>
private static ReadOnlySpan<uint> 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,
];
/// <summary>
/// Addition constants for jumping 2^(index) frames forward.
/// </summary>
private static ReadOnlySpan<uint> 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,
];
/// <summary>
/// Computes the amount of advances (distance) between two seeds.
/// </summary>
/// <param name="start">Initial seed</param>
/// <param name="end">Final seed</param>
/// <returns>Count of advances from <see cref="start"/> to arrive at <see cref="end"/>.</returns>
/// <remarks>
/// 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 &amp; addition constants (skip 2^n) to avoid computing them on the fly.
/// </remarks>
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;
}
}

View file

@ -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);
}
}