Pokewalker: Handle 539 stroll, revise signatures

Was missing the worst case scenario; ran a bruteforce script to find a 539 after adding the final logic. We try to do the least amount of cpu instructions so this is probably the better arrangement anyway.

Revise the method signatures to exclude the scratchspace span reuse (not-obvious behavior). Just stackalloc a new span.

Add seed indication to legality formatting. #4416
This commit is contained in:
Kurt
2025-01-15 01:26:49 -06:00
parent e9092ca702
commit eced651024
4 changed files with 45 additions and 22 deletions

View File

@@ -116,7 +116,7 @@ private uint GetIV32(EncounterCriteria criteria)
if (criteria.IsSpecifiedIVsAll()) // Don't trust that the requirements are valid
{
criteria.GetCombinedIVs(out var iv1, out var iv2);
var seed = PokewalkerRNG.GetFirstSeed(iv1, iv2);
var seed = PokewalkerRNG.GetLeastEffortSeed(iv1, iv2);
if (seed.Type != PokewalkerSeedType.None)
return criteria.GetCombinedIVs();
}
@@ -142,11 +142,11 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return true;
}
private bool IsMatchSeed(PKM pk)
private static bool IsMatchSeed(PKM pk)
{
Span<int> ivs = stackalloc int[6];
pk.GetIVs(ivs);
var seed = PokewalkerRNG.GetFirstSeed(ivs);
var seed = PokewalkerRNG.GetLeastEffortSeed(ivs);
return seed.Type != PokewalkerSeedType.None;
}
@@ -191,7 +191,7 @@ public EncounterMatchRating GetMatchRating(PKM pk)
return EncounterMatchRating.Match;
}
private bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk);
private static bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk);
#endregion
public bool IsCompatible(PIDType type, PKM pk)

View File

@@ -117,7 +117,17 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
}
lines.Add(type);
if (pidiv.NoSeed)
{
if (pidiv.Type is PIDType.Pokewalker)
{
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(L_FOriginSeed_0, result.Seed.ToString("X8"));
line += $" [{result.Type} @ {result.PriorPoke}]";
lines.Add(line);
}
return;
}
if (pidiv.IsSeed64())
{

View File

@@ -99,29 +99,32 @@ public static bool IsSeedFormatNoStroll(uint seed)
];
/// <summary>
/// Gets the first valid seed for the given Pokéwalker IVs.
/// Finds an initial seed for the Pokéwalker IVs with the least amount of capture-IVs advances needed.
/// </summary>
public static PokewalkerSeedResult GetFirstSeed(Span<int> ivs)
/// <param name="ivs">IVs in order (speed last).</param>
public static PokewalkerSeedResult GetLeastEffortSeed(Span<int> ivs)
{
var tmp = MemoryMarshal.Cast<int, uint>(ivs);
return GetFirstSeed(tmp, tmp[0], tmp[1], tmp[2], tmp[4], tmp[5], spe: tmp[3]);
return GetLeastEffortSeed(tmp[0], tmp[1], tmp[2], tmp[4], tmp[5], spe: tmp[3]);
}
/// <inheritdoc cref="GetFirstSeed(Span{int})"/>
public static PokewalkerSeedResult GetFirstSeed(Span<uint> tmpIVs,
uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
/// <inheritdoc cref="LCRNGReversal.GetSeedsIVs(Span{uint},uint, uint)"/>
public static PokewalkerSeedResult GetLeastEffortSeed(uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
{
Span<uint> result = stackalloc uint[LCRNG.MaxCountSeedsIV];
uint first = (hp | (atk << 5) | (def << 10)) << 16;
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
return GetFirstSeed(tmpIVs, first, second);
return GetLeastEffortSeed(result, first, second);
}
/// <inheritdoc cref="GetFirstSeed(Span{int})"/>
public static PokewalkerSeedResult GetFirstSeed(uint first, uint second)
=> GetFirstSeed(stackalloc uint[LCRNG.MaxCountSeedsIV], first, second);
/// <inheritdoc cref="GetLeastEffortSeed(Span{int})"/>
/// <inheritdoc cref="LCRNGReversal.GetSeedsIVs(Span{uint},uint, uint)"/>
public static PokewalkerSeedResult GetLeastEffortSeed(uint first, uint second)
=> GetLeastEffortSeed(stackalloc uint[LCRNG.MaxCountSeedsIV], first, second);
/// <inheritdoc cref="GetFirstSeed(Span{int})"/>
public static PokewalkerSeedResult GetFirstSeed(Span<uint> result, uint first, uint second)
/// <inheritdoc cref="GetLeastEffortSeed(Span{int})"/>
/// <inheritdoc cref="LCRNGReversal.GetSeedsIVs(Span{uint},uint, uint)"/>
public static PokewalkerSeedResult GetLeastEffortSeed(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 begins a stroll, generate an initial seed based on seconds elapsed in the day (< 86400) and 3 slots.
@@ -146,15 +149,25 @@ public static PokewalkerSeedResult GetFirstSeed(Span<uint> result, uint first, u
// Check the [no-stroll] case.
if (IsSeedFormatNoStroll(s))
return new(s, priorPoke, PokewalkerSeedType.NoStroll);
s = seed = LCRNG.Prev(seed);
s = LCRNG.Prev(s);
// Check the [stroll] case.
// Due to this backtracking algorithm, the first time we check won't be a valid (needs 3 advancements)
if (priorPoke != 0 && IsSeedFormatStroll(s)) // don't check species; can be disassociated from slots.
return new(s, --priorPoke, PokewalkerSeedType.Stroll); // decrement priorPoke back to 0-indexed
seed = LCRNG.Prev(seed); // prep for next loop
seed = LCRNG.Prev(s); // prep for next loop
}
}
// The above logic for Stroll checks for [0,n-1) due to the backtracking nature of the algorithm.
// Check the last-empty-slot for the Stroll case.
// That's catching 540 'mons... quite unlikely! But still possible, as not all spreads are obtainable.
foreach (ref var seed in result)
{
var s = LCRNG.Prev(seed);
if (IsSeedFormatStroll(s)) // don't check species; can be disassociated from slots.
return new(s, boxCapacity - 1, PokewalkerSeedType.Stroll); // decrement priorPoke back to 0-indexed
}
return default;
}

View File

@@ -157,11 +157,11 @@ public void PIDIVPokeSpotTest()
}
[Theory]
[InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 24, (int)Species.Pikachu, PokewalkerCourse4.YellowForest, PokewalkerSeedType.NoStroll)]
public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, ushort expect, ushort species, PokewalkerCourse4 course, PokewalkerSeedType type)
[InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 024, PokewalkerSeedType.NoStroll)]
[InlineData(00, 00, 00, 05, 03, 09, 0x00011434, 539, PokewalkerSeedType.Stroll)]
public void PokewalkerIVTest(uint hp, uint atk, uint def, uint spA, uint spD, uint spE, uint seed, ushort expect, PokewalkerSeedType type)
{
Span<uint> tmp = stackalloc uint[LCRNG.MaxCountSeedsIV];
var result = PokewalkerRNG.GetFirstSeed(tmp, hp, atk, def, spA, spD, spE);
var result = PokewalkerRNG.GetLeastEffortSeed(hp, atk, def, spA, spD, spE);
result.Type.Should().Be(type);
result.PriorPoke.Should().Be(expect);
result.Seed.Should().Be(seed);