Files
PKHeX/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs
Kurt 6f717f2359 Add Ranch IV reversal, improved lattice recovery
Introduces the MRNG algorithm and MRNGReversal to model My Pokemon Ranch IV generation, then wires it into Gen4 Ranch gift creation and IV legality checks with a new PIDType.Ranch classification.
Updates the seed-reversal logic in the LCRNG-based recovery methods with more performant lattice-bound implementations and early returns.
Adds more unit tests for Ranch & Channel reversals and expected recovered seed counts/hit-ability.

Refer to the linked LCG_Recovery.py for more details.
https://github.com/StarfBerry/PokeRNG/blob/main/Recovery/LCG_Recovery.py
(big thanks to StarfBerry for the math & documentation; this is merely a port of their work).

From Admiral-Fish's testing for PokeFinder, "shows 17-25% faster. Channel is 12k times faster"
d4512de8dc

Co-Authored-By: StarfBerry <65578604+starfberry@users.noreply.github.com>
2026-07-19 12:49:12 -05:00

107 lines
3.6 KiB
C#

using System;
using static PKHeX.Core.LegalityCheckResultCode;
namespace PKHeX.Core;
/// <summary>
/// Verifies the <see cref="PKM.IVs"/>.
/// </summary>
public sealed class IndividualValueVerifier : Verifier
{
protected override CheckIdentifier Identifier => CheckIdentifier.IVs;
public override void Verify(LegalityAnalysis data)
{
switch (data.EncounterMatch)
{
case IPogoSlot s:
VerifyIVsGoTransfer(data, s);
break;
case IFlawlessIVCount s:
VerifyIVsFlawless(data, s);
break;
case EncounterSlot7:
VerifyIVsGen7(data);
break;
case MysteryGift g:
VerifyIVsMystery(data, g);
break;
case EncounterTrade4RanchGift:
VerifyIVsRanch4(data);
break;
}
var pk = data.Entity;
var hp = pk.IV_HP;
if (hp < 30 && AllIVsEqual(pk, hp))
data.AddLine(Get(Severity.Fishy, IVAllEqual_0, (ushort)hp));
}
private static bool AllIVsEqual(PKM pk, int hp) => pk.IV_ATK == hp
&& pk.IV_DEF == hp
&& pk.IV_SPA == hp
&& pk.IV_SPD == hp
&& pk.IV_SPE == hp;
private void VerifyIVsMystery(LegalityAnalysis data, MysteryGift g)
{
if (!g.HasFixedIVs)
return; // PID/IV style instead of fixed IVs.
Span<int> IVs = stackalloc int[6];
g.GetIVs(IVs);
var ivflag = IVs.IndexOfAny(0xFC, 0xFD, 0xFE);
if (ivflag == -1) // Random IVs
{
bool valid = Legal.GetIsFixedIVSequenceValidSkipRand(IVs, data.Entity);
if (!valid)
data.AddLine(GetInvalid(EncGiftIVMismatch));
}
else
{
int IVCount = IVs[ivflag] - 0xFB; // IV2/IV3
VerifyIVsFlawless(data, IVCount);
}
}
private void VerifyIVsGen7(LegalityAnalysis data)
{
var pk = data.Entity;
if (pk.AbilityNumber == 4)
{
var abilities = (IPersonalAbility12H)pk.PersonalInfo;
if (!AbilityVerifier.CanAbilityPatch(pk.Format, abilities, pk.Species))
VerifyIVsFlawless(data, 2); // Chain of 10 yields 5% HA and 2 flawless IVs
}
}
private void VerifyIVsFlawless(LegalityAnalysis data, IFlawlessIVCount s)
{
if (s.FlawlessIVCount != 0)
VerifyIVsFlawless(data, s.FlawlessIVCount);
}
private void VerifyIVsFlawless(LegalityAnalysis data, int count)
{
if (data.Entity.FlawlessIVCount < count)
data.AddLine(GetInvalid(IVFlawlessCountGEQ_0, (ushort)count));
}
private void VerifyIVsGoTransfer(LegalityAnalysis data, IPogoSlot g)
{
if (!g.GetIVsValid(data.Entity))
data.AddLine(GetInvalid(IVNotCorrect));
}
private void VerifyIVsRanch4(LegalityAnalysis data)
{
var pk = data.Entity;
Span<uint> seeds = stackalloc uint[LCRNG.MaxCountSeedsIV];
// IVs are generated via 3 consecutive rand() calls. PID comes from a timer register, thus unrelated.
var count = MRNGReversal.GetSeedsIVs(seeds, (uint)pk.IV_HP, (uint)pk.IV_ATK, (uint)pk.IV_DEF, (uint)pk.IV_SPA, (uint)pk.IV_SPD, (uint)pk.IV_SPE);
if (count == 0)
data.AddLine(GetInvalid(PIDTypeMismatch));
else
data.Info.PIDIV = new PIDIV(PIDType.Ranch, seeds[0]); // give first seed result even though there will usually be many.
}
}