From ac472b1b06dbf1b744640ce8d901143d53f8dc9f Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 16:31:16 -0500 Subject: [PATCH 01/21] add initial adv rng calcs --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 206 ++++++++++++++++++ .../Source/Pokemon/Pokemon_AdvRng.h | 136 ++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp create mode 100644 SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp new file mode 100644 index 0000000000..44c5168b7e --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -0,0 +1,206 @@ +/* Xoroshiro128+ and reverse + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "Pokemon_AdvRng.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +uint32_t increment_internal_rng_state(uint32_t& state){ + return state * 0x41c64e6d + 0x6073; +} + +AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint32_t& state){ + uint32_t s0 = state; + uint32_t s1 = increment_internal_rng_state(s0); + uint32_t s2 = increment_internal_rng_state(s1); + uint32_t s3 = increment_internal_rng_state(s2); + uint32_t s4 = increment_internal_rng_state(s3); + + return AdvRngState(seed, advances, s0, s1, s2, s3, s4); +} + +AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t& advances){ + uint32_t state = seed; + for (int i=0; i> 16); +} + +uint8_t gender_value_from_pid(uint32_t& pid){ + return pid & 0xff; +} + +Gender gender_from_gender_value(uint8_t gender_value, uint8_t threshold){ + return (gender_value <= threshold) ? Gender::Female : Gender::Male; +} + +Nature nature_from_pid(uint32_t& pid){ + return Nature (pid % 25); +} + +Ability ability_from_pid(uint32_t& pid){ + return Ability (pid % 2); +} + +IvGroup iv_group_from_state(uint32_t& state){ + IvGroup ivgroup; + uint32_t remainingbits = state; + + ivgroup.iv0 = (remainingbits & 0xfffff) % 32; + remainingbits = remainingbits >> 5; + ivgroup.iv1 = (remainingbits & 0xfffff) % 32; + remainingbits = remainingbits >> 5; + ivgroup.iv2 = (remainingbits & 0xfffff) % 32; + remainingbits = remainingbits >> 5; + + return ivgroup; +} + +AdvPokemonResult pokemon_from_state(AdvRngState& state, RngMethod method = RngMethod::Method1){ + uint32_t pid = pid_from_states(state.s0, state.s1); + uint8_t gender = gender_value_from_pid(pid); + Nature nature = nature_from_pid(pid); + Ability ability = ability_from_pid(pid); + + IvGroup ivgroup1; + IvGroup ivgroup2; + switch(method){ + case RngMethod::Method1: + ivgroup1 = iv_group_from_state(state.s2); + ivgroup2 = iv_group_from_state(state.s3); + break; + case RngMethod::Method2: + ivgroup1 = iv_group_from_state(state.s3); + ivgroup2 = iv_group_from_state(state.s4); + break; + case RngMethod::Method4: + ivgroup1 = iv_group_from_state(state.s2); + ivgroup2 = iv_group_from_state(state.s4); + break; + default: + break; + } + + IVs ivs; + ivs.hp = ivgroup1.iv0; + ivs.attack = ivgroup1.iv1; + ivs.defense = ivgroup1.iv2; + ivs.spatk = ivgroup2.iv0; + ivs.spdef = ivgroup2.iv1; + ivs.speed = ivgroup2.iv2; + + return AdvPokemonResult(pid, gender, nature, ability, ivs); +} + +ShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ + uint16_t pid0 = pid >> 16; + uint16_t pid1 = pid & 0xffff; + uint16_t pid_xor (pid0 ^ pid1); + + if (pid_xor == tid_xor_sid){ + return ShinyType::Square; + }else if (pid_xor ^ tid_xor_sid < 8){ + return ShinyType::Star; + }else{ + return ShinyType::Normal; + } +} + + +bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xor_sid, uint8_t gender_threshold){ + return (target.nature == Nature::Any || res.nature == target.nature) + && (target.ability == Ability::Any || res.ability == target.ability) + && (target.gender == Gender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) + && (target.shiny == ShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) + && (target.ivs.hp.low <= res.ivs.hp && target.ivs.hp.high >= res.ivs.hp) + && (target.ivs.attack.low <= res.ivs.attack && target.ivs.attack.high >= res.ivs.attack) + && (target.ivs.defense.low <= res.ivs.defense && target.ivs.defense.high >= res.ivs.defense) + && (target.ivs.spatk.low <= res.ivs.spatk && target.ivs.spatk.high >= res.ivs.spatk) + && (target.ivs.spdef.low <= res.ivs.spdef && target.ivs.spdef.high >= res.ivs.spdef) + && (target.ivs.speed.low <= res.ivs.speed && target.ivs.speed.high >= res.ivs.speed); +} + + + +AdvRng::AdvRng(uint16_t seed, AdvRngState state) + : seed(seed) + , state(state) +{} + +AdvRng::AdvRng(uint16_t seed, uint64_t min_advances) + : seed(seed) + , state(rngstate_from_seed(seed, min_advances)) +{} + +void AdvRng::advance_state(){ + advance_rng_state(state); +} + +void AdvRng::set_state_advances(uint64_t advances){ + state = rngstate_from_seed(seed, advances); +} + +void AdvRng::search_advance_range(std::map& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold ){ + for (int m=0; m<3; m++){ + set_state_advances(min_advances); + + RngMethod method; + switch (m){ + case 0: + method = RngMethod::Method1; + break; + case 1: + method = RngMethod::Method2; + break; + case 2: + method = RngMethod::Method4; + break; + default: + break; + } + + if ((target.method != RngMethod::Any) && (target.method != method)){ + continue; + } + + for (int a=min_advances; a AdvRng::search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold){ + std::map hits; + for (uint16_t seed : seeds){ + search_advance_range(hits, target, min_advances, max_advances, tid_xor_sid, gender_threshold); + } + return hits; +} + + +} +} diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h new file mode 100644 index 0000000000..9b3039d382 --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -0,0 +1,136 @@ +/* Advance RNG + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonSwSh_AdvRng_H +#define PokemonAutomation_PokemonSwSh_AdvRng_H + +#include +#include +#include +#include "Pokemon_StatsCalculation.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +struct IvGroup{ + uint8_t iv0; + uint8_t iv1; + uint8_t iv2; +}; + +struct IVs{ + uint8_t hp = 0; + uint8_t attack = 0; + uint8_t defense = 0; + uint8_t spatk = 0; + uint8_t spdef = 0; + uint8_t speed = 0; +}; + +enum class Nature{ + Hardy, + Lonely, + Brave, + Adamant, + Naughty, + Bold, + Docile, + Relaxed, + Impish, + Lax, + Timid, + Hasty, + Serious, + Jolly, + Naive, + Modest, + Mild, + Quiet, + Bashful, + Rash, + Calm, + Gentle, + Sassy, + Careful, + Quirky, + Any +}; + +enum class Gender{ + Male, + Female, + Any +}; + +enum class Ability{ + Zero, + One, + Any +}; + +enum class ShinyType{ + Normal, + Star, + Square, + Any +}; + + +struct AdvRngState{ + AdvRngState(uint16_t seed, uint64_t advance, uint32_t s0, uint32_t s1, uint32_t s2, uint32_t s3, uint32_t s4); + uint16_t seed; + uint64_t advance; + uint32_t s0; + uint32_t s1; + uint32_t s2; + uint32_t s3; + uint32_t s4; +}; + +enum class RngMethod{ + Method1, + Method2, + Method4, + Any +}; + +struct AdvPokemonResult{ + AdvPokemonResult(uint32_t pid, uint16_t gender, Nature nature, Ability ability, IVs ivs); + uint32_t pid; + uint8_t gender; + Nature nature; + Ability ability; + IVs ivs; +}; + +struct AdvRngFilters{ + Gender gender; + Nature nature; + Ability ability; + IvRanges ivs; + ShinyType shiny; + RngMethod method; +}; + +class AdvRng{ +public: + uint16_t seed; + AdvRngState state; + + std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); +private: + AdvRng(uint16_t seed, AdvRngState state); + AdvRng(uint16_t seed, uint64_t min_advances); + + void set_seed(uint16_t seed); + void set_state_advances(uint64_t advances); + void advance_state(); + void search_advance_range(std::map& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold); +}; + +} +} +#endif From 1afa8b5fff6d6fdd6ecb91e0f4f6089595d37572 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 16:33:24 -0500 Subject: [PATCH 02/21] update cmake files --- SerialPrograms/cmake/SourceFiles.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 2479d90f97..07ee7b3b1b 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1284,6 +1284,8 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Pokemon_Types.h Source/Pokemon/Pokemon_Xoroshiro128Plus.cpp Source/Pokemon/Pokemon_Xoroshiro128Plus.h + Source/Pokemon/Pokemon_AdvanceRng.cpp + Source/Pokemon/Pokemon_AdvanceRng.h Source/Pokemon/Resources/Pokemon_BerryNames.cpp Source/Pokemon/Resources/Pokemon_BerryNames.h Source/Pokemon/Resources/Pokemon_BerrySprites.cpp From f4120deb47ec37ee3e45e9d35585ab9274d7f821 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 19:03:24 -0500 Subject: [PATCH 03/21] trainer card detection and tid reading --- .../Menus/PokemonFRLG_TrainerCardDetector.cpp | 65 +++++++ .../Menus/PokemonFRLG_TrainerCardDetector.h | 52 ++++++ .../Inference/PokemonFRLG_TrainerIdReader.cpp | 159 ++++++++++++++++++ .../Inference/PokemonFRLG_TrainerIdReader.h | 47 ++++++ .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 + .../PokemonFRLG_ReadTrainerId.cpp | 70 ++++++++ .../TestPrograms/PokemonFRLG_ReadTrainerId.h | 42 +++++ SerialPrograms/cmake/SourceFiles.cmake | 6 + 8 files changed, 443 insertions(+) create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp new file mode 100644 index 0000000000..493da5e996 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp @@ -0,0 +1,65 @@ +/* Trainer Card Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_TrainerCardDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +TrainerCardDetector::TrainerCardDetector(Color color) + : m_box_tid_top(0.581250, 0.108413, 0.332692, 0.007212) // light blue (227, 255, 255) + , m_box_tid_right(0.914904, 0.117067, 0.005769, 0.064904) + , m_box_stripe_top(0.038942, 0.090384, 0.918269, 0.002885) // blue (94, 177, 255) + , m_box_stripe_right(0.954808, 0.095432, 0.002885, 0.109615) + , m_box_border_top(0.051442, 0.040625, 0.895192, 0.003606) // gray (100, 100, 120) + , m_box_border_right(0.968750, 0.075961, 0.002885, 0.848077) +{} +void TrainerCardDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_tid_top)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_tid_right)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_stripe_top)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_stripe_right)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_border_top)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_box_border_right)); +} +bool TrainerCardDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + ImageViewRGB32 image_tid_top = extract_box_reference(game_screen, m_box_tid_top); + ImageViewRGB32 image_tid_right = extract_box_reference(game_screen, m_box_tid_right); + ImageViewRGB32 image_stripe_top = extract_box_reference(game_screen, m_box_stripe_top); + ImageViewRGB32 image_stripe_right = extract_box_reference(game_screen, m_box_stripe_right); + ImageViewRGB32 image_border_top = extract_box_reference(game_screen, m_box_border_top); + ImageViewRGB32 image_border_right = extract_box_reference(game_screen, m_box_border_right); + + if ( is_solid(image_tid_top, { 0.308, 0.346, 0.346 }) + && is_solid(image_tid_right, { 0.308, 0.346, 0.346 }) + && is_solid(image_stripe_top, { 0.179, 0.337, 0.485 }) + && is_solid(image_stripe_right, { 0.179, 0.337, 0.485 }) + && is_solid(image_border_top, { 0.313, 0.313, 0.375 }) + && is_solid(image_border_right, { 0.313, 0.313, 0.375 }) + ){ + return true; + } + return false; +} + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h new file mode 100644 index 0000000000..d1ecc27a58 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h @@ -0,0 +1,52 @@ +/* Trainer Card Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_MapDetector_H +#define PokemonAutomation_PokemonFRLG_MapDetector_H + +#include +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonTools/VisualDetector.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" + +namespace PokemonAutomation{ + class CancellableScope; + class VideoFeed; +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +// Detect the map screen for Kanto +class TrainerCardDetector : public StaticScreenDetector{ +public: + TrainerCardDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_box_tid_top; + ImageFloatBox m_box_tid_right; + ImageFloatBox m_box_stripe_top; + ImageFloatBox m_box_stripe_right; + ImageFloatBox m_box_border_top; + ImageFloatBox m_box_border_right; +}; +class TrainerCardWatcher : public DetectorToFinder{ +public: + TrainerCardWatcher(Color color) + : DetectorToFinder("TrainerCardWatcher", std::chrono::milliseconds(250), color) + {} +}; + + + +} +} +} + +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp new file mode 100644 index 0000000000..1458a9fdec --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp @@ -0,0 +1,159 @@ +/* Trainer ID Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "PokemonFRLG_TrainerIdReader.h" +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/ImageManip.h" +#include "CommonTools/OCR/OCR_NumberReader.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_DigitReader.h" +#include + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonFRLG { + +// Debug counter for unique filenames +static int debug_counter = 0; + +// Full OCR preprocessing pipeline for GBA pixel fonts. +// +// GBA fonts are seven-segment-like with 1-pixel gaps between segments. +// Pipeline: blur at native -> smooth upscale -> BW -> smooth BW -> re-BW -> pad +// +// The native blur connects gaps. Post-BW padding provides margins. +static ImageRGB32 preprocess_for_ocr( + const ImageViewRGB32 &image, + int blur_kernel_size, int blur_passes, + bool in_range_black, uint32_t bw_min, + uint32_t bw_max +){ + const bool save_debug_images = GlobalSettings::instance().SAVE_DEBUG_IMAGES; + int id = debug_counter++; + std::string prefix = "DebugDumps/ocr_tid_" + std::to_string(id); + + // Save raw input + if (save_debug_images){ + image.save(prefix + "_0_raw.png"); + } + + cv::Mat src = image.to_opencv_Mat(); + + // Step 1: Gaussian blur at NATIVE resolution with 5x5 kernel. + cv::Mat blurred_native; + src.copyTo(blurred_native); + if (blur_kernel_size > 0 && blur_passes > 0){ + for (int i = 0; i < blur_passes; i++){ + cv::GaussianBlur( + blurred_native, blurred_native, + cv::Size(blur_kernel_size, blur_kernel_size), 1.5 + ); + } + } + + // Save blurred at native res + ImageRGB32 blurred_native_img(blurred_native.cols, blurred_native.rows); + blurred_native.copyTo(blurred_native_img.to_opencv_Mat()); + if (save_debug_images){ + blurred_native_img.save(prefix + "_1_blurred_native.png"); + } + + // Step 2: Smooth upscale 4x with bilinear interpolation. + int scale_factor = 4; + int new_w = static_cast(image.width()) * scale_factor; + int new_h = static_cast(image.height()) * scale_factor; + cv::Mat resized; + cv::resize( + blurred_native, resized, cv::Size(new_w, new_h), 0, 0, + cv::INTER_LINEAR + ); + + // Save upscaled + ImageRGB32 resized_img(resized.cols, resized.rows); + resized.copyTo(resized_img.to_opencv_Mat()); + if (save_debug_images){ + resized_img.save(prefix + "_2_upscaled.png"); + } + + // Step 3: BW threshold on the smooth upscaled image. + ImageRGB32 bw = + to_blackwhite_rgb32_range(resized_img, in_range_black, bw_min, bw_max); + if (save_debug_images){ + bw.save(prefix + "_3_bw.png"); + } + + // Step 4: Post-BW smoothing -> re-threshold. + cv::Mat bw_mat = bw.to_opencv_Mat(); + cv::Mat smoothed; + cv::GaussianBlur(bw_mat, smoothed, cv::Size(7, 7), 2.0); + + // Re-threshold: convert smoothed back to ImageRGB32 and BW threshold. + ImageRGB32 smoothed_img(smoothed.cols, smoothed.rows); + smoothed.copyTo(smoothed_img.to_opencv_Mat()); + ImageRGB32 smooth_bw = to_blackwhite_rgb32_range( + smoothed_img, true, combine_rgb(0, 0, 0), combine_rgb(128, 128, 128)); + if (save_debug_images){ + smooth_bw.save(prefix + "_4_smooth_bw.png"); + } + + // Step 5: Pad with white border (Tesseract needs margins). + ImageRGB32 padded = pad_image(smooth_bw, smooth_bw.height() / 2, 0xffffffff); + if (save_debug_images){ + padded.save(prefix + "_5_padded.png"); + } + + return padded; +} + +TrainerIdReader::TrainerIdReader(Color color) + : m_color(color) + , m_box_tid(0.742683, 0.117314, 0.129734, 0.076006) +{} + +void TrainerIdReader::make_overlays(VideoOverlaySet &items) const { + const BoxOption &GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box_tid)); +} + +uint16_t TrainerIdReader::read_tid( + Logger &logger, const ImageViewRGB32 &frame +){ + ImageViewRGB32 game_screen = + extract_box_reference(frame, GameSettings::instance().GAME_BOX); + + + ImageViewRGB32 tid_region = extract_box_reference(game_screen, m_box_tid); + + if (!GlobalSettings::instance().USE_PADDLE_OCR){ + // Tesseract-free path: waterfill segmentation + template matching + // against the PokemonFRLG/Digits/0-9.png templates. + return uint16_t(read_digits_waterfill_template(logger, tid_region)); + } + + // PaddleOCR path (original): preprocess then per-digit waterfill OCR. + ImageRGB32 ocr_ready = preprocess_for_ocr( + tid_region, 7, 2, true, + combine_rgb(0, 0, 0), combine_rgb(190, 190, 190) + ); + + // Waterfill isolates each digit -> per-char SINGLE_CHAR OCR. + return uint16_t(OCR::read_number_waterfill( + logger, ocr_ready, 0xff000000, + 0xff808080 + )); +} + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h new file mode 100644 index 0000000000..e8b00cf8cc --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h @@ -0,0 +1,47 @@ +/* Trainer ID Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_TrainerIdReader_H +#define PokemonAutomation_PokemonFRLG_TrainerIdReader_H + +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" + + +namespace PokemonAutomation{ + +class Logger; +class ImageViewRGB32; +class VideoOverlaySet; + +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class TrainerIdReader { +public: + TrainerIdReader(Color color = COLOR_RED); + + void make_overlays(VideoOverlaySet &items) const; + + // Reads the Trainer ID on the Trainer Card + uint16_t read_tid( + Logger &logger, const ImageViewRGB32 &frame + ); + +private: + Color m_color; + ImageFloatBox m_box_tid; + +}; + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif + diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index b6686e6b30..606ed485df 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -20,6 +20,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" +#include "Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h" #include "Programs/TestPrograms/PokemonFRLG_ReadEncounter.h" namespace PokemonAutomation{ @@ -61,6 +62,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Developer Tools ----"); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp new file mode 100644 index 0000000000..04d7ec2ae9 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp @@ -0,0 +1,70 @@ +/* Read Trainer ID + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h" +#include "PokemonFRLG_ReadTrainerId.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace std::chrono_literals; + + +ReadTrainerId_Descriptor::ReadTrainerId_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:ReadTrainerId", + Pokemon::STRING_POKEMON + " FRLG", + "Read TID", "", + "Read TID from the trainer card.", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS +){} + +ReadTrainerId::ReadTrainerId() +{} + +void ReadTrainerId::program( + SingleSwitchProgramEnvironment &env, + ProControllerContext &context +){ + env.log( + "Starting Read Trainer ID program... Please ensure you are on the Trainer Card." + ); + + TrainerCardDetector detector(COLOR_RED); + TrainerIdReader reader; + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + VideoSnapshot screen = env.console.video().snapshot(); + bool trainercard = detector.detect(screen); + if (trainercard){ + env.log("Trainer Card detected."); + env.log("Reading TID..."); + uint16_t tid = reader.read_tid(env.logger(), screen); + env.log("TID: " + tid); + }else{ + env.log("Trainer Card not detected!"); + } + context.wait_for_all_requests(); +} + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h new file mode 100644 index 0000000000..5a0e533f03 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h @@ -0,0 +1,42 @@ +/* Read Trainer Id + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_ReadTrainerId_H +#define PokemonAutomation_PokemonFRLG_ReadTrainerId_H + +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonTools/Options/LanguageOCROption.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class ReadTrainerId_Descriptor : public SingleSwitchProgramDescriptor{ +public: + ReadTrainerId_Descriptor(); +}; + +class ReadTrainerId : public SingleSwitchProgramInstance{ +public: + ReadTrainerId(); + virtual void program( + SingleSwitchProgramEnvironment &env, + ProControllerContext &context + ) override; + + virtual void start_program_border_check( + VideoStream &stream, FeedbackType feedback_type + ) override{} + +}; + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif + diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 2479d90f97..5896270303 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1456,6 +1456,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp @@ -1466,6 +1468,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h + Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp @@ -1498,6 +1502,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.h + Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp + Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h Source/PokemonHome/Inference/PokemonHome_BallReader.cpp From 25522ff305ebf954d270a4570a0aaad1561e2b6c Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 21:08:26 -0500 Subject: [PATCH 04/21] fix new files in cmake --- SerialPrograms/cmake/SourceFiles.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 07ee7b3b1b..e9dc45dea9 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1284,8 +1284,8 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Pokemon_Types.h Source/Pokemon/Pokemon_Xoroshiro128Plus.cpp Source/Pokemon/Pokemon_Xoroshiro128Plus.h - Source/Pokemon/Pokemon_AdvanceRng.cpp - Source/Pokemon/Pokemon_AdvanceRng.h + Source/Pokemon/Pokemon_AdvRng.cpp + Source/Pokemon/Pokemon_AdvRng.h Source/Pokemon/Resources/Pokemon_BerryNames.cpp Source/Pokemon/Resources/Pokemon_BerryNames.h Source/Pokemon/Resources/Pokemon_BerrySprites.cpp From 42b79ffeaf16b420ce54c4aa7e71adaf7d0e7ad6 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 21:50:26 -0500 Subject: [PATCH 05/21] finish up a few missed things --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 27 ++++++++++++------- .../Source/Pokemon/Pokemon_AdvRng.h | 24 ++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 44c5168b7e..2a7c46a1b7 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -1,4 +1,4 @@ -/* Xoroshiro128+ and reverse +/* Adv RNG * * From: https://github.com/PokemonAutomation/ * @@ -14,23 +14,24 @@ uint32_t increment_internal_rng_state(uint32_t& state){ return state * 0x41c64e6d + 0x6073; } -AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint32_t& state){ +AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint32_t& state, RngMethod method){ uint32_t s0 = state; uint32_t s1 = increment_internal_rng_state(s0); uint32_t s2 = increment_internal_rng_state(s1); uint32_t s3 = increment_internal_rng_state(s2); uint32_t s4 = increment_internal_rng_state(s3); - return AdvRngState(seed, advances, s0, s1, s2, s3, s4); + return AdvRngState(seed, advances, method, s0, s1, s2, s3, s4); } -AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t& advances){ +AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t advances, RngMethod method){ uint32_t state = seed; - for (int i=0; i& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold ){ +void AdvRng::search_advance_range(std::map& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold){ for (int m=0; m<3; m++){ set_state_advances(min_advances); @@ -196,6 +202,7 @@ void AdvRng::search_advance_range(std::map& hits, std::map AdvRng::search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold){ std::map hits; for (uint16_t seed : seeds){ + set_seed(seed); search_advance_range(hits, target, min_advances, max_advances, tid_xor_sid, gender_threshold); } return hits; diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 9b3039d382..8e59b67599 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -1,11 +1,11 @@ -/* Advance RNG +/* Adv RNG * * From: https://github.com/PokemonAutomation/ * */ -#ifndef PokemonAutomation_PokemonSwSh_AdvRng_H -#define PokemonAutomation_PokemonSwSh_AdvRng_H +#ifndef Pokemon_AdvRng_H +#define Pokemon_AdvRng_H #include #include @@ -78,11 +78,18 @@ enum class ShinyType{ Any }; +enum class RngMethod{ + Method1, + Method2, + Method4, + Any +}; struct AdvRngState{ - AdvRngState(uint16_t seed, uint64_t advance, uint32_t s0, uint32_t s1, uint32_t s2, uint32_t s3, uint32_t s4); + AdvRngState(uint16_t seed, uint64_t advance, RngMethod method, uint32_t s0, uint32_t s1, uint32_t s2, uint32_t s3, uint32_t s4); uint16_t seed; uint64_t advance; + RngMethod method; uint32_t s0; uint32_t s1; uint32_t s2; @@ -90,13 +97,6 @@ struct AdvRngState{ uint32_t s4; }; -enum class RngMethod{ - Method1, - Method2, - Method4, - Any -}; - struct AdvPokemonResult{ AdvPokemonResult(uint32_t pid, uint16_t gender, Nature nature, Ability ability, IVs ivs); uint32_t pid; @@ -123,7 +123,7 @@ class AdvRng{ std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); private: AdvRng(uint16_t seed, AdvRngState state); - AdvRng(uint16_t seed, uint64_t min_advances); + AdvRng(uint16_t seed, uint64_t min_advances, RngMethod method = RngMethod::Method1); void set_seed(uint16_t seed); void set_state_advances(uint64_t advances); From 2e316f672d7d50dd36aa269157c1b11a96ddee57 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 21:53:47 -0500 Subject: [PATCH 06/21] make constructors public --- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 8e59b67599..690b2f182a 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -120,11 +120,11 @@ class AdvRng{ uint16_t seed; AdvRngState state; - std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); -private: AdvRng(uint16_t seed, AdvRngState state); AdvRng(uint16_t seed, uint64_t min_advances, RngMethod method = RngMethod::Method1); + std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); +private: void set_seed(uint16_t seed); void set_state_advances(uint64_t advances); void advance_state(); From 536000a954b874f926ff13c89f6357815ddc6c82 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 21:56:36 -0500 Subject: [PATCH 07/21] wip commit --- .../ShinyHunting/PokemonFRLG_SidHelper.cpp | 240 ++++++++++++++++++ .../ShinyHunting/PokemonFRLG_SidHelper.h | 56 ++++ 2 files changed, 296 insertions(+) create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp new file mode 100644 index 0000000000..380c9785b7 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp @@ -0,0 +1,240 @@ +/* SID Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Pokemon_AdvRng.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" +#include "PokemonFRLG_SidHelper.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +SidHelper_Descriptor::SidHelper_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:SidHelper", + Pokemon::STRING_POKEMON + " FRLG", "Nugget Bridge Farmer", + "Programs/PokemonFRLG/SidHelper.html", + "Farm the Nugget Bridge for money.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct SidHelper_Descriptor::Stats : public StatsTracker { + public: + Stats() + : errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + + std::atomic& errors; +}; + +std::unique_ptr SidHelper_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +SidHelper::SidHelper() + : TARGET_FRAME( + "Target Frame:
" + "The target frame for finalizing the SID. This is arbitrary unless you're attempting to hit a specific TID/SID combination
" + "This value should always be odd", + LockMode::LOCK_WHILE_RUNNING, + 2301, 2000 // default, min + ) + , NUM_CANDIDATES( + "# Candidate SIDs:
" + "The number of SIDs to list near the target", + LockMode::LOCK_WHILE_RUNNING, + 5, 1, 11 // default, min, max + ) + , GO_HOME_WHEN_DONE(false) + , NOTIFICATION_SIDS("SID Candidates", true, true) + , NOTIFICATIONS({ + &NOTIFICATION_SIDS, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_FATAL, + }) +{ + PA_ADD_OPTION(TARGET_FRAME); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +namespace{ + +void set_sid_from_name_screen(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const uint64_t& SID_DELAY){ + // this is performed blind to try to maximize consistency of timing + // ensure the OK button is selected + pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); + // confirm name (SID delay starts now) + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + // advance to "...about to unfold!", picking the default rival name + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_move_left_joystick(context, {0,-1}, 200ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(SID_DELAY - 15700)); // 2000 + 3000 + 1500 + 2000 + 500 + 1500 + 2000 + 3000 + 200 ms + // finish dialogue and lock in SID + pbf_press_button(context, BUTTON_A, 200ms, 0ms); + context.wait_for_all_requests(); +} + +void finish_intro_animations(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + BlackScreenWatcher black_screen; + context.wait_for_all_requests(); + int ret = wait_until( + env.console, context, 10s, + { black_screen } + ); + + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): black screen not detected within 10 seconds of setting SID", env.console + ); + } + + BlackScreenOverWatcher black_screen_over; + context.wait_for_all_requests(); + int ret2 = wait_until( + env.console, context, 10s, + { black_screen_over } + ); + + if (ret2 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): black screen lasted longer than 10 seconds", env.console + ); + } + + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); +} + +void navigate_to_trainer_card(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + + int errors = 0; + while (true){ + if (errors >= 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): failed 5 times to navigate to the Trainer Card", env.console + ); + } + + open_start_menu(env.console, context); + + bool cursor = move_cursor_to_position(env.console, context, SelectionArrowPositionStartMenu(1)); // the positions are different before recieving a Pokemon and Pokedex + if (!cursor){ + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + continue; + } + + TrainerCardWatcher trainer_card(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); + }, + { trainer_card } + ); + + if (ret < 0){ + env.log("SidHelper(): failed to detect Trainer Card"); + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + continue; + } + + break; + } +} + +uint16_t read_tid(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + TrainerIdReader reader; + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + VideoSnapshot screen = env.console.video().snapshot(); + env.log("Trainer Card detected."); + env.log("Reading TID..."); + uint16_t tid = reader.read_tid(env.logger(), screen); + env.log("TID: " + tid); + + context.wait_for_all_requests(); +} + +std::vector> get_sid_messages(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint16_t tid, SimpleIntegerOption& TARGET_FRAME, SimpleIntegerOption& NUM_CANDIDATES){ + std::vector> messages; + + int start = -1 * (NUM_CANDIDATES - 1) / 2; + int end = start + NUM_CANDIDATES; + + AdvRng rng = AdvRng(tid, start); + + for (int i=start; i m; + + m.first = std::to_string(i); + } +} + +} // namespace + +void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + if (TARGET_FRAME % 2 == 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): Target Frame needs to be odd", env.console + ); + } + + home_black_border_check(env.console, context); + + SidHelper_Descriptor::Stats& stats = env.current_stats(); + + double FRAMERATE = 59.999977; // FPS + double FRAME_DURATION = 1000 / FRAMERATE; // ms + + const uint64_t SID_DELAY = TARGET_FRAME * FRAME_DURATION; + + set_sid_from_name_screen(env, context, SID_DELAY); + finish_intro_animations(env, context); + navigate_to_trainer_card(env, context); + + uint16_t tid = read_tid(env, context); + + std::vector> sid_messages = get_sid_messages(env, context, tid, TARGET_FRAME, NUM_CANDIDATES); + + send_program_notification(env, NOTIFICATION_SIDS, COLOR_BLUE, "Possible SIDs:", + sid_messages, ""); + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + GO_HOME_WHEN_DONE.run_end_of_program(context); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h new file mode 100644 index 0000000000..f093722080 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h @@ -0,0 +1,56 @@ +/* SID Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_SidHelper_H +#define PokemonAutomation_PokemonFRLG_SidHelper_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/ButtonOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" + + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class SidHelper_Descriptor : public SingleSwitchProgramDescriptor{ +public: + SidHelper_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class SidHelper : public SingleSwitchProgramInstance { +public: + SidHelper(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override { + } + +private: + + SimpleIntegerOption TARGET_FRAME; + SimpleIntegerOption NUM_CANDIDATES; + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SIDS; + EventNotificationsOption NOTIFICATIONS; + +}; + +} +} +} + + + + +#endif \ No newline at end of file From 88695a17503c7949987c2db129849b5b8a3d4fb1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 21:56:56 -0500 Subject: [PATCH 08/21] cmake --- SerialPrograms/cmake/SourceFiles.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index c293f3918a..ab87fc40a2 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1500,6 +1500,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp From 68c25b45dae29c4057454a0038d7b39ebd851eee Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:11:40 -0500 Subject: [PATCH 09/21] expose more public methods --- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 690b2f182a..aeba61fb33 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -123,11 +123,12 @@ class AdvRng{ AdvRng(uint16_t seed, AdvRngState state); AdvRng(uint16_t seed, uint64_t min_advances, RngMethod method = RngMethod::Method1); - std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); -private: void set_seed(uint16_t seed); void set_state_advances(uint64_t advances); void advance_state(); + + std::map search(AdvRngFilters& target, std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, uint8_t gender_threshold = 126); +private: void search_advance_range(std::map& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold); }; From 72b37b21ec2b631cf1327967658060abbefb6ea7 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:14:18 -0500 Subject: [PATCH 10/21] another wip commit --- .../ShinyHunting/PokemonFRLG_SidHelper.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp index 380c9785b7..2dcfec6ba2 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp @@ -192,13 +192,21 @@ std::vector> get_sid_messages(SingleSwitchPr int start = -1 * (NUM_CANDIDATES - 1) / 2; int end = start + NUM_CANDIDATES; - AdvRng rng = AdvRng(tid, start); + Pokemon::AdvRng rng(tid, TARGET_FRAME + start); for (int i=start; i m; + uint16_t sid = rng.state.s0 >> 16; - m.first = std::to_string(i); + m.first = std::to_string(rng.state.advance); + m.second = std::to_string(sid); + messages.push_back(m); + + rng.advance_state(); + rng.advance_state(); // 2 by 2 } + + return messages; } } // namespace @@ -227,8 +235,7 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex std::vector> sid_messages = get_sid_messages(env, context, tid, TARGET_FRAME, NUM_CANDIDATES); - send_program_notification(env, NOTIFICATION_SIDS, COLOR_BLUE, "Possible SIDs:", - sid_messages, ""); + send_program_notification(env, NOTIFICATION_SIDS, COLOR_BLUE, "Possible SIDs:", sid_messages, ""); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); GO_HOME_WHEN_DONE.run_end_of_program(context); From 3178b8b8e3bcd3702869b7b67289e49a82ce4edc Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:15:07 -0500 Subject: [PATCH 11/21] fix order of operations for shiny type --- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 2a7c46a1b7..e1a3f56759 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -120,7 +120,7 @@ ShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ if (pid_xor == tid_xor_sid){ return ShinyType::Square; - }else if (pid_xor ^ tid_xor_sid < 8){ + }else if ((pid_xor ^ tid_xor_sid) < 8){ return ShinyType::Star; }else{ return ShinyType::Normal; From b52e9ba2a2a7d83ae0b3edcced4df0666573a751 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:17:15 -0500 Subject: [PATCH 12/21] fix int comparisons --- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index e1a3f56759..0317c65f64 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -27,7 +27,7 @@ AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint3 AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t advances, RngMethod method){ uint32_t state = seed; state = increment_internal_rng_state(state); - for (int i=0; i& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, uint8_t gender_threshold){ - for (int m=0; m<3; m++){ + for (uint8_t m=0; m<3; m++){ set_state_advances(min_advances); RngMethod method; @@ -189,7 +189,7 @@ void AdvRng::search_advance_range(std::map& hits, continue; } - for (int a=min_advances; a Date: Sun, 12 Apr 2026 22:24:25 -0500 Subject: [PATCH 13/21] small fixes --- .../Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp index 2dcfec6ba2..d766e01aa5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp @@ -181,9 +181,11 @@ uint16_t read_tid(SingleSwitchProgramEnvironment& env, ProControllerContext& con env.log("Trainer Card detected."); env.log("Reading TID..."); uint16_t tid = reader.read_tid(env.logger(), screen); - env.log("TID: " + tid); + env.log("TID: " + std::to_string(tid)); context.wait_for_all_requests(); + + return tid; } std::vector> get_sid_messages(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint16_t tid, SimpleIntegerOption& TARGET_FRAME, SimpleIntegerOption& NUM_CANDIDATES){ @@ -220,12 +222,12 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex home_black_border_check(env.console, context); - SidHelper_Descriptor::Stats& stats = env.current_stats(); + // SidHelper_Descriptor::Stats& stats = env.current_stats(); double FRAMERATE = 59.999977; // FPS double FRAME_DURATION = 1000 / FRAMERATE; // ms - const uint64_t SID_DELAY = TARGET_FRAME * FRAME_DURATION; + const uint64_t SID_DELAY = uint64_t(TARGET_FRAME * FRAME_DURATION); set_sid_from_name_screen(env, context, SID_DELAY); finish_intro_animations(env, context); From c6811f20a20f144ba8131246f845e3c014a47ce5 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:47:21 -0500 Subject: [PATCH 14/21] more fixes --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 17 ++++++++--------- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h | 7 +++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 0317c65f64..2a8971ea40 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -21,7 +21,7 @@ AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint3 uint32_t s3 = increment_internal_rng_state(s2); uint32_t s4 = increment_internal_rng_state(s3); - return AdvRngState(seed, advances, method, s0, s1, s2, s3, s4); + return {seed, advances, method, s0, s1, s2, s3, s4}; } AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t advances, RngMethod method){ @@ -86,10 +86,7 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, RngMethod method = RngMe IvGroup ivgroup1; IvGroup ivgroup2; switch(method){ - case RngMethod::Method1: - ivgroup1 = iv_group_from_state(state.s2); - ivgroup2 = iv_group_from_state(state.s3); - break; + case RngMethod::Method2: ivgroup1 = iv_group_from_state(state.s3); ivgroup2 = iv_group_from_state(state.s4); @@ -98,7 +95,10 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, RngMethod method = RngMe ivgroup1 = iv_group_from_state(state.s2); ivgroup2 = iv_group_from_state(state.s4); break; + case RngMethod::Method1: default: + ivgroup1 = iv_group_from_state(state.s2); + ivgroup2 = iv_group_from_state(state.s3); break; } @@ -110,7 +110,7 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, RngMethod method = RngMe ivs.spdef = ivgroup2.iv1; ivs.speed = ivgroup2.iv2; - return AdvPokemonResult(pid, gender, nature, ability, ivs); + return {pid, gender, nature, ability, ivs}; } ShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ @@ -172,16 +172,15 @@ void AdvRng::search_advance_range(std::map& hits, RngMethod method; switch (m){ - case 0: - method = RngMethod::Method1; - break; case 1: method = RngMethod::Method2; break; case 2: method = RngMethod::Method4; break; + case 0: default: + method = RngMethod::Method1; break; } diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index aeba61fb33..17e1434eb9 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -86,7 +86,6 @@ enum class RngMethod{ }; struct AdvRngState{ - AdvRngState(uint16_t seed, uint64_t advance, RngMethod method, uint32_t s0, uint32_t s1, uint32_t s2, uint32_t s3, uint32_t s4); uint16_t seed; uint64_t advance; RngMethod method; @@ -95,10 +94,14 @@ struct AdvRngState{ uint32_t s2; uint32_t s3; uint32_t s4; + + bool operator<(const AdvRngState& rhs) const noexcept + { + return this->advance < rhs.advance; + } }; struct AdvPokemonResult{ - AdvPokemonResult(uint32_t pid, uint16_t gender, Nature nature, Ability ability, IVs ivs); uint32_t pid; uint8_t gender; Nature nature; From 844cf076f5be0f3073bff28b7fd892b3b28b8b00 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 22:51:17 -0500 Subject: [PATCH 15/21] fix panels --- SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 ++ .../Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 606ed485df..821bce4700 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -18,6 +18,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" +#include "Programs/ShinyHunting/PokemonFRLG_SidHelper.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h" @@ -43,6 +44,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ + ret.emplace_back(make_single_switch_program()); } //ret.emplace_back("---- General ----"); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp index d766e01aa5..c2ae421336 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp @@ -27,9 +27,9 @@ namespace PokemonFRLG{ SidHelper_Descriptor::SidHelper_Descriptor() : SingleSwitchProgramDescriptor( "PokemonFRLG:SidHelper", - Pokemon::STRING_POKEMON + " FRLG", "Nugget Bridge Farmer", + Pokemon::STRING_POKEMON + " FRLG", "SID Helper", "Programs/PokemonFRLG/SidHelper.html", - "Farm the Nugget Bridge for money.", + "Hit a specific frame when starting a new game and determine the corresponding SID.", ProgramControllerClass::StandardController_RequiresPrecision, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS From a650f4b815547f59eba54a42d2122ca1d311ccbe Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 23:20:34 -0500 Subject: [PATCH 16/21] a few tweaks --- .../ShinyHunting/PokemonFRLG_SidHelper.cpp | 33 ++++++++++--------- .../ShinyHunting/PokemonFRLG_SidHelper.h | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp index c2ae421336..d43b157fa6 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp @@ -29,7 +29,7 @@ SidHelper_Descriptor::SidHelper_Descriptor() "PokemonFRLG:SidHelper", Pokemon::STRING_POKEMON + " FRLG", "SID Helper", "Programs/PokemonFRLG/SidHelper.html", - "Hit a specific frame when starting a new game and determine the corresponding SID.", + "Hit a specific RNG advance when starting a new game and determine the corresponding SID.", ProgramControllerClass::StandardController_RequiresPrecision, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS @@ -52,16 +52,16 @@ std::unique_ptr SidHelper_Descriptor::make_stats() const{ } SidHelper::SidHelper() - : TARGET_FRAME( - "Target Frame:
" - "The target frame for finalizing the SID. This is arbitrary unless you're attempting to hit a specific TID/SID combination
" - "This value should always be odd", + : TARGET_ADVANCES( + "Target Advances:
" + "The target advances for finalizing the SID. This is arbitrary unless you're attempting to hit a specific TID/SID combination.
" + "This value should always be odd.", LockMode::LOCK_WHILE_RUNNING, 2301, 2000 // default, min ) , NUM_CANDIDATES( "# Candidate SIDs:
" - "The number of SIDs to list near the target", + "The number of SIDs to list near the target.", LockMode::LOCK_WHILE_RUNNING, 5, 1, 11 // default, min, max ) @@ -73,7 +73,8 @@ SidHelper::SidHelper() &NOTIFICATION_ERROR_FATAL, }) { - PA_ADD_OPTION(TARGET_FRAME); + PA_ADD_OPTION(TARGET_ADVANCES); + PA_ADD_OPTION(NUM_CANDIDATES); PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); } @@ -188,22 +189,24 @@ uint16_t read_tid(SingleSwitchProgramEnvironment& env, ProControllerContext& con return tid; } -std::vector> get_sid_messages(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint16_t tid, SimpleIntegerOption& TARGET_FRAME, SimpleIntegerOption& NUM_CANDIDATES){ +std::vector> get_sid_messages(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint16_t tid, SimpleIntegerOption& TARGET_ADVANCES, SimpleIntegerOption& NUM_CANDIDATES){ std::vector> messages; - int start = -1 * (NUM_CANDIDATES - 1) / 2; + int start = -1 * NUM_CANDIDATES / 2; int end = start + NUM_CANDIDATES; - Pokemon::AdvRng rng(tid, TARGET_FRAME + start); + Pokemon::AdvRng rng(tid, TARGET_ADVANCES + 2*start); for (int i=start; i m; uint16_t sid = rng.state.s0 >> 16; - m.first = std::to_string(rng.state.advance); + m.first = "Advances " + std::to_string(rng.state.advance); m.second = std::to_string(sid); messages.push_back(m); + env.log(m.first + ": " + m.second); + rng.advance_state(); rng.advance_state(); // 2 by 2 } @@ -214,9 +217,9 @@ std::vector> get_sid_messages(SingleSwitchPr } // namespace void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - if (TARGET_FRAME % 2 == 0){ + if (TARGET_ADVANCES % 2 == 0){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, "SidHelper(): Target Frame needs to be odd", env.console + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): the Target Advances setting needs to be odd", env.console ); } @@ -227,7 +230,7 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex double FRAMERATE = 59.999977; // FPS double FRAME_DURATION = 1000 / FRAMERATE; // ms - const uint64_t SID_DELAY = uint64_t(TARGET_FRAME * FRAME_DURATION); + const uint64_t SID_DELAY = uint64_t((TARGET_ADVANCES - 1) * FRAME_DURATION / 2); // advances pass 2 by 2, first one doesn't count (?) set_sid_from_name_screen(env, context, SID_DELAY); finish_intro_animations(env, context); @@ -235,7 +238,7 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex uint16_t tid = read_tid(env, context); - std::vector> sid_messages = get_sid_messages(env, context, tid, TARGET_FRAME, NUM_CANDIDATES); + std::vector> sid_messages = get_sid_messages(env, context, tid, TARGET_ADVANCES, NUM_CANDIDATES); send_program_notification(env, NOTIFICATION_SIDS, COLOR_BLUE, "Possible SIDs:", sid_messages, ""); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h index f093722080..7a2a1fdb19 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h @@ -37,7 +37,7 @@ class SidHelper : public SingleSwitchProgramInstance { private: - SimpleIntegerOption TARGET_FRAME; + SimpleIntegerOption TARGET_ADVANCES; SimpleIntegerOption NUM_CANDIDATES; GoHomeWhenDoneOption GO_HOME_WHEN_DONE; From 854447ba6a3de552a44b55b5bef6e23b7c36dbc8 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 12 Apr 2026 23:23:04 -0500 Subject: [PATCH 17/21] small style fix --- SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 17e1434eb9..7b86c159f7 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -95,8 +95,7 @@ struct AdvRngState{ uint32_t s3; uint32_t s4; - bool operator<(const AdvRngState& rhs) const noexcept - { + bool operator<(const AdvRngState& rhs) const noexcept{ return this->advance < rhs.advance; } }; From 189e63c6db50e4f9bc2a650bf8efb9046d7c07d2 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 13 Apr 2026 00:18:45 -0500 Subject: [PATCH 18/21] fix small typo --- .../Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h index 5a0e533f03..fff3c2c268 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h @@ -1,4 +1,4 @@ -/* Read Trainer Id +/* Read Trainer ID * * From: https://github.com/PokemonAutomation/ * From c079530add83eb604b44cea482d97d757b93621c Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 13 Apr 2026 00:38:24 -0500 Subject: [PATCH 19/21] fix string addition --- .../Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp index 04d7ec2ae9..a2d365f1c9 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.cpp @@ -57,7 +57,7 @@ void ReadTrainerId::program( env.log("Trainer Card detected."); env.log("Reading TID..."); uint16_t tid = reader.read_tid(env.logger(), screen); - env.log("TID: " + tid); + env.log("TID: " + std::to_string(tid)); }else{ env.log("Trainer Card not detected!"); } From 5b8e187d5518701d9239500f2e5b585522fc946e Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 20 Apr 2026 23:11:31 -0500 Subject: [PATCH 20/21] update with RNG code reorganization --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 77 ++++++++++++------- .../Source/Pokemon/Pokemon_AdvRng.h | 21 +++-- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 5 +- .../PokemonFRLG_SidHelper.cpp | 10 +-- .../PokemonFRLG_SidHelper.h | 0 SerialPrograms/cmake/SourceFiles.cmake | 2 + 6 files changed, 73 insertions(+), 42 deletions(-) rename SerialPrograms/Source/PokemonFRLG/Programs/{ShinyHunting => RngManipulation}/PokemonFRLG_SidHelper.cpp (94%) rename SerialPrograms/Source/PokemonFRLG/Programs/{ShinyHunting => RngManipulation}/PokemonFRLG_SidHelper.h (100%) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 9a94b8864f..cd9a710d2a 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -11,6 +11,23 @@ namespace PokemonAutomation{ namespace Pokemon{ +void level_up_observed_pokemon(AdvObservedPokemon& pokemon, StatReads& newstats, EVs& evyield){ + uint8_t newlevel = pokemon.level.back() + 1; + pokemon.level.emplace_back(newlevel); + + pokemon.stats.emplace_back(newstats); + + EVs old_evs = pokemon.evs.back(); + EVs new_evs; + new_evs.hp = old_evs.hp + evyield.hp; + new_evs.attack = old_evs.attack + evyield.attack; + new_evs.defense = old_evs.defense + evyield.defense; + new_evs.spatk = old_evs.spatk + evyield.spatk; + new_evs.spdef = old_evs.spdef + evyield.spdef; + new_evs.speed = old_evs.speed + evyield.speed; + pokemon.evs.emplace_back(new_evs); +} + uint32_t increment_internal_rng_state(uint32_t& state){ return state * 0x41c64e6d + 0x6073; } @@ -66,19 +83,18 @@ AdvAbility ability_from_pid(uint32_t& pid){ AdvIvGroup iv_group_from_state(uint32_t& state){ AdvIvGroup ivgroup; - uint32_t remainingbits = state; + uint32_t remainingbits = state >> 16; - ivgroup.iv0 = (remainingbits & 0xfffff) % 32; - remainingbits = remainingbits >> 5; - ivgroup.iv1 = (remainingbits & 0xfffff) % 32; + ivgroup.iv0 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; - ivgroup.iv2 = (remainingbits & 0xfffff) % 32; + ivgroup.iv1 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; + ivgroup.iv2 = remainingbits & 0x1f; return ivgroup; } -AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = AdvRngMethod::Method1){ +AdvPokemonResult pokemon_from_state(AdvRngState& state){ uint32_t pid = pid_from_states(state.s0, state.s1); uint8_t gender = gender_value_from_pid(pid); AdvNature nature = nature_from_pid(pid); @@ -86,7 +102,7 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad AdvIvGroup ivgroup1; AdvIvGroup ivgroup2; - switch(method){ + switch(state.method){ case AdvRngMethod::Method2: ivgroup1 = iv_group_from_state(state.s3); @@ -107,9 +123,9 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad ivs.hp = ivgroup1.iv0; ivs.attack = ivgroup1.iv1; ivs.defense = ivgroup1.iv2; - ivs.spatk = ivgroup2.iv0; - ivs.spdef = ivgroup2.iv1; - ivs.speed = ivgroup2.iv2; + ivs.speed = ivgroup2.iv0; + ivs.spatk = ivgroup2.iv1; + ivs.spdef = ivgroup2.iv2; return {pid, gender, nature, ability, ivs}; } @@ -134,40 +150,44 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xo && (target.ability == AdvAbility::Any || res.ability == target.ability) && (target.gender == AdvGender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) && (target.shiny == AdvShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) - && (target.ivs.hp.low <= res.ivs.hp && target.ivs.hp.high >= res.ivs.hp) - && (target.ivs.attack.low <= res.ivs.attack && target.ivs.attack.high >= res.ivs.attack) - && (target.ivs.defense.low <= res.ivs.defense && target.ivs.defense.high >= res.ivs.defense) - && (target.ivs.spatk.low <= res.ivs.spatk && target.ivs.spatk.high >= res.ivs.spatk) - && (target.ivs.spdef.low <= res.ivs.spdef && target.ivs.spdef.high >= res.ivs.spdef) - && (target.ivs.speed.low <= res.ivs.speed && target.ivs.speed.high >= res.ivs.speed); + && ((target.ivs.hp.low <= res.ivs.hp) && (target.ivs.hp.high >= res.ivs.hp)) + && ((target.ivs.attack.low <= res.ivs.attack) && (target.ivs.attack.high >= res.ivs.attack)) + && ((target.ivs.defense.low <= res.ivs.defense) && (target.ivs.defense.high >= res.ivs.defense)) + && ((target.ivs.spatk.low <= res.ivs.spatk) && (target.ivs.spatk.high >= res.ivs.spatk)) + && ((target.ivs.spdef.low <= res.ivs.spdef) && (target.ivs.spdef.high >= res.ivs.spdef)) + && ((target.ivs.speed.low <= res.ivs.speed) && (target.ivs.speed.high >= res.ivs.speed)); } -AdvRng::AdvRng(uint16_t seed, AdvRngState state) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, AdvRngState state) : seed(seed) , state(state) {} -AdvRng::AdvRng(uint16_t seed, uint64_t min_advances, AdvRngMethod method) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, uint64_t min_advances, AdvRngMethod method) : seed(seed) , state(rngstate_from_seed(seed, min_advances, method)) {} -void AdvRng::advance_state(){ +void AdvRngSearcher::advance_state(){ advance_rng_state(state); } -void AdvRng::set_seed(uint16_t newseed){ +void AdvRngSearcher::set_seed(uint16_t newseed){ seed = newseed; state = rngstate_from_seed(seed, 0, state.method); } -void AdvRng::set_state_advances(uint64_t advances){ +void AdvRngSearcher::set_state_advances(uint64_t advances){ state = rngstate_from_seed(seed, advances, state.method); } -void AdvRng::search_advance_range( +AdvPokemonResult AdvRngSearcher::generate_pokemon(){ + return pokemon_from_state(state); +} + +void AdvRngSearcher::search_advance_range( std::map& hits, AdvRngFilters& target, uint64_t min_advances, @@ -194,21 +214,24 @@ void AdvRng::search_advance_range( if ((target.method != AdvRngMethod::Any) && (target.method != method)){ continue; + }else{ + state.method = method; } for (uint64_t a=min_advances; a AdvRng::search( +std::map AdvRngSearcher::search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, @@ -343,9 +366,9 @@ void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); } -AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1){ +AdvRngFilters observation_to_filters(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; - for (int i=0; i search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, @@ -161,9 +171,6 @@ class AdvRng{ - - - } } #endif diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 65c0fab114..829b5b4e01 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -21,6 +21,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" +#include "Programs/RngManipulation/PokemonFRLG_SidHelper.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -47,9 +48,6 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); - if (PreloadSettings::instance().DEVELOPER_MODE){ - ret.emplace_back(make_single_switch_program()); - } //ret.emplace_back("---- General ----"); @@ -68,6 +66,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Untested/Beta/WIP ----"); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } if (PreloadSettings::instance().DEVELOPER_MODE){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp similarity index 94% rename from SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp rename to SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp index d43b157fa6..8405ca092a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -195,20 +195,20 @@ std::vector> get_sid_messages(SingleSwitchPr int start = -1 * NUM_CANDIDATES / 2; int end = start + NUM_CANDIDATES; - Pokemon::AdvRng rng(tid, TARGET_ADVANCES + 2*start); + Pokemon::AdvRngSearcher searcher(tid, TARGET_ADVANCES + 2*start); for (int i=start; i m; - uint16_t sid = rng.state.s0 >> 16; + uint16_t sid = searcher.state.s0 >> 16; - m.first = "Advances " + std::to_string(rng.state.advance); + m.first = "Advances " + std::to_string(searcher.state.advance); m.second = std::to_string(sid); messages.push_back(m); env.log(m.first + ": " + m.second); - rng.advance_state(); - rng.advance_state(); // 2 by 2 + searcher.advance_state(); + searcher.advance_state(); // 2 by 2 } return messages; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h similarity index 100% rename from SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_SidHelper.h rename to SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 54590f4c88..9d4b3e69f8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1526,6 +1526,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp From f98466d84e418feb8690ac317885592be5113e15 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Tue, 21 Apr 2026 21:06:41 -0500 Subject: [PATCH 21/21] add logging of delay time --- .../Programs/RngManipulation/PokemonFRLG_SidHelper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp index 8405ca092a..e6228a0fe3 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -231,6 +231,7 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex double FRAME_DURATION = 1000 / FRAMERATE; // ms const uint64_t SID_DELAY = uint64_t((TARGET_ADVANCES - 1) * FRAME_DURATION / 2); // advances pass 2 by 2, first one doesn't count (?) + env.log("Delay: " + std::to_string(SID_DELAY) + "ms"); set_sid_from_name_screen(env, context, SID_DELAY); finish_intro_animations(env, context);