From e2e5d3a8f4bb8395e2df83c5d9bde5bdbdfeec21 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 23 Aug 2026 13:11:56 -0500 Subject: [PATCH 1/2] fix name lookup for unown --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 10 +++++-- .../Source/Pokemon/Pokemon_AdvRng.h | 5 ++++ .../PokemonFRLG_RngCalibration.cpp | 26 ++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 7816565aec..d0557b1304 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -259,6 +259,13 @@ uint8_t slot_number_from_roll(uint8_t roll, size_t size, bool super_rod) { } } +std::string base_species_slug(const std::string& species){ + if (species.find("unown") != std::string::npos){ + return "unown"; + } + return species; +} + int slot_to_unownform(AdvEncounterSlot slot){ if (slot.species.find("unown") == std::string::npos){ return -1; @@ -510,8 +517,7 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, int16_t gender_ } bool check_for_match(AdvWildPokemonResult res, AdvRngFilters target, int16_t gender_threshold, uint16_t tid_xor_sid){ - std::string res_name = res.species.find("unown") != std::string::npos ? "unown" : res.species; - return (target.species == res_name) + return (base_species_slug(target.species) == base_species_slug(res.species)) && (target.level == res.level) && (target.nature == AdvNature::Any || (res.nature == target.nature)) && (target.ability == AdvAbility::Any || (res.ability == target.ability)) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index dad2bcf7f1..85a3ad8707 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -202,6 +202,11 @@ Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature); Pokemon::AdvNature string_to_nature(const std::string& nature_string); std::string nature_to_string(const AdvNature& nature); +// Forms that cannot be told apart from an observation (e.g. Unown letters) share a base slug. +// Encounter slots carry the form-specific slug, while an observed species only ever carries +// the base slug, so both sides must be folded before they are compared. +std::string base_species_slug(const std::string& species); + std::string gender_to_string(const AdvGender& gender); AdvGender gender_from_gender_value(uint8_t gender_value, int16_t threshold); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index 694f4af839..b7945fac0e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngCalibration.h" @@ -309,9 +310,7 @@ bool validate_level( ){ std::vector valid_slots; for (const AdvEncounterSlot& slot : ENCOUNTER_SLOTS){ - if (filters.species == slot.species - - ){ + if (base_species_slug(filters.species) == base_species_slug(slot.species)){ valid_slots.push_back(slot); } } @@ -323,22 +322,31 @@ bool validate_level( return true; } } - // try all levels within the encounter slots for the observed species if there was no valid match - std::vector levels; + // try all levels within the encounter slots for the observed species if there was no valid match. + // slots commonly overlap in level, so only count each distinct level once. + const AdvRngFilters original_filters = filters; + const uint8_t original_level = pokemon.level[0]; + std::set levels; for (const AdvEncounterSlot& slot : valid_slots){ for (uint8_t level = slot.minlevel; level <= slot.maxlevel; level++){ + if (levels.find(level) != levels.end()){ + continue; + } pokemon.level[0] = level; - filters = observation_to_filters(pokemon, BASE_STATS, filters.method); - if (ranges_are_valid(filters.ivs)){ - levels.push_back(level); + AdvRngFilters candidate = observation_to_filters(pokemon, BASE_STATS, filters.method); + if (ranges_are_valid(candidate.ivs)){ + levels.insert(level); } } } if (levels.size() == 1){ - pokemon.level[0] = levels[0]; + pokemon.level[0] = *levels.begin(); filters = observation_to_filters(pokemon, BASE_STATS, filters.method); return true; } + // no unambiguous level was found: leave the caller's state as it was found + pokemon.level[0] = original_level; + filters = original_filters; return false; } From ba0283463f1042214f3d55eb747e7022ace19c40 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 23 Aug 2026 13:38:08 -0500 Subject: [PATCH 2/2] fix recovery for bad reads in level_up_observed_pokemon --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 4 +++ .../PokemonFRLG_RngCalibration.cpp | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index d0557b1304..06cb46445e 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -12,6 +12,10 @@ namespace PokemonAutomation{ namespace Pokemon{ void level_up_observed_pokemon(AdvObservedPokemon& pokemon, const StatReads& newstats, const EVs& evyield){ + if (pokemon.level.empty() || pokemon.stats.empty() || pokemon.evs.empty()){ + return; + } + uint8_t newlevel = pokemon.level.back() + 1; pokemon.level.emplace_back(newlevel); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index b7945fac0e..7c0a59625a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -360,9 +360,19 @@ void update_filters( ){ level_up_observed_pokemon(pokemon, stats, evyield); + if (pokemon.level.empty() || pokemon.stats.empty() || pokemon.evs.empty()){ + filters.ivs = { {0,31}, {0,31}, {0,31}, {0,31}, {0,31}, {0,31} }; + return; + } + + const uint8_t new_level = pokemon.level.back(); + const EVs new_evs = pokemon.evs.back(); + while (true){ - // in the worst case (the new stats are the problem), start over if (pokemon.level.size() == 0){ + pokemon.level = { new_level }; + pokemon.stats = { stats }; + pokemon.evs = { new_evs }; filters.ivs = { {0,31}, {0,31}, {0,31}, {0,31}, {0,31}, {0,31} }; return; } @@ -372,15 +382,17 @@ void update_filters( if (!ranges_are_valid(new_filters.ivs)){ IvRanges new_stat_ivs = calc_iv_ranges(BASE_STATS, pokemon.level.back(), pokemon.evs.back(), pokemon.stats.back(), nature_to_adjustment(pokemon.nature)); if (!ranges_are_valid(new_stat_ivs)){ - // remove newest stats first if they aren't valid - pokemon.level.erase(pokemon.level.begin()); - pokemon.stats.erase(pokemon.stats.begin()); - pokemon.evs.erase(pokemon.evs.begin()); - }else{ - // remove oldest stats first + // the newest reading is impossible on its own. + // don't keep it pokemon.level.pop_back(); pokemon.stats.pop_back(); pokemon.evs.pop_back(); + }else{ + // the newest reading is self-consistent but conflicts with an older one. + // discard the oldest reading until things are consistent again + pokemon.level.erase(pokemon.level.begin()); + pokemon.stats.erase(pokemon.stats.begin()); + pokemon.evs.erase(pokemon.evs.begin()); } continue; }