From b0e2e7ddc77d76322a255f7de39eff0e208a91ff Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 23 May 2026 20:32:37 +0200 Subject: [PATCH] First fix to the 'Cannot open Rotom phone menu after multiple retries.' error --- .../PokemonSwSh_DialogBoxDetector.cpp | 44 ++++++++++++++ .../Inference/PokemonSwSh_DialogBoxDetector.h | 19 ++++++ .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 59 ++++++++++++++++++- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 10 ++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp index a16fdb66dd..4ecd3cf570 100644 --- a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp @@ -6,6 +6,9 @@ #include #include +#include +using std::cout; +using std::endl; #include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" @@ -65,6 +68,47 @@ bool BlackDialogBoxDetector::process_frame(const ImageViewRGB32& frame, WallCloc +namespace{ + std::array EGG_HATCH_DIALOG_BOXES{{ + {0.200, 0.900, 0.520, 0.050}, + {0.270, 0.820, 0.480, 0.050}, + }}; +} + +EggHatchBlackDialogBoxDetector::EggHatchBlackDialogBoxDetector(bool stop_on_detected) + : VisualInferenceCallback("EggHatchBlackDialogBoxDetector") + , m_stop_on_detected(stop_on_detected) + , m_detected(false) +{} + +void EggHatchBlackDialogBoxDetector::make_overlays(VideoOverlaySet& items) const{ + for (const auto& box : EGG_HATCH_DIALOG_BOXES){ + items.add(COLOR_YELLOW, box); + } +} + +bool EggHatchBlackDialogBoxDetector::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ + static size_t s_frame_count = 0; + ++s_frame_count; + + bool detected = true; + for (size_t i = 0; i < EGG_HATCH_DIALOG_BOXES.size(); ++i){ + ImageStats stats = image_stats(extract_box_reference(frame, EGG_HATCH_DIALOG_BOXES[i])); + // Print once per ~60 frames (~1s). Only runs while open_menu_to_fly is active. + if (s_frame_count % 60 == 0){ + cout << "EggHatchBlackDialogBox box[" << i << "]: avg_sum=" << stats.average.sum() + << " stddev_sum=" << stats.stddev.sum() << endl; + } + if (!is_black(stats, 210, 30)){ + detected = false; + break; + } + } + m_detected.store(detected, std::memory_order_release); + return detected && m_stop_on_detected; +} + + WhiteDialogBoxDetector::WhiteDialogBoxDetector(Color color) : m_color(color) , m_right(0.782, 0.850, 0.030, 0.050) diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h index 4f970abdec..d51816d99a 100644 --- a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h @@ -38,6 +38,25 @@ class BlackDialogBoxDetector : public VisualInferenceCallback{ }; +// Detects the semi-transparent dark dialog that appears when an egg begins hatching. +// Checks two horizontal bands inside the dialog that are uniformly near-black (~RGB 20-30). +class EggHatchBlackDialogBoxDetector : public VisualInferenceCallback{ +public: + EggHatchBlackDialogBoxDetector(bool stop_on_detected); + + bool detected() const{ + return m_detected.load(std::memory_order_acquire); + } + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; + +private: + bool m_stop_on_detected; + std::atomic m_detected; +}; + + class WhiteDialogBoxDetector : public StaticScreenDetector{ public: WhiteDialogBoxDetector(Color color = COLOR_RED); diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 66d818777d..00514f2058 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -380,8 +380,14 @@ bool EggAutonomous::run_batch( // we still need to fetch more eggs // Use fly to reset the location because now we don't know where the player character is. - const bool fly_from_overworld = true; - call_flying_taxi(env, context, fly_from_overworld); + // open_menu_to_fly handles the race where an egg starts hatching during the X press. + open_menu_to_fly(env, context, stats, num_eggs_hatched); + // open_menu_to_fly may have hatched the 5th egg during the X press race condition. + // After it returns the player is at loop start (flew back), so check if we're done. + if (num_eggs_hatched == 5 && m_num_eggs_retrieved == 5){ + m_player_at_loop_start = true; + break; + } restart_bike_loop = true; // We don't update i_bike_loop here because we haven't finished one full bike loop due to egg hatching } // end one bike loop @@ -464,6 +470,55 @@ void EggAutonomous::save_game(SingleSwitchProgramEnvironment& env, ProController mash_B_until_y_comm_icon(env, context, "Cannot detect end of saving game."); } +void EggAutonomous::open_menu_to_fly( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EggAutonomous_Descriptor::Stats& stats, + size_t& num_eggs_hatched +){ + const size_t MAX_RETRIES = 5; + for (size_t retry = 0; retry < MAX_RETRIES; ++retry){ + RotomPhoneMenuArrowWatcher menu_arrow(env.console.overlay()); + EggHatchBlackDialogBoxDetector black_dialog(true); + + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + // Press X then wait for the menu animation to complete. + // Do NOT press anything after X — the menu must stay open for the detector. + pbf_press_button(context, BUTTON_X, 160ms, + GameSettings::instance().OVERWORLD_TO_MENU_DELAY0); + ssf_do_nothing(context, 3000ms); + }, + {{menu_arrow, black_dialog}} + ); + + switch (ret){ + case 0: // Menu is open — navigate and fly (already in menu, skip X press) + call_flying_taxi(env, context, false); + return; + case 1: // Egg started hatching in the transition window — handle it then retry + ++num_eggs_hatched; + stats.m_hatched++; + env.update_stats(); + wait_for_egg_hatched(env, context, stats, num_eggs_hatched); + if (num_eggs_hatched < 5){ + pbf_move_left_joystick(context, {-1.0, -1.0}, 800ms, 80ms); + context.wait_for_all_requests(); + } + break; + default: // lambda timed out, X was ignored — retry + env.log("Menu didn't open, retrying X press."); + break; + } + } + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Cannot open Rotom phone menu after multiple retries.", + env.console + ); +} + void EggAutonomous::call_flying_taxi( SingleSwitchProgramEnvironment& env, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index a4790bafac..2f1308cab7 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -93,6 +93,16 @@ class EggAutonomous : public SingleSwitchProgramInstance{ bool need_taxi ); + // Press X to open the Rotom phone menu and fly home. Uses a state machine to handle + // the race condition where an egg starts hatching just as the menu is being opened. + // num_eggs_hatched is updated in-place if additional hatches are detected. + void open_menu_to_fly( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EggAutonomous_Descriptor::Stats& stats, + size_t& num_eggs_hatched + ); + // Used to wait until Y-Comm icon shows up. // Throw error if it does not find it after 10 sec. void mash_B_until_y_comm_icon(