From a33e5f02f062182ff58fa579ce8b83dde803b77d Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 11 Jun 2026 22:19:39 -0500 Subject: [PATCH 01/11] Updated helioptile, ButtonDetector, and MapDetector Updated the helioptile shiny hunter to use fast travel and day/night state detection to improve the function of the program. Update Helioptile, ButtonDetector, and MapDetector Added ButtonBInterior to button detector and used that as an improvement to the mapdetector. Tested Helioptile, Donut Maker, and Mega Shard Farmer to confirm that the updates to MapDetector did not cause harm. --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index e8d3e10744..9a22b0daee 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -19,6 +19,7 @@ #include "PokemonLZA/Inference/PokemonLZA_WeatherDetector.h" #include "PokemonLZA_ShinyHunt_HelioptileHunter.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" +#include "PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h" #include #include @@ -85,19 +86,35 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ open_map(env.console, context, true, true); - - WeatherIconDetector sunnyDetector( - WeatherIconType::Sunny, - &env.console.overlay() - ); - - WeatherIconDetector clearDetector( - WeatherIconType::Clear, - &env.console.overlay() - ); - VideoSnapshot screen = env.console.video().snapshot(); - return (sunnyDetector.detect(screen) || clearDetector.detect(screen)); + context.wait_for_all_requests(); + // zoom fully in + pbf_move_right_joystick(context, {0, 1}, 900ms, 120ms); + context.wait_for_all_requests(); + // hide icons + pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); + context.wait_for_all_requests(); + VideoSnapshot screen = env.console.video().snapshot(); + WeatherIconDetector sunnyDetector(WeatherIconType::Sunny, &env.console.overlay()); + WeatherIconDetector clearDetector(WeatherIconType::Clear,&env.console.overlay()); + DayNightStateDetector dayNightDetector(&env.console.overlay()); + dayNightDetector.detect(screen); + + bool correct_weather = + sunnyDetector.detect(screen) || + clearDetector.detect(screen); + bool daytime = + dayNightDetector.state() == DayNightState::DAY; + + env.log(std::string("Weather=") + + (correct_weather ? "Good" : "Bad") + + " Time=" + + (daytime ? "Day" : "Night") + ); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); + context.wait_for_all_requests(); + return correct_weather && daytime; } void bench_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, size_t quantity){ @@ -113,8 +130,7 @@ void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& con bench_loop(env, context, (is_night_time) ? 1 : 2); while (!proper_weather(env, context)){ - env.log("Weather not found"); - pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); + env.log("Incorrect weather or nighttime"); bench_loop(env, context, 2); } env.log("Weather found"); @@ -123,10 +139,24 @@ void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& con void reach_bench(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ //Go to poke center - pbf_move_left_joystick(context, {-0.141, +1}, 100ms, 200ms); - pbf_press_button(context, BUTTON_A, 500ms, 500ms); - pbf_press_button(context, BUTTON_A, 500ms, 500ms); - pbf_wait(context, 3000ms); + FastTravelState result = open_map_and_fly_to( + env.console, context, Language::English, + Location::MAGENTA_POKEMON_CENTER, false, true + ); + + if (result != FastTravelState::SUCCESS) { + env.log( + "Fast travel to Magenta failed. State = " + + std::to_string((int)result) + ); + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to fast travel to Magenta Pokemon Center.", + env.console + ); + } + context.wait_for_all_requests(); //Go to bench pbf_move_left_joystick(context, {-1, 0}, 700ms, 200ms); pbf_move_left_joystick(context, {0, +1}, 500ms, 200ms); @@ -151,11 +181,20 @@ void reach_gate( env.console.overlay().add_log("Detect Entrance"); } -void reach_wild_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - pbf_move_left_joystick(context, {+0.094, -1}, 200ms, 200ms); - pbf_press_button(context, BUTTON_A, 500ms, 500ms); - pbf_press_button(context, BUTTON_A, 500ms, 500ms); - pbf_wait(context, 2000ms); +void warp_wild_zone_14(SingleSwitchProgramEnvironment& env, ProControllerContext& context +) { + FastTravelState result = open_map_and_fly_to( + env.console, context, Language::English, + Location::WILD_ZONE_14, false, true + ); + + if (result != FastTravelState::SUCCESS) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to fast travel to Wild Zone 14.", + env.console + ); + } } void execute_fixed_routine(SingleSwitchProgramEnvironment& env, ConsoleHandle& console, ProControllerContext& context, EventNotificationOption& settings){ @@ -191,7 +230,6 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr ShinyHunt_HelioptileHunter_Descriptor::Stats& stats = env.current_stats(); - // This routine do not care for day/night change as is supposed to stop before time change. while(true){ if (END_AFTER_CYCLE.current_value() > 0 && END_AFTER_CYCLE.current_value() == stats.cycles.load()){ @@ -206,13 +244,12 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr env.log("Not correct weather"); reach_bench(env, context); find_weather(env, context, false); - reach_wild_zone(env, context); + warp_wild_zone_14(env, context); hunt_loops = 0; }else{ env.log("Correct weather. Continuing"); - move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14); - fly_from_map(env.console, context); - } + warp_wild_zone_14(env, context); + } execute_fixed_routine(env,env.console, context, NOTIFICATION_STATUS); From ed299f5ffd68fade8dc27b8e461d2694e87fefd2 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 18 Jun 2026 09:20:21 -0500 Subject: [PATCH 02/11] Removed other parts of ButtonBInterior Removed remnants of the ButtonBInterior attempt. Added comment to helioptile hunter to explain the hunt loop. --- .../ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 9a22b0daee..5c8710f975 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -240,6 +240,10 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr int hunt_loops = 0; while (hunt_loops < 65){ + // On startup and every 65 hunt loops, force a return to the bench + // and re-roll/check weather conditions. This ensures the hunt does + // not continue indefinitely after the desired daytime sunny/clear + // weather has changed. if (!proper_weather(env,context) || (hunt_loops == 0)){ env.log("Not correct weather"); reach_bench(env, context); From da469474b77381cbb46e4d2d5ccdc13c1558347d Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 20 Jun 2026 19:45:30 -0500 Subject: [PATCH 03/11] Update PokemonLZA_ShinyHunt_HelioptileHunter.cpp adjusted some formatting --- .../ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 5c8710f975..0642672353 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -86,14 +86,15 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ open_map(env.console, context, true, true); - context.wait_for_all_requests(); + // zoom fully in pbf_move_right_joystick(context, {0, 1}, 900ms, 120ms); context.wait_for_all_requests(); // hide icons pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); context.wait_for_all_requests(); + VideoSnapshot screen = env.console.video().snapshot(); WeatherIconDetector sunnyDetector(WeatherIconType::Sunny, &env.console.overlay()); WeatherIconDetector clearDetector(WeatherIconType::Clear,&env.console.overlay()); From a774ef8def4c15ccc9626d573116e2c29709d7a8 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 21 Jun 2026 09:43:52 -0500 Subject: [PATCH 04/11] Updated ROI and Loop count stop Refined the ROI for MapDetector's FlySpotIcon. This adjustment should allow the CommandLineTests to pass. The loop counter wasn't working, so adjusted the logic to correctly stop after the requested loops. --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 0642672353..852b47d011 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -67,7 +67,7 @@ std::unique_ptr ShinyHunt_HelioptileHunter_Descriptor::make_stats( } ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() - : END_AFTER_CYCLE("How Many cycles before stopping. 0 for never stop.", + : END_AFTER_CYCLE("How many loops before stopping. 0 for never stop.", LockMode::LOCK_WHILE_RUNNING, 0, 0, 32*30 ) @@ -85,7 +85,7 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - open_map(env.console, context, true, true); + open_map(env.console, context, false, true); context.wait_for_all_requests(); // zoom fully in @@ -125,7 +125,31 @@ void bench_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& conte } } -void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool is_night_time){ +bool check_if_night_time(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + open_map(env.console, context, false, true); + context.wait_for_all_requests(); + + // zoom fully in + pbf_move_right_joystick(context, {0, 1}, 900ms, 120ms); + context.wait_for_all_requests(); + // hide icons + pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); + context.wait_for_all_requests(); + + VideoSnapshot screen = env.console.video().snapshot(); + DayNightStateDetector dayNightDetector(&env.console.overlay()); + dayNightDetector.detect(screen); + bool night_time = dayNightDetector.state() == DayNightState::NIGHT; + + env.log(std::string("Starting time=") + (night_time ? "Night" : "Day")); + pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); + context.wait_for_all_requests(); + + return night_time; +} + +void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + bool is_night_time = check_if_night_time(env, context); context.wait_for_all_requests(); env.log("Starting weather loop"); bench_loop(env, context, (is_night_time) ? 1 : 2); @@ -230,17 +254,28 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr require_player(env.console, context, BUTTON_L); ShinyHunt_HelioptileHunter_Descriptor::Stats& stats = env.current_stats(); - + + auto stop_requested = [&]() { + return END_AFTER_CYCLE.current_value() > 0 + && stats.loops.load() >= END_AFTER_CYCLE.current_value(); + }; + while(true){ - if (END_AFTER_CYCLE.current_value() > 0 && END_AFTER_CYCLE.current_value() == stats.cycles.load()){ + if (stop_requested()) { go_home(env.console, context); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - break; + return; } int hunt_loops = 0; while (hunt_loops < 65){ + if (stop_requested()) { + go_home(env.console, context); + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + return; + } + // On startup and every 65 hunt loops, force a return to the bench // and re-roll/check weather conditions. This ensures the hunt does // not continue indefinitely after the desired daytime sunny/clear @@ -248,7 +283,7 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr if (!proper_weather(env,context) || (hunt_loops == 0)){ env.log("Not correct weather"); reach_bench(env, context); - find_weather(env, context, false); + find_weather(env, context); warp_wild_zone_14(env, context); hunt_loops = 0; }else{ From 48b5da5539ddfbb825cc3adffd26c923d18c8118 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 21 Jun 2026 10:21:12 -0500 Subject: [PATCH 05/11] Update PokemonLZA_ShinyHunt_HelioptileHunter.cpp Reverted to cycles instead of loops after some additional thought and rereading the wiki. Added clarification of what a cycle is in the UI. --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 852b47d011..4879c4d485 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -67,7 +67,11 @@ std::unique_ptr ShinyHunt_HelioptileHunter_Descriptor::make_stats( } ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() - : END_AFTER_CYCLE("How many loops before stopping. 0 for never stop.", + : END_AFTER_CYCLE("How many cycles before stopping. 0 for never stop.
" + "
" + "Cycle Definition:
" + "A cycle consists of entering Wild Zone 14 a total of 65 times. " + "After 65 entries, the program resets the day before continuing.", LockMode::LOCK_WHILE_RUNNING, 0, 0, 32*30 ) @@ -254,28 +258,17 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr require_player(env.console, context, BUTTON_L); ShinyHunt_HelioptileHunter_Descriptor::Stats& stats = env.current_stats(); - - auto stop_requested = [&]() { - return END_AFTER_CYCLE.current_value() > 0 - && stats.loops.load() >= END_AFTER_CYCLE.current_value(); - }; - + while(true){ - if (stop_requested()) { + if (END_AFTER_CYCLE.current_value() > 0 && END_AFTER_CYCLE.current_value() == stats.cycles.load()){ go_home(env.console, context); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - return; + break; } int hunt_loops = 0; while (hunt_loops < 65){ - if (stop_requested()) { - go_home(env.console, context); - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - return; - } - // On startup and every 65 hunt loops, force a return to the bench // and re-roll/check weather conditions. This ensures the hunt does // not continue indefinitely after the desired daytime sunny/clear From 1c7179f36d12ebe7c98bc5b94069139fbb681de3 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Tue, 7 Jul 2026 17:46:36 -0500 Subject: [PATCH 06/11] fixed some things that got removed on accident during rebase. reordered the include block. --- .../ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 4879c4d485..9cc7431cac 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -3,8 +3,8 @@ * From: https://github.com/PokemonAutomation/ * */ - #include "Common/Cpp/Options/ConfigOption.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" @@ -16,10 +16,11 @@ #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "Pokemon/Pokemon_Strings.h" #include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_DayNightStateDetector.h" #include "PokemonLZA/Inference/PokemonLZA_WeatherDetector.h" -#include "PokemonLZA_ShinyHunt_HelioptileHunter.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" #include "PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h" +#include "PokemonLZA_ShinyHunt_HelioptileHunter.h" #include #include From fc2d54dea34cffd6fd98a2edfebd10caef6e495c Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 9 Jul 2026 09:25:19 -0500 Subject: [PATCH 07/11] refactored the loop and weather functions to open the map less often. reverted to move_map_cursor to improve speed of the program, but added some wat time to ensure the move doesn't fire early. --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 94 ++++++++++--------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 9cc7431cac..df9e59e6b1 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -71,8 +71,8 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() : END_AFTER_CYCLE("How many cycles before stopping. 0 for never stop.
" "
" "Cycle Definition:
" - "A cycle consists of entering Wild Zone 14 a total of 65 times. " - "After 65 entries, the program resets the day before continuing.", + "A cycle consists of entering Wild Zone 14 a total of at most 65 times. " + "After 65 entry loops, the program resets the day before continuing.", LockMode::LOCK_WHILE_RUNNING, 0, 0, 32*30 ) @@ -88,8 +88,19 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() PA_ADD_OPTION(NOTIFICATIONS); } -bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - +void bench_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, size_t quantity){ + for (size_t i = 0; i < quantity; i++){ + sit_on_bench(env.console, context); + pbf_move_left_joystick(context, {0, -1}, 500ms, 200ms); + } +} + +struct WeatherTimeState{ + bool correct_weather; + bool daytime; +}; + +WeatherTimeState get_weather_time_state(SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool close_map){ open_map(env.console, context, false, true); context.wait_for_all_requests(); @@ -102,64 +113,42 @@ bool proper_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& c VideoSnapshot screen = env.console.video().snapshot(); WeatherIconDetector sunnyDetector(WeatherIconType::Sunny, &env.console.overlay()); - WeatherIconDetector clearDetector(WeatherIconType::Clear,&env.console.overlay()); + WeatherIconDetector clearDetector(WeatherIconType::Clear, &env.console.overlay()); DayNightStateDetector dayNightDetector(&env.console.overlay()); dayNightDetector.detect(screen); + bool night_time = dayNightDetector.state() == DayNightState::NIGHT; + bool daytime = !night_time; bool correct_weather = sunnyDetector.detect(screen) || clearDetector.detect(screen); - bool daytime = - dayNightDetector.state() == DayNightState::DAY; env.log(std::string("Weather=") + (correct_weather ? "Good" : "Bad") + " Time=" + (daytime ? "Day" : "Night") ); - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); - context.wait_for_all_requests(); - return correct_weather && daytime; -} -void bench_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, size_t quantity){ - for (size_t i = 0; i < quantity; i++){ - sit_on_bench(env.console, context); - pbf_move_left_joystick(context, {0, -1}, 500ms, 200ms); + if (close_map){ + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); + context.wait_for_all_requests(); } -} -bool check_if_night_time(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - open_map(env.console, context, false, true); - context.wait_for_all_requests(); - - // zoom fully in - pbf_move_right_joystick(context, {0, 1}, 900ms, 120ms); - context.wait_for_all_requests(); - // hide icons - pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); - context.wait_for_all_requests(); - - VideoSnapshot screen = env.console.video().snapshot(); - DayNightStateDetector dayNightDetector(&env.console.overlay()); - dayNightDetector.detect(screen); - bool night_time = dayNightDetector.state() == DayNightState::NIGHT; - - env.log(std::string("Starting time=") + (night_time ? "Night" : "Day")); - pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); - context.wait_for_all_requests(); - - return night_time; + return {correct_weather, daytime}; } void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - bool is_night_time = check_if_night_time(env, context); + WeatherTimeState state = get_weather_time_state(env, context, true); context.wait_for_all_requests(); env.log("Starting weather loop"); - bench_loop(env, context, (is_night_time) ? 1 : 2); + bench_loop(env, context, (state.daytime) ? 2 : 1); - while (!proper_weather(env, context)){ + while (true){ + state = get_weather_time_state(env, context, true); + if (state.correct_weather && state.daytime){ + break; + } env.log("Incorrect weather or nighttime"); bench_loop(env, context, 2); } @@ -274,15 +263,32 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr // and re-roll/check weather conditions. This ensures the hunt does // not continue indefinitely after the desired daytime sunny/clear // weather has changed. - if (!proper_weather(env,context) || (hunt_loops == 0)){ + if (hunt_loops == 0){ env.log("Not correct weather"); reach_bench(env, context); find_weather(env, context); warp_wild_zone_14(env, context); hunt_loops = 0; }else{ - env.log("Correct weather. Continuing"); - warp_wild_zone_14(env, context); + WeatherTimeState state = get_weather_time_state(env, context, false); + if (state.correct_weather && state.daytime){ + env.log("Correct weather. Continuing"); + // Re-show icons before moving the map cursor to the destination. + pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); + //these extra waits help prevent early execution of the map move which sometimes fired early during testing + context.wait_for_all_requests(); + pbf_wait(context, 100ms); + move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14); + fly_from_map(env.console, context); + }else{ + env.log("Not correct weather"); + pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); + context.wait_for_all_requests(); + reach_bench(env, context); + find_weather(env, context); + warp_wild_zone_14(env, context); + hunt_loops = 0; + } } execute_fixed_routine(env,env.console, context, NOTIFICATION_STATUS); From 9443b7f1ec3dd45960f97b8b9b942631aff49d6f Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 9 Jul 2026 16:17:51 -0500 Subject: [PATCH 08/11] added zoom out functioins, failure detectioin to the fly logic similar to wildzoneentrance --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index df9e59e6b1..b40f2c52bd 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -273,13 +273,28 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr WeatherTimeState state = get_weather_time_state(env, context, false); if (state.correct_weather && state.daytime){ env.log("Correct weather. Continuing"); + // Zoom fully out before moving map cursor for fast travel. + pbf_move_right_joystick(context, {0, -1}, 900ms, 120ms); // Re-show icons before moving the map cursor to the destination. pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); //these extra waits help prevent early execution of the map move which sometimes fired early during testing context.wait_for_all_requests(); pbf_wait(context, 100ms); move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14); - fly_from_map(env.console, context); + FastTravelState result = fly_from_map(env.console, context); + + switch (result) { + case FastTravelState::SUCCESS: + wait_until_overworld(env.console, context); + break; + + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to fast travel back to Wild Zone 14 after weather check.", + env.console + ); + } }else{ env.log("Not correct weather"); pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); From 7ebfc40e5958e7cbc2373ed2496103e7169006e2 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Tue, 29 Sep 2026 21:22:51 -0500 Subject: [PATCH 09/11] Fix Helioptile fast travel error reporting --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 814264890e..33ce0756a2 100644 --- a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -4,7 +4,7 @@ * */ #include "Common/Cpp/Options/ConfigOption.h" -#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" @@ -169,8 +169,8 @@ void reach_bench(SingleSwitchProgramEnvironment& env, ProControllerContext& cont + std::to_string((int)result) ); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to fast travel to Magenta Pokemon Center.", env.console ); @@ -208,8 +208,8 @@ void warp_wild_zone_14(SingleSwitchProgramEnvironment& env, ProControllerContext ); if (result != FastTravelState::SUCCESS) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to fast travel to Wild Zone 14.", env.console ); @@ -289,8 +289,8 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr break; default: - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to fast travel back to Wild Zone 14 after weather check.", env.console ); From 32e32b81ce8fc46995bcc2956c17dfe8063f8e3e Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Fri, 2 Oct 2026 13:37:57 -0500 Subject: [PATCH 10/11] Update Helioptile Hunter weather integration and cycle counting Merge upstream main, including the WeatherDetector refactor, into the Helioptile Hunter branch. End each hunting cycle after 65 completed loops or when unsuitable weather or nighttime is detected. Count shortened cycles toward the stop limit and update the cycle description. Add normal user logs for weather matches, day/night measurements, loop counts, bench attempts, and cycle completion reasons to support troubleshooting. Validation: Debug build passed; all 36 WeatherDetector tests passed. Five-cycle unattended Switch test passed. --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 107 +++++++++++++----- 1 file changed, 80 insertions(+), 27 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index 33ce0756a2..ec6d5bb753 100644 --- a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -8,6 +8,7 @@ #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/ImageTools/ImageStats.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/StartupChecks/VideoResolutionCheck.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -21,6 +22,7 @@ #include "VideoGames/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" #include "VideoGames/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h" #include "PokemonLZA_ShinyHunt_HelioptileHunter.h" +#include #include #include @@ -72,7 +74,8 @@ ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() "
" "Cycle Definition:
" "A cycle consists of entering Wild Zone 14 a total of at most 65 times. " - "After 65 entry loops, the program resets the day before continuing.", + "A cycle ends after 65 entry loops or when unsuitable weather or nighttime is detected. " + "The next cycle resets the day before continuing.", LockMode::LOCK_WHILE_RUNNING, 0, 0, 32*30 ) @@ -112,21 +115,63 @@ WeatherTimeState get_weather_time_state(SingleSwitchProgramEnvironment& env, Pro context.wait_for_all_requests(); VideoSnapshot screen = env.console.video().snapshot(); - WeatherIconDetector sunnyDetector(WeatherIconType::Sunny, &env.console.overlay()); - WeatherIconDetector clearDetector(WeatherIconType::Clear, &env.console.overlay()); + // Log every weather type so uploaded user logs distinguish unsuitable + // weather from missed or ambiguous detections. These small-region checks + // run between controller sequences; retain them for support troubleshooting. + // Keep the Sunny-or-Clear acceptance rule, including multiple matches. + const std::array types = { + WeatherIconType::Sunny, WeatherIconType::Clear, WeatherIconType::Rain, + WeatherIconType::Cloudy, WeatherIconType::Foggy, WeatherIconType::Rainbow, + }; + const std::array names = { + "Sunny", "Clear", "Rain", "Cloudy", "Foggy", "Rainbow", + }; + std::array matches{}; + std::string detected; + std::string detection_map; + size_t match_count = 0; + for (size_t i = 0; i < types.size(); i++){ + WeatherIconDetector detector(types[i], &env.console.overlay()); + matches[i] = detector.detect(screen); + if (i > 0){ + detection_map += ","; + } + detection_map += std::string(names[i]) + "=" + (matches[i] ? "1" : "0"); + if (matches[i]){ + if (match_count++ > 0){ + detected += ","; + } + detected += names[i]; + } + } + if (match_count == 0){ + detected = "Unknown"; + } + DayNightStateDetector dayNightDetector(&env.console.overlay()); dayNightDetector.detect(screen); - bool night_time = dayNightDetector.state() == DayNightState::NIGHT; - bool daytime = !night_time; + bool daytime = dayNightDetector.state() != DayNightState::NIGHT; + bool correct_weather = matches[0] || matches[1]; - bool correct_weather = - sunnyDetector.detect(screen) || - clearDetector.detect(screen); - - env.log(std::string("Weather=") - + (correct_weather ? "Good" : "Bad") - + " Time=" - + (daytime ? "Day" : "Night") + // Mirror the detector's terrain sample so the day/night threshold can be + // checked against capture brightness without changing the detector itself. + const ImageStats terrain = image_stats( + extract_box_reference(screen, ImageFloatBox(0.30, 0.55, 0.15, 0.18)) + ); + const double blue_ratio = terrain.average.b / + (terrain.average.r + terrain.average.g + terrain.average.b); + auto& stats = env.current_stats(); + env.log("[Helioptile][Weather] cycle=" + std::to_string(stats.cycles.load() + 1) + + " completed_loops=" + std::to_string(stats.loops.load()) + + " phase=" + (close_map ? "bench" : "hunt") + + " detected=" + detected + + " ambiguous=" + (match_count > 1 ? "1" : "0") + + " matches={" + detection_map + "}" + + " time=" + (daytime ? "Day" : "Night") + + " blue_ratio=" + std::to_string(blue_ratio) + + " night_threshold=0.36" + + " eligible=" + (correct_weather && daytime ? "1" : "0") + + " frame=" + std::to_string(screen->width()) + "x" + std::to_string(screen->height()) ); if (close_map){ @@ -141,18 +186,22 @@ WeatherTimeState get_weather_time_state(SingleSwitchProgramEnvironment& env, Pro void find_weather(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ WeatherTimeState state = get_weather_time_state(env, context, true); context.wait_for_all_requests(); - env.log("Starting weather loop"); + env.log("[Helioptile][Decision] action=initial_bench_reroll sits=" + + std::to_string(state.daytime ? 2 : 1)); bench_loop(env, context, (state.daytime) ? 2 : 1); + size_t weather_attempt = 0; while (true){ + env.log("[Helioptile][Bench] attempt=" + std::to_string(++weather_attempt)); state = get_weather_time_state(env, context, true); if (state.correct_weather && state.daytime){ break; } - env.log("Incorrect weather or nighttime"); + env.log("[Helioptile][Decision] action=bench_reroll sits=2 reason=" + + std::string(!state.correct_weather ? "weather" : "night")); bench_loop(env, context, 2); } - env.log("Weather found"); + env.log("[Helioptile][Decision] action=weather_found destination=WildZone14"); context.wait_for_all_requests(); } @@ -258,13 +307,12 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr } int hunt_loops = 0; + std::string cycle_end_reason = "loop_limit"; while (hunt_loops < 65){ - // On startup and every 65 hunt loops, force a return to the bench - // and re-roll/check weather conditions. This ensures the hunt does - // not continue indefinitely after the desired daytime sunny/clear - // weather has changed. + // Begin each cycle by re-rolling weather at the bench. A cycle ends + // after 65 completed hunt loops or when the weather/time check fails. if (hunt_loops == 0){ - env.log("Not correct weather"); + env.log("[Helioptile][Decision] action=reach_bench reason=cycle_start"); reach_bench(env, context); find_weather(env, context); warp_wild_zone_14(env, context); @@ -272,7 +320,7 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr }else{ WeatherTimeState state = get_weather_time_state(env, context, false); if (state.correct_weather && state.daytime){ - env.log("Correct weather. Continuing"); + env.log("[Helioptile][Decision] action=continue_hunt"); // Zoom fully out before moving map cursor for fast travel. pbf_move_right_joystick(context, {0, -1}, 900ms, 120ms); // Re-show icons before moving the map cursor to the destination. @@ -296,22 +344,27 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr ); } }else{ - env.log("Not correct weather"); + cycle_end_reason = !state.correct_weather ? "weather" : "night"; + env.log("[Helioptile][Decision] action=end_cycle reason=" + cycle_end_reason); pbf_press_button(context, BUTTON_PLUS, 500ms, 500ms); context.wait_for_all_requests(); - reach_bench(env, context); - find_weather(env, context); - warp_wild_zone_14(env, context); - hunt_loops = 0; + break; } } + env.log("[Helioptile][Loop] cycle=" + std::to_string(stats.cycles.load() + 1) + + " cycle_loop=" + std::to_string(hunt_loops + 1) + + " total_loop=" + std::to_string(stats.loops.load() + 1)); execute_fixed_routine(env,env.console, context, NOTIFICATION_STATUS); stats.loops++; hunt_loops++; env.update_stats(); } + env.log("[Helioptile][Decision] action=cycle_complete cycle=" + + std::to_string(stats.cycles.load() + 1) + + " completed_loops=" + std::to_string(hunt_loops) + + " reason=" + cycle_end_reason); stats.cycles++; env.update_stats(); } From 40f12f79a895e8ac70f2d6b3a6be7515e4586795 Mon Sep 17 00:00:00 2001 From: Alexander Yee Date: Mon, 5 Oct 2026 22:30:35 -0700 Subject: [PATCH 11/11] cleanup --- .../PokemonLZA_ShinyHunt_HelioptileHunter.cpp | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp index ec6d5bb753..cbaa511a8b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.cpp @@ -3,6 +3,10 @@ * From: https://github.com/PokemonAutomation/ * */ + +#include +#include +#include #include "Common/Cpp/Options/ConfigOption.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ProgramStats/StatsTracking.h" @@ -22,9 +26,6 @@ #include "VideoGames/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" #include "VideoGames/PokemonLZA/Programs/PokemonLZA_FastTravelNavigation.h" #include "PokemonLZA_ShinyHunt_HelioptileHunter.h" -#include -#include -#include namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -70,16 +71,14 @@ std::unique_ptr ShinyHunt_HelioptileHunter_Descriptor::make_stats( } ShinyHunt_HelioptileHunter::ShinyHunt_HelioptileHunter() - : END_AFTER_CYCLE("How many cycles before stopping. 0 for never stop.
" + : END_AFTER_CYCLE( + "How many day/night cycles before stopping. 0 for never stop.
" "
" - "Cycle Definition:
" - "A cycle consists of entering Wild Zone 14 a total of at most 65 times. " - "A cycle ends after 65 entry loops or when unsuitable weather or nighttime is detected. " - "The next cycle resets the day before continuing.", + "Each day/night cycle is roughly 65 tries.", LockMode::LOCK_WHILE_RUNNING, - 0, 0, 32*30 + 0, 0 ) - , NOTIFICATION_STATUS("Status Update", true, false, ImageAttachmentMode::JPG, {"Notifs"}, std::chrono::seconds(3600)) + , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ &NOTIFICATION_STATUS, &NOTIFICATION_PROGRAM_FINISH, @@ -103,7 +102,11 @@ struct WeatherTimeState{ bool daytime; }; -WeatherTimeState get_weather_time_state(SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool close_map){ +WeatherTimeState get_weather_time_state( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + bool close_map +){ open_map(env.console, context, false, true); context.wait_for_all_requests(); @@ -297,10 +300,12 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr require_player(env.console, context, BUTTON_L); ShinyHunt_HelioptileHunter_Descriptor::Stats& stats = env.current_stats(); - + while(true){ - - if (END_AFTER_CYCLE.current_value() > 0 && END_AFTER_CYCLE.current_value() == stats.cycles.load()){ + + if (END_AFTER_CYCLE.current_value() > 0 && + END_AFTER_CYCLE.current_value() == stats.cycles.load(std::memory_order_relaxed) + ){ go_home(env.console, context); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); break; @@ -326,7 +331,7 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr // Re-show icons before moving the map cursor to the destination. pbf_press_button(context, BUTTON_MINUS, 80ms, 120ms); //these extra waits help prevent early execution of the map move which sometimes fired early during testing - context.wait_for_all_requests(); + context.wait_for_all_requests(); pbf_wait(context, 100ms); move_map_cursor_from_entrance_to_zone(env.console, context, Location::WILD_ZONE_14); FastTravelState result = fly_from_map(env.console, context); @@ -350,21 +355,25 @@ void ShinyHunt_HelioptileHunter::program(SingleSwitchProgramEnvironment& env, Pr context.wait_for_all_requests(); break; } - } + } - env.log("[Helioptile][Loop] cycle=" + std::to_string(stats.cycles.load() + 1) + env.log( + "[Helioptile][Loop] cycle=" + std::to_string(stats.cycles.load(std::memory_order_relaxed) + 1) + " cycle_loop=" + std::to_string(hunt_loops + 1) - + " total_loop=" + std::to_string(stats.loops.load() + 1)); + + " total_loop=" + std::to_string(stats.loops.load() + 1) + ); execute_fixed_routine(env,env.console, context, NOTIFICATION_STATUS); stats.loops++; hunt_loops++; env.update_stats(); } - env.log("[Helioptile][Decision] action=cycle_complete cycle=" - + std::to_string(stats.cycles.load() + 1) + env.log( + "[Helioptile][Decision] action=cycle_complete cycle=" + + std::to_string(stats.cycles.load(std::memory_order_relaxed) + 1) + " completed_loops=" + std::to_string(hunt_loops) - + " reason=" + cycle_end_reason); + + " reason=" + cycle_end_reason + ); stats.cycles++; env.update_stats(); }