From 99410c79bdbb303538cc86f2251757d4ef0e857f Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:49:53 -0500 Subject: [PATCH 01/10] Reduce Hyperspace Fly Spot Reset Calorie Loss Rate --- .gitignore | 1 + .../Programs/PokemonLZA_BasicNavigation.cpp | 27 ++++++++++--------- .../Programs/PokemonLZA_BasicNavigation.h | 2 +- .../PokemonLZA_ShinyHunt_FlySpotReset.cpp | 4 +-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index a9a26cf016..073865607a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ build/ # Qt config file on a user basis *.pro.user +*.txt.user # all other common C++ ignorables # Prerequisites diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp index 5bd8289970..81769fcae3 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.cpp @@ -171,7 +171,7 @@ bool open_map(ConsoleHandle& console, ProControllerContext& context, bool zoom_t } void open_hyperspace_map(ConsoleHandle& console, ProControllerContext& context){ - pbf_press_button(context, BUTTON_PLUS, 240ms, 40ms); + pbf_press_button(context, BUTTON_PLUS, 240ms, 40ms); context.wait_for_all_requests(); console.log("Opening Hyperspace Map..."); console.overlay().add_log("Open Hyperspace Map"); @@ -219,7 +219,8 @@ void open_hyperspace_map(ConsoleHandle& console, ProControllerContext& context){ FastTravelState fly_from_map( ConsoleHandle& console, ProControllerContext& context, - std::shared_ptr* overworld_screen + std::shared_ptr* overworld_screen, + Button mash_while_waiting ){ console.log("Flying from map..."); context.wait_for_all_requests(); @@ -260,13 +261,16 @@ FastTravelState fly_from_map( } } - OverworldPartySelectionWatcher overworld(COLOR_WHITE, &console.overlay()); + OverworldPartySelectionWatcher overworld(COLOR_WHITE, &console.overlay(), 0ms); BlueDialogWatcher blue_dialog(COLOR_BLUE, &console.overlay()); - int ret = wait_until( - console, context, 30s, // set 30sec to be long enough for Switch 1 to load the overworld - {overworld, blue_dialog} + int ret = run_until( + console, context, + [&](ProControllerContext& context) { + pbf_mash_button(context, mash_while_waiting, 30s); + }, + { {overworld} } ); - switch (ret){ + switch (ret) { case 0: console.log("Flying from map... Done!"); console.overlay().add_log("Fast Travel Done"); @@ -275,12 +279,12 @@ FastTravelState fly_from_map( console.log("Detected blue dialog. Rare by probably too many button A mashing to trigger return to Lumiose dialog while teleporting to Hyperspace portal"); ret = run_until( console, context, - [](ProControllerContext& context){ + [](ProControllerContext& context) { pbf_mash_button(context, BUTTON_B, 5s); }, - {{overworld}} + { {overworld} } ); - if (ret != 0){ + if (ret != 0) { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "fly_from_map(): Does not detect overworld after encountering blue dialog.", @@ -297,8 +301,7 @@ FastTravelState fly_from_map( console ); } - - if (overworld_screen != nullptr){ + if (overworld_screen != nullptr) { *overworld_screen = overworld.last_detected_frame(); } diff --git a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h index b8434538ce..e4512ed5bb 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/PokemonLZA_BasicNavigation.h @@ -66,7 +66,7 @@ void open_hyperspace_map(ConsoleHandle& console, ProControllerContext& context); // - NOT_AT_FLY_SPOT: the current map cursor is not on a fly spot, cannot fast travel. After the function // returns, the game is in fly map. FastTravelState fly_from_map(ConsoleHandle& console, ProControllerContext& context, - std::shared_ptr* overworld_screen = nullptr); + std::shared_ptr* overworld_screen = nullptr, Button mash_while_waiting = BUTTON_NONE); // Fast travel without moving map cursor. // This is useful to fast travel back to the wild zone gate while in the zone. diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp index a377135a83..59f2761d52 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp @@ -210,7 +210,7 @@ bool route_hyperspace_wild_zone( // Fly from map to reset spawns std::shared_ptr overworld_screen; - FastTravelState travel_status = fly_from_map(env.console, context, &overworld_screen); + FastTravelState travel_status = fly_from_map(env.console, context, &overworld_screen, BUTTON_PLUS); if (travel_status != FastTravelState::SUCCESS){ stats.errors++; env.update_stats(); @@ -224,7 +224,7 @@ bool route_hyperspace_wild_zone( if (overworld_screen == nullptr){ throw InternalProgramError(&env.logger(), PA_CURRENT_FUNCTION, "overworld_screen is nullptr but FastTravelState is successful."); } - + HyperspaceCalorieDetector hyperspace_calorie_detector(env.logger()); if (!hyperspace_calorie_detector.detect(*overworld_screen)){ stats.errors++; From 1b7915f5a069961db6653baf6b6e33597e33254b Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:01:52 -0500 Subject: [PATCH 02/10] Hyperspace Latias Hunt Prototype --- .../Source/CommonTools/Images/ImageFilter.cpp | 17 ++- .../Source/CommonTools/Images/ImageFilter.h | 12 ++ .../Source/PokemonLZA/PokemonLZA_Panels.cpp | 2 +- .../PokemonLZA_ShinyHunt_FlySpotReset.cpp | 2 +- ...kemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 126 +++++++++++++----- ...PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 1 + 6 files changed, 127 insertions(+), 33 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/Images/ImageFilter.cpp b/SerialPrograms/Source/CommonTools/Images/ImageFilter.cpp index 2c0c0033e4..c0ab77c964 100644 --- a/SerialPrograms/Source/CommonTools/Images/ImageFilter.cpp +++ b/SerialPrograms/Source/CommonTools/Images/ImageFilter.cpp @@ -9,6 +9,8 @@ #include "Kernels/ImageFilters/RGB32_Range/Kernels_ImageFilter_RGB32_Range.h" #include "Kernels/ImageFilters/RGB32_EuclideanDistance/Kernels_ImageFilter_RGB32_Euclidean.h" #include "Kernels/ImageFilters/RGB32_Brightness/Kernels_ImageFilter_RGB32_Brightness.h" +#include "CommonFramework/ImageTypes/ImageViewHSV32.h" +#include "CommonFramework/ImageTypes/ImageHSV32.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "ImageFilter.h" @@ -218,6 +220,19 @@ ImageRGB32 filter_green( return ret; } - +ImageRGB32 to_blackwhite_hsv32_range( + const ImageViewHSV32& image, + bool in_range_black, + uint32_t mins, uint32_t maxs +) { + ImageRGB32 ret(image.width(), image.height()); + Kernels::to_blackwhite_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + ret.data(), ret.bytes_per_row(), + in_range_black, + mins, maxs + ); + return ret; +} } diff --git a/SerialPrograms/Source/CommonTools/Images/ImageFilter.h b/SerialPrograms/Source/CommonTools/Images/ImageFilter.h index f7e1473558..3c76adcd55 100644 --- a/SerialPrograms/Source/CommonTools/Images/ImageFilter.h +++ b/SerialPrograms/Source/CommonTools/Images/ImageFilter.h @@ -14,6 +14,8 @@ namespace PokemonAutomation{ +class ImageViewHSV32; +class ImageHSV32; class ImageViewRGB32; class ImageRGB32; @@ -163,6 +165,16 @@ ImageRGB32 filter_green( ); +// Convert an HSV-format image to black and white. +// Inside [mins, maxs] is white, otherwise it's black. +// Set "in_range_black" to true to invert the colors. +// Both white and black colors have alpha=255. +ImageRGB32 to_blackwhite_hsv32_range( + const ImageViewHSV32& image, + bool in_range_black, + uint32_t mins, uint32_t maxs +); + } #endif diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index fccbcfdd6f..8d7e46f847 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -103,7 +103,7 @@ std::vector PanelListFactory::make_panels() const{ if (IS_BETA_VERSION){ ret.emplace_back(make_single_switch_program()); } - if (PreloadSettings::instance().DEVELOPER_MODE){ + if (true){//PreloadSettings::instance().DEVELOPER_MODE){ 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/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp index 71756bd18b..97881f6c84 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp @@ -220,7 +220,7 @@ bool route_hyperspace_wild_zone( // Fly from map to reset spawns std::shared_ptr overworld_screen; - FastTravelState travel_status = fly_from_map(env.console, context, &overworld_screen, BUTTON_PLUS); + FastTravelState travel_status = fly_from_map(env.console, context, &overworld_screen); if (travel_status != FastTravelState::SUCCESS){ stats.errors++; env.update_stats(); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index cbaf44663e..d1c42a14b6 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -5,11 +5,18 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageHSV32.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewHSV32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlay.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Images/ImageFilter.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/StartupChecks/VideoResolutionCheck.h" #include "ML/Inference/ML_YOLOv5Detector.h" @@ -81,6 +88,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() {Legendary::COBALION, "cobalion", "Cobalion"}, {Legendary::TERRAKION, "terrakion", "Terrakion"}, {Legendary::VIRIZION, "virizion", "Virizion"}, + {Legendary::LATIAS, "latias", "Latias"}, }, LockMode::LOCK_WHILE_RUNNING, Legendary::VIRIZION @@ -109,7 +117,51 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() } namespace { +// Save on the rooftop, facing the Latias spawning platform, but without having it spawned + bool hunt_latias(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats) + { + stats.spawns++; + pbf_press_button(context, BUTTON_Y, 160ms, 2000ms); + context.wait_for_all_requests(); + + // Capture an image of the screen region that Latias should spawn in and visually assess whether it's shiny. + ImageViewRGB32 full_image = ImageViewRGB32(env.console.video().snapshot()); + ImagePixelBox latias_search_zone = ImagePixelBox( + static_cast(full_image.width() * 0.4), + static_cast(full_image.height() * 0.2), + static_cast(full_image.width() * 0.6), + static_cast(full_image.height() * 0.4)); + ImageViewRGB32 cropped_image = extract_box_reference(full_image, latias_search_zone); + ImageHSV32 cropped_hsv_image = ImageHSV32(cropped_image); + ImageViewHSV32 cropped_hsv_image_view = ImageViewHSV32( + cropped_hsv_image.data(), + cropped_hsv_image.bytes_per_row(), + cropped_hsv_image.width(), + cropped_hsv_image.height()); + ImageRGB32 filtered_image_nonshiny = to_blackwhite_hsv32_range(cropped_hsv_image_view, false, 0xff006051, 0xff25ffd2); + ImageRGB32 filtered_image_shiny = to_blackwhite_hsv32_range(cropped_hsv_image_view, false, 0xff0d3d51, 0xff37ffd2); + double nonshiny_result = image_average(filtered_image_nonshiny).r; + double shiny_result = image_average(filtered_image_shiny).r; + + filtered_image_nonshiny.save("filtered_nonshiny.png"); + filtered_image_shiny.save("filtered_shiny.png"); + cropped_image.save("cropped.png"); + env.console.log("Saved images for Latias reset", COLOR_MAGENTA); + env.console.log(std::format("Score for non-shiny Latias: {}", nonshiny_result), COLOR_MAGENTA); + env.console.log(std::format("Score for shiny Latias: {}", shiny_result), COLOR_MAGENTA); + env.update_stats(); + + // For now, return true (triggering program stop) if a shiny is detected or a non-shiny is not detected + if (nonshiny_result < 0.18 || shiny_result > 0.18) { + return true; + } + else { + return false; + } +} // We save at warp pad and spawn Latios only once per game reset void hunt_latios( @@ -455,36 +507,50 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, } while (true){ - const int ret = run_until( - env.console, context, - [&](ProControllerContext& context){ - if (LEGENDARY == Legendary::LATIOS){ - hunt_latios(env, context, stats, MIN_CALORIE_TO_CATCH); - } else if (LEGENDARY == Legendary::VIRIZION){ - hunt_virizion_rooftop(env, context, stats, MIN_CALORIE_TO_CATCH, use_switch1_only_timings); - } else if (LEGENDARY == Legendary::TERRAKION){ - hunt_terrakion(env, context, stats, MIN_CALORIE_TO_CATCH); - } else if (LEGENDARY == Legendary::COBALION){ - hunt_cobalion(env, context, stats, MIN_CALORIE_TO_CATCH); - } else { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "legendary hunt not implemented", - env.console - ); - } - }, - {{shiny_detector}} - ); // end run_until() - shiny_detector.throw_if_no_sound(); - shiny_detector.clear(); - - if (ret == 0 && SHINY_DETECTED.on_shiny_sound( - env, env.console, context, - shiny_count, - shiny_coefficient - )){ - break; + if (LEGENDARY == Legendary::LATIAS) { + if (hunt_latias(env, context, stats)) { + break; // shiny found + } + } + else { + const int ret = run_until( + env.console, context, + [&](ProControllerContext& context) { + if (LEGENDARY == Legendary::LATIOS) { + hunt_latios(env, context, stats, MIN_CALORIE_TO_CATCH); + } + else if (LEGENDARY == Legendary::VIRIZION) { + hunt_virizion_rooftop(env, context, stats, MIN_CALORIE_TO_CATCH, use_switch1_only_timings); + } + else if (LEGENDARY == Legendary::TERRAKION) { + hunt_terrakion(env, context, stats, MIN_CALORIE_TO_CATCH); + } + else if (LEGENDARY == Legendary::COBALION) { + hunt_cobalion(env, context, stats, MIN_CALORIE_TO_CATCH); + } + else if (LEGENDARY == Legendary::LATIAS) { + hunt_latias(env, context, stats); + } + else { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "legendary hunt not implemented", + env.console + ); + } + }, + { {shiny_detector} } + ); // end run_until() + shiny_detector.throw_if_no_sound(); + shiny_detector.clear(); + + if (ret == 0 && SHINY_DETECTED.on_shiny_sound( + env, env.console, context, + shiny_count, + shiny_coefficient + )) { + break; + } } // no shiny sound detected or no shiny legendary detected. Reset game diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h index 1b2db114cb..9973f957f9 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h @@ -39,6 +39,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ COBALION, TERRAKION, VIRIZION, + LATIAS, }; private: From 022579b58679c65e76e649176c47212ac334a604 Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:44:06 -0500 Subject: [PATCH 03/10] Fix hue calculation bug in RGB to HSV conversion --- SerialPrograms/Source/CommonFramework/ImageTypes/ImageHSV32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/CommonFramework/ImageTypes/ImageHSV32.cpp b/SerialPrograms/Source/CommonFramework/ImageTypes/ImageHSV32.cpp index a0580c4be2..f5a75e3804 100644 --- a/SerialPrograms/Source/CommonFramework/ImageTypes/ImageHSV32.cpp +++ b/SerialPrograms/Source/CommonFramework/ImageTypes/ImageHSV32.cpp @@ -80,7 +80,7 @@ uint32_t hsv_to_rgb(uint32_t p){ double Hf = 0; if (delta > 0){ if (M == r){ - Hf = fmod((g - b)/(double)delta, 6.0); + Hf = fmod(fmod((g - b)/(double)delta, 6.0)+6.0, 6.0); }else if (M == g){ Hf = (b - r)/(double)delta + 2.0; }else{ From ce69bd8b43e0aef309780da51c73fb940dcbebe0 Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:01:22 -0500 Subject: [PATCH 04/10] Tweak HSV filters --- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index d1c42a14b6..19f3514e31 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -130,9 +130,9 @@ namespace { ImageViewRGB32 full_image = ImageViewRGB32(env.console.video().snapshot()); ImagePixelBox latias_search_zone = ImagePixelBox( static_cast(full_image.width() * 0.4), - static_cast(full_image.height() * 0.2), - static_cast(full_image.width() * 0.6), - static_cast(full_image.height() * 0.4)); + static_cast(full_image.height() * 0.2), + static_cast(full_image.width() * 0.6), + static_cast(full_image.height() * 0.4)); ImageViewRGB32 cropped_image = extract_box_reference(full_image, latias_search_zone); ImageHSV32 cropped_hsv_image = ImageHSV32(cropped_image); ImageViewHSV32 cropped_hsv_image_view = ImageViewHSV32( @@ -140,7 +140,7 @@ namespace { cropped_hsv_image.bytes_per_row(), cropped_hsv_image.width(), cropped_hsv_image.height()); - ImageRGB32 filtered_image_nonshiny = to_blackwhite_hsv32_range(cropped_hsv_image_view, false, 0xff006051, 0xff25ffd2); + ImageRGB32 filtered_image_nonshiny = to_blackwhite_hsv32_range(cropped_hsv_image_view, false, 0xffe76051, 0xffffffd2); ImageRGB32 filtered_image_shiny = to_blackwhite_hsv32_range(cropped_hsv_image_view, false, 0xff0d3d51, 0xff37ffd2); double nonshiny_result = image_average(filtered_image_nonshiny).r; double shiny_result = image_average(filtered_image_shiny).r; From b59dba1a4460b19591dba3577311ea25d1da9b0d Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:05:15 -0500 Subject: [PATCH 05/10] Update naming to avoid conflicts with other Latias programs --- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 11 ++++------- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 19f3514e31..1b2884dcf6 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -88,7 +88,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() {Legendary::COBALION, "cobalion", "Cobalion"}, {Legendary::TERRAKION, "terrakion", "Terrakion"}, {Legendary::VIRIZION, "virizion", "Virizion"}, - {Legendary::LATIAS, "latias", "Latias"}, + {Legendary::LATIAS_ALT, "latias_alt", "Latias_Alt"}, }, LockMode::LOCK_WHILE_RUNNING, Legendary::VIRIZION @@ -118,7 +118,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() namespace { // Save on the rooftop, facing the Latias spawning platform, but without having it spawned - bool hunt_latias(SingleSwitchProgramEnvironment& env, + bool hunt_latias_alt(SingleSwitchProgramEnvironment& env, ProControllerContext& context, ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats) { @@ -507,8 +507,8 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, } while (true){ - if (LEGENDARY == Legendary::LATIAS) { - if (hunt_latias(env, context, stats)) { + if (LEGENDARY == Legendary::LATIAS_ALT) { + if (hunt_latias_alt(env, context, stats)) { break; // shiny found } } @@ -528,9 +528,6 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, else if (LEGENDARY == Legendary::COBALION) { hunt_cobalion(env, context, stats, MIN_CALORIE_TO_CATCH); } - else if (LEGENDARY == Legendary::LATIAS) { - hunt_latias(env, context, stats); - } else { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h index 9973f957f9..e68378fd58 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h @@ -39,7 +39,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ COBALION, TERRAKION, VIRIZION, - LATIAS, + LATIAS_ALT, }; private: From 7edfd5406840215edd12cdc9ccfd60b2a7187a16 Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:41:42 -0500 Subject: [PATCH 06/10] Clean up in preparation for pull request --- SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp | 2 +- .../ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp | 2 +- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index 8d7e46f847..fccbcfdd6f 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -103,7 +103,7 @@ std::vector PanelListFactory::make_panels() const{ if (IS_BETA_VERSION){ ret.emplace_back(make_single_switch_program()); } - if (true){//PreloadSettings::instance().DEVELOPER_MODE){ + if (PreloadSettings::instance().DEVELOPER_MODE){ 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/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp index 97881f6c84..0d126913ef 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_FlySpotReset.cpp @@ -234,7 +234,7 @@ bool route_hyperspace_wild_zone( if (overworld_screen == nullptr){ throw InternalProgramError(&env.logger(), PA_CURRENT_FUNCTION, "overworld_screen is nullptr but FastTravelState is successful."); } - + HyperspaceCalorieDetector hyperspace_calorie_detector(env.logger()); if (!hyperspace_calorie_detector.detect(*overworld_screen)){ stats.errors++; diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 1b2884dcf6..6969cacb1f 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -118,6 +118,8 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() namespace { // Save on the rooftop, facing the Latias spawning platform, but without having it spawned +// Note that this program is different from other PLZA legendary programs in that it uses image analysis to identify the shiny. +// Additionally, respawning is accomplished by resetting the game, so there is no need to track calories. bool hunt_latias_alt(SingleSwitchProgramEnvironment& env, ProControllerContext& context, ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats) @@ -145,10 +147,10 @@ namespace { double nonshiny_result = image_average(filtered_image_nonshiny).r; double shiny_result = image_average(filtered_image_shiny).r; - filtered_image_nonshiny.save("filtered_nonshiny.png"); + /*filtered_image_nonshiny.save("filtered_nonshiny.png"); filtered_image_shiny.save("filtered_shiny.png"); cropped_image.save("cropped.png"); - env.console.log("Saved images for Latias reset", COLOR_MAGENTA); + env.console.log("Saved images for Latias reset", COLOR_MAGENTA);*/ env.console.log(std::format("Score for non-shiny Latias: {}", nonshiny_result), COLOR_MAGENTA); env.console.log(std::format("Score for shiny Latias: {}", shiny_result), COLOR_MAGENTA); @@ -156,10 +158,12 @@ namespace { // For now, return true (triggering program stop) if a shiny is detected or a non-shiny is not detected if (nonshiny_result < 0.18 || shiny_result > 0.18) { + env.console.log("Shiny Latias identified or regular Latias not identified. Stopping program.", COLOR_MAGENTA); return true; } else { return false; + env.console.log("Non-shiny Latias identified. Resetting the game.", COLOR_MAGENTA); } } From 1ad6ec4c69745bef8e22ee8822de2e4488b9902b Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:49:51 -0500 Subject: [PATCH 07/10] Fix missing bracket introduced during conflict resolution --- .../ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 19a39084df..833ae6c98c 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -694,8 +694,10 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, [&](ProControllerContext& context) { if (LEGENDARY == Legendary::LATIAS){ hunt_latias(env, context, stats, MIN_CALORIE_TO_CATCH); - } else if (LEGENDARY == Legendary::LATIOS){ + } + else if (LEGENDARY == Legendary::LATIOS) { hunt_latios(env, context, stats, MIN_CALORIE_TO_CATCH); + } else if (LEGENDARY == Legendary::VIRIZION) { hunt_virizion_rooftop(env, context, stats, MIN_CALORIE_TO_CATCH, use_switch1_only_timings); } From 9d5a5f39d1b3b3e0357382c19fe2272a5b596e4d Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Thu, 15 Jan 2026 19:27:36 -0500 Subject: [PATCH 08/10] Fix issue with return statement position --- .../ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 833ae6c98c..431ee9c869 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -163,8 +163,8 @@ namespace { return true; } else { - return false; env.console.log("Non-shiny Latias identified. Resetting the game.", COLOR_MAGENTA); + return false; } } // Start at the ladder up to Latias and use it to fix the position and camera angle From d7675cc21c1af0639806bba5c45a867ddeb5daa4 Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:13:47 -0500 Subject: [PATCH 09/10] Align PLZA Hyperspace Legendary Nomenclature --- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 13 +++++++------ .../PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 431ee9c869..8afeb436ab 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -84,12 +84,12 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() : SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::STOP_PROGRAM) , LEGENDARY("Legendary " + STRING_POKEMON + ":", { - {Legendary::LATIAS, "latias", "Latias"}, - {Legendary::LATIOS, "latios", "Latios"}, - {Legendary::COBALION, "cobalion", "Cobalion"}, - {Legendary::TERRAKION, "terrakion", "Terrakion"}, - {Legendary::VIRIZION, "virizion", "Virizion"}, - {Legendary::LATIAS_ALT, "latias_alt", "Latias_Alt"}, + {Legendary::LATIAS, "latias", "Latias: Shuttle Run"}, + {Legendary::LATIAS_ALT, "latias-game-reset", "Latias: Game Reset"}, + {Legendary::LATIOS, "latios", "Latios: Game Reset"}, + {Legendary::COBALION, "cobalion", "Cobalion: Shuttle Run"}, + {Legendary::TERRAKION, "terrakion", "Terrakion: Shuttle Run"}, + {Legendary::VIRIZION, "virizion", "Virizion: Shuttle Run"}, }, LockMode::LOCK_WHILE_RUNNING, Legendary::LATIAS @@ -97,6 +97,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() , MIN_CALORIE_TO_CATCH( "Minimum Cal. Reserved to Catch Legendary:
If applicable, the program will stop refreshing the Legendary spawn to give this amount of Calorie left for catching the Legendary." "
NOTE: use 5-star donut for best catch chance and enough time in the Legendary hyperspace." + "
NOTE: not required for Game Reset programs." "
Cal. per sec: 1 Star: 1 Cal./s, 2 Star: 1.6 Cal./s, 3 Star: 3.5 Cal./s, 4 Star: 7.5 Cal./s, 5 Star: 10 Cal./s", LockMode::UNLOCK_WHILE_RUNNING, 600, 0, 9999 // default, min, max diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h index 5f0da80d04..a4ef660754 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h @@ -36,11 +36,11 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ enum class Legendary{ LATIAS, + LATIAS_ALT, LATIOS, COBALION, TERRAKION, VIRIZION, - LATIAS_ALT, }; private: From a81d270e4146dcd3645051d560e9f97a47f74630 Mon Sep 17 00:00:00 2001 From: ercdndrs <55447330+ercdndrs@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:23:56 -0500 Subject: [PATCH 10/10] Fix PLZA Latias reset occasional false positive Add the option to adjust the shiny detection threshold (in case the user encounters false positives) --- ...kemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 23 +++++++++++++------ ...PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 28f3dc5e3d..b70272ca18 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -102,6 +102,13 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() LockMode::UNLOCK_WHILE_RUNNING, 600, 0, 9999 // default, min, max ) + , LATIAS_VISUAL_SHINY_THRESHOLD( + "Latias Visual Shiny Threshold Value:
Applies only to the Latias Game Reset program. This value is used to evaluate whether a shiny or regular Latias is detected." + "
NOTE: Increase this threshold to decrease the sensitivity of the detector (decreasing the false negative rate and true positive rate)." + "
NOTE: The default value of 18 typically works fine. However, some setups/capture cards may result in occasional false positives. If this issue occurs, try increasing the value in increments of 5.", + LockMode::LOCK_WHILE_RUNNING, + 18, 0, 100 // default, min, max + ) , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ &NOTIFICATION_STATUS, @@ -114,6 +121,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); PA_ADD_OPTION(LEGENDARY); PA_ADD_OPTION(MIN_CALORIE_TO_CATCH); + PA_ADD_OPTION(LATIAS_VISUAL_SHINY_THRESHOLD); PA_ADD_OPTION(SHINY_DETECTED); PA_ADD_OPTION(NOTIFICATIONS); } @@ -125,7 +133,8 @@ namespace { // Additionally, respawning is accomplished by resetting the game, so there is no need to track calories. bool hunt_latias_alt(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats) + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats, + SimpleIntegerOption& LATIAS_VISUAL_SHINY_THRESHOLD) { stats.spawns++; pbf_press_button(context, BUTTON_Y, 160ms, 2000ms); @@ -155,20 +164,20 @@ bool hunt_latias_alt(SingleSwitchProgramEnvironment& env, cropped_hsv_image_view, false, 0xff0d3d51, 0xff37ffd2 ); - double nonshiny_result = image_average(filtered_image_nonshiny).r; - double shiny_result = image_average(filtered_image_shiny).r; + double nonshiny_result = 100 * image_average(filtered_image_nonshiny).r; + double shiny_result = 100 * image_average(filtered_image_shiny).r; /*filtered_image_nonshiny.save("filtered_nonshiny.png"); filtered_image_shiny.save("filtered_shiny.png"); cropped_image.save("cropped.png"); env.console.log("Saved images for Latias reset", COLOR_MAGENTA);*/ - env.console.log(std::format("Score for non-shiny Latias: {}", nonshiny_result), COLOR_MAGENTA); - env.console.log(std::format("Score for shiny Latias: {}", shiny_result), COLOR_MAGENTA); + env.console.log(std::format("Score for non-shiny Latias: {} (considered non-shiny if over 18)", nonshiny_result), COLOR_MAGENTA); + env.console.log(std::format("Score for shiny Latias: {0} (considered shiny if over {1})", shiny_result, 0+LATIAS_VISUAL_SHINY_THRESHOLD), COLOR_MAGENTA); env.update_stats(); // For now, return true (triggering program stop) if a shiny is detected or a non-shiny is not detected - if (nonshiny_result < 0.18 || shiny_result > 0.18) { + if (nonshiny_result < 18 || shiny_result > LATIAS_VISUAL_SHINY_THRESHOLD) { env.console.log("Shiny Latias identified or regular Latias not identified. Stopping program.", COLOR_MAGENTA); return true; } @@ -706,7 +715,7 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, while (true){ if (LEGENDARY == Legendary::LATIAS_ALT){ - if (hunt_latias_alt(env, context, stats)){ + if (hunt_latias_alt(env, context, stats, LATIAS_VISUAL_SHINY_THRESHOLD)){ // shiny found SHINY_DETECTED.on_shiny_sighted( env, env.console, context, diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h index a4ef660754..34c9a4d7ed 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h @@ -48,6 +48,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ ShinySoundDetectedActionOption SHINY_DETECTED; EnumDropdownOption LEGENDARY; SimpleIntegerOption MIN_CALORIE_TO_CATCH; + SimpleIntegerOption LATIAS_VISUAL_SHINY_THRESHOLD; EventNotificationOption NOTIFICATION_STATUS; EventNotificationsOption NOTIFICATIONS;