mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-22 12:03:10 +00:00
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:
parent
f99e4e54f3
commit
aba7c800b3
3 changed files with 148 additions and 0 deletions
|
@ -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 & 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 & 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;
|
||||
}
|
||||
}
|
||||
|
|
29
Tests/PKHeX.Core.Tests/PKM/LCRNGTest.cs
Normal file
29
Tests/PKHeX.Core.Tests/PKM/LCRNGTest.cs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue