diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs index ed5abab5d..ac33abf53 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterStatic4Pokewalker.cs @@ -117,8 +117,10 @@ private static uint GetIV32(in EncounterCriteria criteria) if (criteria.IsSpecifiedIVsAll()) // Don't trust that the requirements are valid { criteria.GetCombinedIVs(out var iv1, out var iv2); - var seed = PokewalkerRNG.GetLeastEffortSeed(iv1, iv2); - if (seed.Type != PokewalkerSeedType.None) + + // With Mic Test, any seed can be the seed that immediately generates the IVs. + var ctr = LCRNGReversal.GetSeedsIVs(stackalloc uint[LCRNG.MaxCountSeedsIV], iv1, iv2); + if (ctr != 0) return criteria.GetCombinedIVs(); } PokewalkerRNG.GetRandomIVs(criteria, out var iv32); diff --git a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs index 32772af48..419332b9e 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs @@ -215,7 +215,7 @@ private static string GetLinePokewalkerSeed(LegalInfo info, LegalityLocalization var pk = info.Entity; var result = PokewalkerRNG.GetLeastEffortSeed((uint)pk.IV_HP, (uint)pk.IV_ATK, (uint)pk.IV_DEF, (uint)pk.IV_SPA, (uint)pk.IV_SPD, (uint)pk.IV_SPE); var line = string.Format(strings.Encounter.Format, strings.Encounter.OriginSeed, result.Seed.ToString("X8")); - line += $" [{result.Type} @ {result.PriorPoke}]"; + line += $" [{result.Type} @ {result.Count}]"; return line; } diff --git a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs index ea1e89aaa..4a1e47d17 100644 --- a/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs +++ b/PKHeX.Core/Legality/RNG/ClassicEra/Gen4/PokewalkerRNG.cs @@ -170,7 +170,28 @@ public static PokewalkerSeedResult GetLeastEffortSeed(Span result, uint fi if (IsSeedFormatStroll(s)) // don't check species; can be disassociated from slots. return new(s, MaxPriorPokes, PokewalkerSeedType.Stroll); // decrement priorPoke back to 0-indexed } - return default; + + // Reset the seeds; recomputing doesn't take too much time since this is incredibly rare for a passing case. + LCRNGReversal.GetSeedsIVs(result, first, second); // could just reverse all seeds n steps with a single *a+b, but this is clearer. + return GetMicTest(result); + } + + private static PokewalkerSeedResult GetMicTest(Span result) + { + // Initiating a mic test can advance the seed from the initial NoStroll seed to any "initial" seed. Reverse to find a possible starting initial seed. + uint ctr = 0; + while (true) + { + foreach (ref var seed in result) + { + if (IsSeedFormatNoStroll(seed)) + return new(seed, ctr, PokewalkerSeedType.MicTestNoStroll); + if (ctr != 0 && IsSeedFormatStroll(seed)) + return new(seed, (ctr - 1), PokewalkerSeedType.MicTestStroll); + seed = LCRNG.Prev(seed); + } + ctr++; + } } /// @@ -341,10 +362,36 @@ private static bool TryApply(ref uint seed, out uint iv32, in EncounterCriteria /// /// Wrapper for Pokewalker Seed Results /// -/// 32-bit seed -/// Count of Pokémon generated prior to the checked Pokémon. Maximum value: (539) -/// Type of seed -public readonly record struct PokewalkerSeedResult(uint Seed, ushort PriorPoke, PokewalkerSeedType Type); +[StructLayout(LayoutKind.Explicit)] +public readonly record struct PokewalkerSeedResult +{ + /// + /// Wrapper for Pokewalker Seed Results + /// + public PokewalkerSeedResult(uint seed, uint value, PokewalkerSeedType type) + { + Seed = seed; + Value = value; + Type = type; + } + + /// 32-bit seed + [field: FieldOffset(0)] public uint Seed { get; } + + /// + /// Only the lower 24-bits are used. + /// + [field: FieldOffset(4)] private uint Value { get; } + + /// Type of seed + [field: FieldOffset(7)] public PokewalkerSeedType Type { get; } + + /// + /// Mic Test: frames advanced from initial seed to the checked seed. + /// Not Mic Test: Count of Pokémon generated prior to the checked Pokémon. Maximum value: (539) + /// + public uint Count => Value & 0x00_FFFFFF; +} /// /// Type of Pokewalker Seed @@ -357,4 +404,8 @@ public enum PokewalkerSeedType : byte Stroll = 1, /// No Stroll Seed NoStroll = 2, + /// Mic Test Stroll Seed + MicTestStroll = 3, + /// Mic Test No Stroll Seed + MicTestNoStroll = 4, } diff --git a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs index be8839984..6d7d0168d 100644 --- a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs @@ -175,7 +175,7 @@ public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, ui { var result = PokewalkerRNG.GetLeastEffortSeed(hp, atk, def, spA, spD, spE); result.Type.Should().Be(type); - result.PriorPoke.Should().Be(expect); + result.Count.Should().Be(expect); result.Seed.Should().Be(seed); }