From 545a7e3930dc4f9d7534a082148fdfbf8efff031 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 7 Jun 2026 15:20:30 -0500 Subject: [PATCH 1/8] switch to is_grey for distinguishing dark/light colors --- .../PokemonFRLG_BattlePokemonDetector.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp index fb576f0dc4..04ba09e2d8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp @@ -25,8 +25,8 @@ namespace PokemonFRLG{ BattlePokemonDetector::BattlePokemonDetector(Color color) // be warned: the name/level/hp box moves up and down a little bit - : m_left_box(0.577404, 0.504327, 0.001442, 0.121875) // off-white (255, 255, 236), can be interrupted by a status condition - , m_right_box(0.948558, 0.585096, 0.000481, 0.063462) // dark teal (74, 111, 102) + : m_left_box(0.577404, 0.504327, 0.001442, 0.121875) // off-white rgb(255, 255, 235), can be interrupted by a status condition + , m_right_box(0.948558, 0.585096, 0.000481, 0.063462) // dark teal rgb(72, 106, 98) , m_top_box(0.594712, 0.481971, 0.325962, 0.002163) // off-white, movement makes this unreliable , m_bottom_box(0.554808, 0.674519, 0.034615, 0.002163) // dark teal {} @@ -44,10 +44,10 @@ bool BattlePokemonDetector::detect(const ImageViewRGB32& screen){ ImageViewRGB32 right_image = extract_box_reference(game_screen, m_right_box); ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); - if (is_solid(left_image, { 0.3418, 0.3418, 0.3164 }) - && is_solid(right_image, { 0.2578, 0.3868, 0.3554 }, 0.075, 5) - && is_solid(top_image, { 0.3418, 0.3418, 0.3164 }) - && is_solid(bottom_image, { 0.2578, 0.3868, 0.3554 }, 0.075, 5) + if (is_grey(left_image, 680, 770, 20) + && is_grey(right_image, 220, 320, 20) + && is_grey(top_image, 680, 770, 20) + && is_grey(bottom_image, 220, 320, 20) ){ return true; } @@ -55,10 +55,10 @@ bool BattlePokemonDetector::detect(const ImageViewRGB32& screen){ } BattleOpponentDetector::BattleOpponentDetector(Color color) - : m_left_box(0.067308, 0.140865, 0.001442, 0.090144) // off-white (255, 255, 236) + : m_left_box(0.067308, 0.140865, 0.001442, 0.090144) // off-white rgb(255, 255, 235) , m_right_box(0.422596, 0.132211, 0.000481, 0.054808) // off-white , m_top_box(0.085577, 0.119952, 0.329327, 0.000721) // off-white - , m_bottom_box(0.109615, 0.264182, 0.328365, 0.001442) // dark teal (74, 111, 102) + , m_bottom_box(0.109615, 0.264182, 0.328365, 0.001442) // dark teal rgb(72, 106, 98) {} void BattleOpponentDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; @@ -75,10 +75,10 @@ bool BattleOpponentDetector::detect(const ImageViewRGB32& screen){ ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); - if (is_solid(left_image, { 0.3418, 0.3418, 0.3164 }) - && is_solid(right_image, { 0.3418, 0.3418, 0.3164 }) - && is_solid(top_image, { 0.3418, 0.3418, 0.3164 }) - && is_solid(bottom_image, { 0.2578, 0.3868, 0.3554 }, 0.075, 5) + if (is_grey(left_image, 680, 770, 20) + && is_grey(right_image, 680, 770, 20) + && is_grey(top_image, 680, 770, 20) + && is_grey(bottom_image, 220, 320, 20) ){ return true; } From f1464fa6b33dfb0af462173d16c0316b26664d63 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 7 Jun 2026 20:39:06 -0500 Subject: [PATCH 2/8] fix color-related issues --- .../Inference/PokemonFRLG_StatsReader.cpp | 108 ++++++++++-------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index f0bf1ac0d5..2281788073 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -88,18 +88,26 @@ void StatsReader::read_page1( ImageViewRGB32 name_box = extract_box_reference(game_screen, jpn ? m_box_name_jpn : m_box_name); - // remove shadow - ImageRGB32 name_filtered = filter_rgb32_range( - name_box, 0xff000000, 0xffc7c7c7, Color(0xffc3c3c3), true - ); - // make text black - name_filtered = filter_rgb32_range( - name_filtered, 0xffc8c8c8, 0xffffffff, Color(0xff000000), true - ); + ImageRGB32 name_filtered(name_box.width(), name_box.height()); + for (size_t r = 0; r < name_box.height(); r++){ + for (size_t c = 0; c < name_box.width(); c++){ + Color pixel(name_box.pixel(c, r)); + // Try to detect lilac background first based on low green channel. + // For other pixels, if it's bright it becomes black text. + // Otherwise, it becomes white background. + if ((pixel.blue() > pixel.green() + 25) && (pixel.red() > pixel.green() + 15)){ + name_filtered.pixel(c, r) = (uint32_t)0xffffffff; // White + }else if (pixel.red() > 200 && pixel.green() > 200 && pixel.blue() > 200){ + name_filtered.pixel(c, r) = (uint32_t)0xff000000; // Black + }else{ + name_filtered.pixel(c, r) = (uint32_t)0xffffffff; // White + } + } + } ImageRGB32 name_ready = preprocess_for_ocr( name_filtered, "name", 7, 2, true, - combine_rgb(0, 0, 0), jpn ? combine_rgb(160, 160, 160) : combine_rgb(120, 120, 120) + combine_rgb(0, 0, 0), jpn ? combine_rgb(160, 160, 160) : combine_rgb(125, 125, 125) ); const std::vector name_text_color_ranges{ @@ -148,42 +156,8 @@ void StatsReader::read_page1( stats.gender = SummaryGender::Genderless; } - - ImageViewRGB32 level_box = extract_box_reference(game_screen, jpn ? m_box_level_jpn : m_box_level); - ImageRGB32 level_upscaled = - level_box.scale_to(level_box.width() * 4, level_box.height() * 4); - if (save_debug_images){ - level_upscaled.save("DebugDumps/ocr_level_upscaled.png"); - } - - // The level has a colored (lilac) background. The text is white, with a - // gray/black shadow. To bridge the gaps and make a solid black character on a - // white background: We want to turn BOTH the bright white text AND the dark - // shadow into BLACK pixels, and turn the mid-tone lilac background into - // WHITE. We can do this by keeping pixels that are very bright (text) or very - // dark (shadow). - - ImageRGB32 level_ready(level_upscaled.width(), level_upscaled.height()); - for (size_t r = 0; r < level_upscaled.height(); r++){ - for (size_t c = 0; c < level_upscaled.width(); c++){ - Color pixel(level_upscaled.pixel(c, r)); - // If it's very bright (white text) OR very dark (shadow), it becomes - // black text. Otherwise (lilac background), it becomes white background. - if ((pixel.red() > 200 && pixel.green() > 200 && pixel.blue() > 200) || - (pixel.red() < 100 && pixel.green() < 100 && pixel.blue() < 100)){ - level_ready.pixel(c, r) = (uint32_t)0xff000000; // Black - }else{ - level_ready.pixel(c, r) = (uint32_t)0xffffffff; // White - } - } - } - - if (save_debug_images){ - level_ready.save("DebugDumps/ocr_level_ready.png"); - } - if (!GlobalSettings::instance().USE_PADDLE_OCR){ // The level uses white text with dark shadow on a lilac background. // The digit reader's binarizer captures dark pixels (<=190 on all channels) @@ -191,9 +165,20 @@ void StatsReader::read_page1( // shadow outline fragmented into many small disconnected blobs. // Preprocess: convert bright-white text pixels to black so the binarizer // merges text + shadow into one solid connected blob per digit. - ImageRGB32 preprocessed = filter_rgb32_range( - level_box, 0xffc8c8c8, 0xffffffff, Color(0xff000000), true - ); + ImageRGB32 preprocessed = level_box.scale_to(level_box.width(), level_box.height()); + for (size_t r = 0; r < level_box.height(); r++){ + for (size_t c = 0; c < level_box.width(); c++){ + Color pixel(level_box.pixel(c, r)); + // Try to detect lilac background first based on low green channel, + // replacing it with a darker lilac color (for matching the template) + // For other pixels, if it's bright it becomes black text. + if ((pixel.blue() > pixel.green() + 25) && (pixel.red() > pixel.green() + 15)){ + preprocessed.pixel(c, r) = (uint32_t)0xffd1b0f0; // from template + }else if (pixel.red() > 200 && pixel.green() > 200 && pixel.blue() > 200){ + preprocessed.pixel(c, r) = (uint32_t)0xff000000; // Black + } + } + } if (save_debug_images){ preprocessed.save("DebugDumps/ocr_level_preprocessed.png"); } @@ -215,6 +200,37 @@ void StatsReader::read_page1( logger, level_digit_view, 230.0, DigitTemplateType::LevelBox, "levelDigit", 0x7F); }else{ + // The level has a colored (lilac) background. The text is white, with a + // gray/black shadow. To bridge the gaps and make a solid black character on a + // white background: We want to turn BOTH the bright white text AND the dark + // shadow into BLACK pixels, and turn the mid-tone lilac background into + // WHITE. We can do this by keeping pixels that are very bright (text) or very + // dark (shadow). + ImageRGB32 level_upscaled = + level_box.scale_to(level_box.width() * 4, level_box.height() * 4); + if (save_debug_images){ + level_upscaled.save("DebugDumps/ocr_level_upscaled.png"); + } + ImageRGB32 level_ready(level_upscaled.width(), level_upscaled.height()); + for (size_t r = 0; r < level_upscaled.height(); r++){ + for (size_t c = 0; c < level_upscaled.width(); c++){ + Color pixel(level_upscaled.pixel(c, r)); + // Try to detect lilac background first based on low green channel. + // For other pixels, if it's very bright (white text) OR very dark (shadow), + // it becomes black text. Otherwise, it becomes white background. + if ((pixel.blue() > pixel.green() + 25) && (pixel.red() > pixel.green() + 15)){ + level_ready.pixel(c, r) = (uint32_t)0xffffffff; // White + }else if ((pixel.red() > 200 && pixel.green() > 200 && pixel.blue() > 200) || + (pixel.red() < 100 && pixel.green() < 100 && pixel.blue() < 100)){ + level_ready.pixel(c, r) = (uint32_t)0xff000000; // Black + }else{ + level_ready.pixel(c, r) = (uint32_t)0xffffffff; // White + } + } + } + if (save_debug_images){ + level_ready.save("DebugDumps/ocr_level_ready.png"); + } // Pass the binarized image to PaddleOCR stats.level = OCR::read_number(logger, level_ready, language); } From ea41aa33507d4e20ae10dfe227672e4cf5b74c8c Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 7 Jun 2026 20:39:29 -0500 Subject: [PATCH 3/8] fix 0 rare candies calibration issue --- .../Programs/RngManipulation/PokemonFRLG_GiftRng.cpp | 2 +- .../RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp | 2 +- .../Programs/RngManipulation/PokemonFRLG_StaticRng.cpp | 2 +- .../Programs/RngManipulation/PokemonFRLG_WildRng.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index df2299d36b..3d6e41a234 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -463,7 +463,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& RNG_CALIBRATION.set_hits(search_hits); bool finished = update_history( env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, 1 + calibrations, search_hits, 1, 2, MAX_RARE_CANDIES == 0 ); for (uint64_t i=0; i search_hits = get_wild_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, gender_threshold, SUPER_ROD); RNG_CALIBRATION.set_hits(search_hits); - bool finished = update_history(env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, calibrations, search_hits, 1); + bool finished = update_history(env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, MAX_RARE_CANDIES == 0); finished = finished || all_indistinguishable(search_hits, searcher, gender_threshold, SUPER_ROD); for (uint64_t i=0; i Date: Sun, 7 Jun 2026 23:11:14 -0500 Subject: [PATCH 4/8] multi-filter white text detection for first step of name OCR --- .../Inference/PokemonFRLG_StatsReader.cpp | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index 2281788073..a313c02e68 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -88,47 +88,57 @@ void StatsReader::read_page1( ImageViewRGB32 name_box = extract_box_reference(game_screen, jpn ? m_box_name_jpn : m_box_name); - ImageRGB32 name_filtered(name_box.width(), name_box.height()); - for (size_t r = 0; r < name_box.height(); r++){ - for (size_t c = 0; c < name_box.width(); c++){ - Color pixel(name_box.pixel(c, r)); - // Try to detect lilac background first based on low green channel. - // For other pixels, if it's bright it becomes black text. - // Otherwise, it becomes white background. - if ((pixel.blue() > pixel.green() + 25) && (pixel.red() > pixel.green() + 15)){ - name_filtered.pixel(c, r) = (uint32_t)0xffffffff; // White - }else if (pixel.red() > 200 && pixel.green() > 200 && pixel.blue() > 200){ - name_filtered.pixel(c, r) = (uint32_t)0xff000000; // Black - }else{ - name_filtered.pixel(c, r) = (uint32_t)0xffffffff; // White + + static const std::vector WHITE_THRESHOLDS = { 180, 200, 220, 230, 240 }; + OCR::StringMatchResult best_result; + bool initialized = false; + for (int thresh : WHITE_THRESHOLDS){ + ImageRGB32 name_filtered(name_box.width(), name_box.height()); + for (size_t r = 0; r < name_box.height(); r++){ + for (size_t c = 0; c < name_box.width(); c++){ + Color pixel(name_box.pixel(c, r)); + if (pixel.red() > thresh && pixel.green() > thresh && pixel.blue() > thresh){ + name_filtered.pixel(c, r) = (uint32_t)0xff000000; // Black + }else{ + name_filtered.pixel(c, r) = (uint32_t)0xffffffff; // White + } } } - } - - ImageRGB32 name_ready = preprocess_for_ocr( - name_filtered, "name", 7, 2, true, - combine_rgb(0, 0, 0), jpn ? combine_rgb(160, 160, 160) : combine_rgb(125, 125, 125) - ); - - const std::vector name_text_color_ranges{ - {combine_rgb(0, 0, 0), combine_rgb(120, 120, 120)} - }; - - if (subset.size() > 0){ - auto name_result = Pokemon::PokemonNameReader(subset).read_substring( - logger, language, name_ready, name_text_color_ranges + ImageRGB32 name_ready = preprocess_for_ocr( + name_filtered, "name", 7, 2, true, + combine_rgb(0, 0, 0), jpn ? combine_rgb(160, 160, 160) : combine_rgb(140, 140, 140) ); - if (!name_result.results.empty()){ - stats.name = name_result.results.begin()->second.token; + + const std::vector name_text_color_ranges{ + {combine_rgb(0, 0, 0), combine_rgb(120, 120, 120)} + }; + + OCR::StringMatchResult result; + if (subset.size() > 0){ + auto name_result = Pokemon::PokemonNameReader(subset).read_substring( + logger, language, name_ready, name_text_color_ranges + ); + if (!name_result.results.empty()){ + result = name_result; + } + }else{ + auto name_result = Pokemon::PokemonNameReader::instance().read_substring( + logger, language, name_ready, name_text_color_ranges + ); + if (!name_result.results.empty()){ + result = name_result; + } } - }else{ - auto name_result = Pokemon::PokemonNameReader::instance().read_substring( - logger, language, name_ready, name_text_color_ranges - ); - if (!name_result.results.empty()){ - stats.name = name_result.results.begin()->second.token; + if (!initialized){ + best_result = result; + initialized = true; + }else{ + if (result.results.begin()->first < best_result.results.begin()->first){ + best_result = result; + } } } + stats.name = best_result.results.begin()->second.token; // Detect gender by comparing red vs blue pixels ImageViewRGB32 gender_box = extract_box_reference(game_screen, jpn ? m_box_gender_jpn : m_box_gender); From ce8d7c36b746e387fd6e454ae64640ef5fbc1008 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 7 Jun 2026 23:14:29 -0500 Subject: [PATCH 5/8] handle empty results --- .../Inference/PokemonFRLG_StatsReader.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index a313c02e68..47cab0bf55 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -129,16 +129,18 @@ void StatsReader::read_page1( result = name_result; } } - if (!initialized){ - best_result = result; - initialized = true; - }else{ - if (result.results.begin()->first < best_result.results.begin()->first){ + if (!result.results.empty()){ + if (!initialized){ + best_result = result; + initialized = true; + }else if (result.results.begin()->first < best_result.results.begin()->first){ best_result = result; } } } - stats.name = best_result.results.begin()->second.token; + if (!best_result.results.empty()){ + stats.name = best_result.results.begin()->second.token; + } // Detect gender by comparing red vs blue pixels ImageViewRGB32 gender_box = extract_box_reference(game_screen, jpn ? m_box_gender_jpn : m_box_gender); From fe94c7f9ff122b6bfc427a7ce59540ef0a037ee7 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 7 Jun 2026 23:15:26 -0500 Subject: [PATCH 6/8] also handle if all results are empty --- .../Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index 47cab0bf55..7c518cfe9f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -138,7 +138,7 @@ void StatsReader::read_page1( } } } - if (!best_result.results.empty()){ + if (initialized && !best_result.results.empty()){ stats.name = best_result.results.begin()->second.token; } From 7530371b1733cdc5ba5d276d0edef4a404b85195 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 8 Jun 2026 09:06:55 -0500 Subject: [PATCH 7/8] add negative background detection --- .../Inference/PokemonFRLG_BattlePokemonDetector.cpp | 7 +++++++ .../Inference/PokemonFRLG_BattlePokemonDetector.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp index 04ba09e2d8..bc1fd93ef8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp @@ -29,6 +29,7 @@ BattlePokemonDetector::BattlePokemonDetector(Color color) , m_right_box(0.948558, 0.585096, 0.000481, 0.063462) // dark teal rgb(72, 106, 98) , m_top_box(0.594712, 0.481971, 0.325962, 0.002163) // off-white, movement makes this unreliable , m_bottom_box(0.554808, 0.674519, 0.034615, 0.002163) // dark teal + , m_background_box(0.552564, 0.583653, 0.414103, 0.009615) // depends on in-game location. white or rgb(215, 169, 96) {} void BattlePokemonDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; @@ -44,10 +45,13 @@ bool BattlePokemonDetector::detect(const ImageViewRGB32& screen){ ImageViewRGB32 right_image = extract_box_reference(game_screen, m_right_box); ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); + ImageViewRGB32 background_image = extract_box_reference(game_screen, m_background_box); + if (is_grey(left_image, 680, 770, 20) && is_grey(right_image, 220, 320, 20) && is_grey(top_image, 680, 770, 20) && is_grey(bottom_image, 220, 320, 20) + && !(is_white(background_image) || is_solid(background_image, {0.4468, 0.3528, 0.2004}, 0.25, 20)) ){ return true; } @@ -59,6 +63,7 @@ BattleOpponentDetector::BattleOpponentDetector(Color color) , m_right_box(0.422596, 0.132211, 0.000481, 0.054808) // off-white , m_top_box(0.085577, 0.119952, 0.329327, 0.000721) // off-white , m_bottom_box(0.109615, 0.264182, 0.328365, 0.001442) // dark teal rgb(72, 106, 98) + , m_background_box(0.050641, 0.126923, 0.388462, 0.063462) // depends on in-game location. white or rgb(187, 149, 65) {} void BattleOpponentDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; @@ -74,11 +79,13 @@ bool BattleOpponentDetector::detect(const ImageViewRGB32& screen){ ImageViewRGB32 right_image = extract_box_reference(game_screen, m_right_box); ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); + ImageViewRGB32 background_image = extract_box_reference(game_screen, m_background_box); if (is_grey(left_image, 680, 770, 20) && is_grey(right_image, 680, 770, 20) && is_grey(top_image, 680, 770, 20) && is_grey(bottom_image, 220, 320, 20) + && !(is_white(background_image) || is_solid(background_image, {0.4663, 0.3716, 0.1621}, 0.25, 20)) ){ return true; } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h index a95a79c505..d561d1194e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h @@ -33,6 +33,7 @@ class BattlePokemonDetector : public StaticScreenDetector{ ImageFloatBox m_right_box; ImageFloatBox m_top_box; ImageFloatBox m_bottom_box; + ImageFloatBox m_background_box; }; // Watches for the player's Pokemon to disappear @@ -56,6 +57,7 @@ class BattleOpponentDetector : public StaticScreenDetector{ ImageFloatBox m_right_box; ImageFloatBox m_top_box; ImageFloatBox m_bottom_box; + ImageFloatBox m_background_box; }; // Watches for the opponent to disappear From 4ce01000f7407c3106f2adf0e307e663395c3594 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 8 Jun 2026 09:14:45 -0500 Subject: [PATCH 8/8] better calibration with recoverable errors --- .../RngManipulation/PokemonFRLG_GiftRng.cpp | 4 ++++ .../PokemonFRLG_RoamingLegendaryRng.cpp | 4 ++++ .../RngManipulation/PokemonFRLG_StarterRng.cpp | 16 ++++++++++++++++ .../RngManipulation/PokemonFRLG_StaticRng.cpp | 4 ++++ .../RngManipulation/PokemonFRLG_WildRng.cpp | 4 ++++ 5 files changed, 32 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index 33d270a737..62926992c0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -473,6 +473,10 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, BASE_STATS, AdvRngMethod::Method1, false, i == 0); if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; send_program_recoverable_error_notification( env, NOTIFICATION_ERROR_RECOVERABLE, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp index 7ae0a3f96c..bb2e9fd7d0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp @@ -445,6 +445,10 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, BASE_STATS, AdvRngMethod::Method1, false, i == 0); if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; send_program_recoverable_error_notification( env, NOTIFICATION_ERROR_RECOVERABLE, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index fc0612def9..7c655180cf 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -756,12 +756,20 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte // Stage 2: first search update -- post-rival-battle bool failed = walk_to_rival_battle(env, context); if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; continue; // reset game } failed = auto_battle_rival(env, context, pokemon, filters, BASE_STATS); if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; continue; // reset game } @@ -787,6 +795,10 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte // Stage 3: subsequent search updates -- leveling up from wild encounters failed = walk_to_route1_from_lab(env, context); if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; continue; // reset game } @@ -808,6 +820,10 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte int ret2 = autolevel_on_route1(env, context, pokemon, filters, BASE_STATS); if (ret2 < 0){ + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; break; }else if(ret2 == 1){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp index eb5426c9b9..619aabbe73 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp @@ -466,6 +466,10 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex } bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, BASE_STATS, AdvRngMethod::Method1, false, i == 0); if (failed) { + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; send_program_recoverable_error_notification( env, NOTIFICATION_ERROR_RECOVERABLE, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp index 61b3d6a99a..e83127e497 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp @@ -549,6 +549,10 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& } bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, base_stats, AdvRngMethod::Any, safari_zone, i == 0); if (failed) { + update_history( + env.console, uncertain_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true + ); stats.errors++; send_program_recoverable_error_notification( env, NOTIFICATION_ERROR_RECOVERABLE,