Add Pokewalker IV validation methods

Not yet hooked into the legality analysis (MethodFinder doesn't know about encounter template info).
This commit is contained in:
Kurt 2023-10-07 00:00:36 -07:00
parent fac682bcad
commit 707898d4e2
3 changed files with 132 additions and 4 deletions

View file

@ -807,3 +807,120 @@ public static class MethodFinder
return default;
}
}
public static class MethodFinderPokewalker
{
public static (uint Seed, int Prior) GetSeedsPokewalkerIVs(ushort species, PokewalkerCourse4 course,
Span<uint> tmpIVs, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
{
uint first = (hp | (atk << 5) | (def << 10)) << 16;
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
return GetSeedsPokewalkerIVs(species, course, tmpIVs, first, second);
}
public const int NoPokwalkerMatch = -1;
public static (uint Seed, int Prior) GetSeedsPokewalkerIVs(ushort species, PokewalkerCourse4 course,
Span<uint> result, uint first, uint second)
{
// When generating a set of Pokéwalker Pokémon (and their IVs), the game does the following logic:
// If the player does not begin a stroll, generate an initial seed based on seconds elapsed in the day (< 86400).
// Otherwise, generate an initial seed based on the elapsed time and date (similar to Gen4 initial seeding).
// If the player begins a stroll, the game generates a set of 3 Pokémon to see, with results untraceable to the correlation.
// Then, the game generates each Pokémon's IVs by calling rand() twice.
// Since stroll causes 3 RNG advancements, an initial seed [stroll] can be advanced 3+(2*n) times, or [no-stroll] advanced 0+(2*n) times.
// To determine the first valid initial seed, take advantage of the even-odd nature of the RNG frames (different initial seeding algorithm).
// seeding for [stroll]: 3600 * hour + 60 * minute + second
// seeding for [no-stroll]: (((month*day + minute + second) & 0xff) << 24) | (hour << 16) | (year)
// the top byte of no-stroll can be any value, so we can skip checking that byte.
int ctr = LCRNGReversal.GetSeedsIVs(result, first, second);
if (ctr == 0)
return (0, NoPokwalkerMatch);
result = result[..ctr];
const int boxCount = 18;
const int boxSize = 30;
const int boxCapacity = boxCount * boxSize;
const int maxHours = 24;
const int maxYears = 100;
const int secondsPerDay = 60 * 60 * 24;
for (int priorPoke = 0; priorPoke < boxCapacity; priorPoke++)
{
foreach (ref var seed in result)
{
var s = seed; // already unrolled once
// Check the [no-stroll] case.
if ((byte)(s >> 16) < maxHours && (ushort)s < maxYears)
return (s, priorPoke);
s = seed = LCRNG.Prev(seed);
// Check the [stroll] case.
if (priorPoke != 0 && s < secondsPerDay && IsValidStrollSeed(s, species, course)) // seed can't be hit due to the 3 advances from stroll
return (s, priorPoke);
seed = LCRNG.Prev(seed); // prep for next loop
}
}
return (0, NoPokwalkerMatch);
}
private const int SlotsPerCourse = 6;
public static bool IsValidStrollSeed(uint seed, ushort species, PokewalkerCourse4 course)
{
// initial seed
// rand() & 1 => slot A
// rand() & 1 => slot B
// rand() & 1 => slot C
// generate IVs
var span = CourseSpecies.Slice((int)course * SlotsPerCourse, SlotsPerCourse);
seed = LCRNG.Next(seed);
var slotA = (seed >> 16) & 1;
if (span[(int)slotA] == species)
return true;
seed = LCRNG.Next(seed);
var slotB = (seed >> 16) & 1;
if (span[(int)slotB + 2] == species)
return true;
seed = LCRNG.Next(seed);
var slotC = (seed >> 16) & 1;
if (span[(int)slotC + 4] == species)
return true;
return false;
}
private static ReadOnlySpan<ushort> CourseSpecies => new ushort[]
{
115, 084, 029, 032, 016, 161, // 00 Refreshing Field
202, 069, 048, 046, 043, 021, // 01 Noisy Forest
240, 095, 066, 077, 163, 074, // 02 Rugged Road
054, 120, 079, 060, 191, 194, // 03 Beautiful Beach
239, 081, 081, 198, 163, 019, // 04 Suburban Area
238, 092, 092, 095, 041, 066, // 05 Dim Cave
147, 060, 098, 090, 118, 072, // 06 Blue Lake
063, 100, 109, 088, 019, 162, // 07 Town Outskirts
300, 264, 314, 313, 263, 265, // 08 Hoenn Field
320, 298, 116, 318, 118, 129, // 09 Warm Beach
218, 307, 228, 111, 077, 074, // 10 Volcano Path
352, 351, 203, 234, 044, 070, // 11 Treehouse
105, 128, 042, 177, 066, 092, // 12 Scary Cave
439, 415, 403, 406, 399, 401, // 13 Sinnoh Field
459, 361, 215, 436, 220, 179, // 14 Icy Mountain Road
357, 438, 114, 400, 179, 102, // 15 Big Forest
433, 200, 093, 418, 223, 170, // 16 White Lake
456, 422, 129, 086, 054, 090, // 17 Stormy Beach
417, 025, 039, 035, 183, 187, // 18 Resort
442, 446, 433, 349, 164, 042, // 19 Quiet Cave
120, 224, 116, 222, 223, 170, // 20 Beyond The Sea
035, 039, 041, 163, 074, 095, // 21 Night Sky's Edge
025, 025, 025, 025, 025, 025, // 22 Yellow Forest
441, 302, 025, 453, 427, 417, // 23 Rally
255, 133, 279, 061, 052, 025, // 24 Sightseeing
446, 374, 116, 355, 129, 436, // 25 Winners Path
239, 240, 238, 440, 174, 173, // 26 Amity Meadow
};
}

View file

@ -6,10 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit" Version="2.5.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -152,6 +152,17 @@ public class PIDIVTest
Assert.True(MethodFinder.GetPokeSpotSeedFirst(pkPS2, 2).Type == PIDType.PokeSpot, "PokeSpot encounter info mismatch (Rare)");
}
[Theory]
[InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 24, (int)Species.Pikachu, PokewalkerCourse4.YellowForest)]
public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, int expect, ushort species, PokewalkerCourse4 course)
{
Span<uint> tmp = stackalloc uint[LCRNG.MaxCountSeedsIV];
(uint actualSeed, int prior) = MethodFinderPokewalker.GetSeedsPokewalkerIVs(species, course, tmp, hp, atk, def, spA, spD, spE);
prior.Should().NotBe(MethodFinderPokewalker.NoPokwalkerMatch);
prior.Should().Be(expect);
actualSeed.Should().Be(seed);
}
[Fact]
public void PIDIVPokewalkerTest()
{