diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp
new file mode 100644
index 000000000..7f944b0f7
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp
@@ -0,0 +1,124 @@
+/*  Party Held Item Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#include "Common/Cpp/Exceptions.h"
+#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h"
+#include "CommonTools/Images/WaterfillUtilities.h"
+#include "PokemonFRLG/PokemonFRLG_Settings.h"
+#include "PokemonFRLG_PartyHeldItemDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+class PartyHeldItemMatcher : public ImageMatch::WaterfillTemplateMatcher{
+public:
+    PartyHeldItemMatcher(const char* path)
+        : WaterfillTemplateMatcher(
+            path,
+            Color(210, 210, 100),
+            Color(255, 255, 190),
+            20
+        )
+    {
+        m_aspect_ratio_lower = 0.5;
+        m_aspect_ratio_upper = 2.0;
+        m_area_ratio_lower = 0.2;
+        m_area_ratio_upper = 4.0;
+    }
+
+    static const PartyHeldItemMatcher& matcher(){
+        static PartyHeldItemMatcher matcher("PokemonFRLG/PartyHeldItem.png");
+        return matcher;
+    }
+};
+
+ImageFloatBox PartyHeldItemDetector::box_for_slot(PartyHeldItemSlot slot){
+    switch (slot){
+    case PartyHeldItemSlot::SLOT_1:
+        return ImageFloatBox(0.069, 0.285, 0.025, 0.050);
+    case PartyHeldItemSlot::SLOT_2:
+        return ImageFloatBox(0.432, 0.150, 0.025, 0.050);
+    case PartyHeldItemSlot::SLOT_3:
+        return ImageFloatBox(0.432, 0.3, 0.025, 0.050);
+    case PartyHeldItemSlot::SLOT_4:
+        return ImageFloatBox(0.432, 0.45, 0.025, 0.050);
+    case PartyHeldItemSlot::SLOT_5:
+        return ImageFloatBox(0.432, 0.6, 0.025, 0.050);
+    case PartyHeldItemSlot::SLOT_6:
+        return ImageFloatBox(0.432, 0.725, 0.025, 0.050);
+    default:
+        break;
+    }
+    throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Party Held Item Slot");
+}
+
+PartyHeldItemDetector::PartyHeldItemDetector(
+    Color color,
+    VideoOverlay* overlay,
+    PartyHeldItemSlot slot
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_box(box_for_slot(slot))
+{}
+PartyHeldItemDetector::PartyHeldItemDetector(
+    Color color,
+    VideoOverlay* overlay,
+    const ImageFloatBox& box
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_box(box)
+{}
+void PartyHeldItemDetector::make_overlays(VideoOverlaySet& items) const{
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    items.add(m_color, GAME_BOX.inner_to_outer(m_box));
+}
+bool PartyHeldItemDetector::detect(const ImageViewRGB32& screen){
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX);
+
+    double screen_rel_size = (game_screen.height() / 1080.0);
+    double screen_rel_size_2 = screen_rel_size * screen_rel_size;
+
+    double min_area_1080p = 100;
+    double rmsd_threshold = 220;
+    size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
+
+    const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
+        {0xffd2d264, 0xffffffbe}
+    };
+
+    bool found = match_template_by_waterfill(
+        game_screen.size(),
+        extract_box_reference(game_screen, m_box),
+        PartyHeldItemMatcher::matcher(),
+        FILTERS,
+        {min_area, SIZE_MAX},
+        rmsd_threshold,
+        [&](Kernels::Waterfill::WaterfillObject& object) -> bool {
+            m_last_detected = translate_to_parent(game_screen, m_box, object);
+            return true;
+        }
+    );
+
+    if (m_overlay){
+        if (found){
+            m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN);
+        }else{
+            m_last_detected_box.reset();
+        }
+    }
+
+    return found;
+}
+
+
+}
+}
+}
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h
new file mode 100644
index 000000000..7dae06b1c
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h
@@ -0,0 +1,83 @@
+/*  Party Held Item Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#ifndef PokemonAutomation_PokemonFRLG_PartyHeldItemDetector_H
+#define PokemonAutomation_PokemonFRLG_PartyHeldItemDetector_H
+
+#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h"
+#include "CommonTools/VisualDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+enum class PartyHeldItemSlot{
+    SLOT_1,
+    SLOT_2,
+    SLOT_3,
+    SLOT_4,
+    SLOT_5,
+    SLOT_6
+};
+
+class PartyHeldItemDetector : public StaticScreenDetector{
+public:
+    PartyHeldItemDetector(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box
+    );
+    PartyHeldItemDetector(
+        Color color,
+        VideoOverlay* overlay,
+        PartyHeldItemSlot slot
+    );
+
+    static ImageFloatBox box_for_slot(PartyHeldItemSlot slot);
+
+    const ImageFloatBox& last_detected() const { return m_last_detected; }
+
+    virtual void make_overlays(VideoOverlaySet& items) const override;
+
+    // This is not const so that detectors can save/cache state.
+    virtual bool detect(const ImageViewRGB32& screen) override;
+
+private:
+    friend class PartyHeldItemWatcher;
+
+    const Color m_color;
+    VideoOverlay* m_overlay;
+    const ImageFloatBox m_box;
+
+    ImageFloatBox m_last_detected;
+    std::optional<OverlayBoxScope> m_last_detected_box;
+};
+class PartyHeldItemWatcher : public DetectorToFinder<PartyHeldItemDetector>{
+public:
+    PartyHeldItemWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("PartyHeldItemWatcher", hold_duration, color, overlay, box)
+    {}
+    PartyHeldItemWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        PartyHeldItemSlot slot,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("PartyHeldItemWatcher", hold_duration, color, overlay, box_for_slot(slot))
+    {}
+};
+
+
+}
+}
+}
+#endif
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp
new file mode 100644
index 000000000..8dc1cbfb6
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp
@@ -0,0 +1,171 @@
+/*  Battle Selection Arrow Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#include "Common/Cpp/Exceptions.h"
+#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h"
+#include "CommonTools/Images/WaterfillUtilities.h"
+#include "PokemonFRLG/PokemonFRLG_Settings.h"
+#include "PokemonFRLG_BattleSelectionArrowDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+class BattleSelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher{
+public:
+    BattleSelectionArrowMatcher(const char* path)
+        : WaterfillTemplateMatcher(
+            path,
+            Color(35, 40, 40), Color(100, 100, 100), 70
+        )
+    {
+        m_aspect_ratio_lower = 0.8;
+        m_aspect_ratio_upper = 1.2;
+        m_area_ratio_lower = 0.8;
+        m_area_ratio_upper = 1.2;
+    }
+
+    static const BattleSelectionArrowMatcher& matcher(){
+        static BattleSelectionArrowMatcher matcher("PokemonFRLG/BattleSelectionArrow.png");
+        return matcher;
+    }
+};
+
+ImageFloatBox BattleSelectionArrowDetector::box_for_option(BattleMenuOption option){
+    switch (option){
+    case BattleMenuOption::FIGHT:
+        return ImageFloatBox(0.533, 0.767, 0.024, 0.067);
+    case BattleMenuOption::BAG:
+        return ImageFloatBox(0.768, 0.767, 0.024, 0.067);
+    case BattleMenuOption::POKEMON:
+        return ImageFloatBox(0.533, 0.868, 0.024, 0.067);
+    case BattleMenuOption::RUN:
+        return ImageFloatBox(0.768, 0.868, 0.024, 0.067);
+    default:
+        break;
+    }
+    throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Battle Menu Option");
+}
+ImageFloatBox BattleSelectionArrowDetector::box_for_option(SafariBattleMenuOption option){
+    switch (option){
+    case SafariBattleMenuOption::BALL:
+        return ImageFloatBox(0.533, 0.767, 0.024, 0.067);
+    case SafariBattleMenuOption::BAIT:
+        return ImageFloatBox(0.768, 0.767, 0.024, 0.067);
+    case SafariBattleMenuOption::ROCK:
+        return ImageFloatBox(0.533, 0.868, 0.024, 0.067);
+    case SafariBattleMenuOption::RUN:
+        return ImageFloatBox(0.768, 0.868, 0.024, 0.067);
+    default:
+        break;
+    }
+    throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Battle Menu Option");
+}
+ImageFloatBox BattleSelectionArrowDetector::box_for_option(BattleConfirmationOption option){
+    switch (option){
+    case BattleConfirmationOption::YES:
+        return ImageFloatBox(0.795, 0.465, 0.030, 0.071);
+    case BattleConfirmationOption::NO:
+        return ImageFloatBox(0.795, 0.569, 0.030, 0.071);
+    default:
+        break;
+    }
+    throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Battle Confirmation Option");
+}
+
+BattleSelectionArrowDetector::BattleSelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay,
+    const ImageFloatBox& box
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(box)
+{}
+BattleSelectionArrowDetector::BattleSelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay,
+    BattleMenuOption option
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(box_for_option(option))
+{}
+BattleSelectionArrowDetector::BattleSelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay,
+    SafariBattleMenuOption option
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(box_for_option(option))
+{}
+BattleSelectionArrowDetector::BattleSelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(ImageFloatBox(0.768, 0.868, 0.212, 0.159))
+{
+}
+BattleSelectionArrowDetector::BattleSelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay,
+    BattleConfirmationOption option
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(box_for_option(option))
+{}
+void BattleSelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    items.add(m_color, GAME_BOX.inner_to_outer(m_arrow_box));
+}
+bool BattleSelectionArrowDetector::detect(const ImageViewRGB32& screen){
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX);
+
+    double screen_rel_size = (game_screen.height() / 1080.0);
+    double screen_rel_size_2 = screen_rel_size * screen_rel_size;
+
+    double min_area_1080p = 700;
+    double rmsd_threshold = 80;
+    size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
+
+    const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
+        {0xff232828, 0xff646464}
+    };
+
+    bool found = match_template_by_waterfill(
+        game_screen.size(),
+        extract_box_reference(game_screen, m_arrow_box),
+        BattleSelectionArrowMatcher::matcher(),
+        FILTERS,
+        {min_area, SIZE_MAX},
+        rmsd_threshold,
+        [&](Kernels::Waterfill::WaterfillObject& object) -> bool {
+            m_last_detected = translate_to_parent(game_screen, m_arrow_box, object);
+            return true;
+        }
+    );
+
+    if (m_overlay){
+        if (found){
+            m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN);
+        }else{
+            m_last_detected_box.reset();
+        }
+    }
+
+    return found;
+}
+
+
+}
+}
+}
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h
new file mode 100644
index 000000000..4557facee
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h
@@ -0,0 +1,135 @@
+/*  Battle Selection Arrow Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#ifndef PokemonAutomation_PokemonFRLG_BattleSelectionArrowDetector_H
+#define PokemonAutomation_PokemonFRLG_BattleSelectionArrowDetector_H
+
+#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h"
+#include "CommonTools/VisualDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+// The order of these enums matches the in-game layout (row-major, top-left to bottom-right).
+const int BATTLE_MENU_OPTION_COUNT = 4;
+enum class BattleMenuOption{
+    FIGHT,
+    BAG,
+    POKEMON,
+    RUN,
+};
+
+const int SAFARI_BATTLE_MENU_OPTION_COUNT = 4;
+enum class SafariBattleMenuOption{
+    BALL,
+    BAIT,
+    ROCK,
+    RUN,
+};
+
+enum class BattleConfirmationOption{
+    YES,
+    NO,
+};
+
+class BattleSelectionArrowDetector : public StaticScreenDetector{
+public:
+    BattleSelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box
+    );
+    BattleSelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay,
+        BattleMenuOption option
+    );
+    BattleSelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay,
+        SafariBattleMenuOption option
+    );
+    BattleSelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay
+    );
+    BattleSelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay,
+        BattleConfirmationOption option
+    );
+
+    static ImageFloatBox box_for_option(BattleMenuOption option);
+    static ImageFloatBox box_for_option(SafariBattleMenuOption option);
+    static ImageFloatBox box_for_option(BattleConfirmationOption option);
+
+    const ImageFloatBox& last_detected() const { return m_last_detected; }
+
+    virtual void make_overlays(VideoOverlaySet& items) const override;
+
+    // This is not const so that detectors can save/cache state.
+    virtual bool detect(const ImageViewRGB32& screen) override;
+
+private:
+    friend class BattleSelectionArrowWatcher;
+
+    const Color m_color;
+    VideoOverlay* m_overlay;
+    const ImageFloatBox m_arrow_box;
+
+    ImageFloatBox m_last_detected;
+    std::optional<OverlayBoxScope> m_last_detected_box;
+};
+class BattleSelectionArrowWatcher : public DetectorToFinder<BattleSelectionArrowDetector>{
+public:
+    BattleSelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box)
+    {}
+    BattleSelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        BattleMenuOption option,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option))
+    {}
+    BattleSelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        SafariBattleMenuOption option,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option))
+    {}
+    BattleSelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        BattleConfirmationOption option,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option))
+    {}
+    BattleSelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, ImageFloatBox(0.768, 0.868, 0.212, 0.159))
+    {}
+};
+
+
+}
+}
+}
+#endif
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp
new file mode 100644
index 000000000..ea4e741cf
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp
@@ -0,0 +1,103 @@
+/*  Pokedex Registered Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#include "Kernels/Waterfill/Kernels_Waterfill_Types.h"
+#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h"
+#include "CommonTools/Images/WaterfillUtilities.h"
+#include "PokemonFRLG/PokemonFRLG_Settings.h"
+#include "PokemonFRLG_PokedexRegisteredDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+class PokedexRegisteredMatcher : public ImageMatch::WaterfillTemplateMatcher{
+public:
+    PokedexRegisteredMatcher()
+        : WaterfillTemplateMatcher(
+            "PokemonFRLG/PokedexButtonA.png",
+            Color(128, 128, 128), Color(255, 255, 255), 100
+        )
+    {
+        m_aspect_ratio_lower = 0.5;
+        m_aspect_ratio_upper = 2.0;
+        m_area_ratio_lower = 0.5;
+        m_area_ratio_upper = 2.0;
+    }
+
+    static const PokedexRegisteredMatcher& matcher(){
+        static PokedexRegisteredMatcher matcher;
+        return matcher;
+    }
+};
+
+
+PokedexRegisteredDetector::PokedexRegisteredDetector(
+    Color color,
+    VideoOverlay* overlay,
+    const ImageFloatBox& box
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_box(box)
+{}
+
+void PokedexRegisteredDetector::make_overlays(VideoOverlaySet& items) const{
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    items.add(m_color, GAME_BOX.inner_to_outer(m_box));
+}
+
+bool PokedexRegisteredDetector::detect(const ImageViewRGB32& screen){
+    const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
+    ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX);
+
+    double screen_rel_size = (game_screen.height() / 1080.0);
+    double screen_rel_size_2 = screen_rel_size * screen_rel_size;
+
+    double min_area_1080p = 700;
+    double rmsd_threshold = 70;
+    size_t min_area = size_t(screen_rel_size_2 * min_area_1080p);
+
+    const std::vector<std::pair<uint32_t, uint32_t>> FILTERS = {
+        {0xff808080, 0xffffffff},
+        {0xff909090, 0xffffffff},
+        {0xffa0a0a0, 0xffffffff},
+        {0xffb0b0b0, 0xffffffff},
+        {0xffc0c0c0, 0xffffffff},
+        {0xffd0d0d0, 0xffffffff},
+        {0xffe0e0e0, 0xffffffff},
+        {0xfff0f0f0, 0xffffffff},
+    };
+
+    bool found = match_template_by_waterfill(
+        game_screen.size(),
+        extract_box_reference(game_screen, m_box),
+        PokedexRegisteredMatcher::matcher(),
+        FILTERS,
+        {min_area, SIZE_MAX},
+        rmsd_threshold,
+        [&](Kernels::Waterfill::WaterfillObject& object) -> bool {
+            m_last_detected = translate_to_parent(game_screen, m_box, object);
+            return true;
+        }
+    );
+
+    if (m_overlay){
+        if (found){
+            m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN);
+        }else{
+            m_last_detected_box.reset();
+        }
+    }
+
+    return found;
+}
+
+
+}
+}
+}
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h
new file mode 100644
index 000000000..1c0a8dfe3
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h
@@ -0,0 +1,59 @@
+/*  Pokedex Registered Detector
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#ifndef PokemonAutomation_PokemonFRLG_PokedexRegisteredDetector_H
+#define PokemonAutomation_PokemonFRLG_PokedexRegisteredDetector_H
+
+#include "optional"
+#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h"
+#include "CommonTools/VisualDetector.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+// Detects the white "A" button prompt that appears in the bottom right of the
+// screen when a Pokémon is registered in the Pokédex.
+class PokedexRegisteredDetector : public StaticScreenDetector{
+public:
+    PokedexRegisteredDetector(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box = ImageFloatBox(0.85, 0.923, 0.082, 0.070)
+    );
+
+    virtual void make_overlays(VideoOverlaySet& items) const override;
+    virtual bool detect(const ImageViewRGB32& screen) override;
+
+    virtual void reset_state() override { m_last_detected_box.reset(); }
+
+private:
+    Color m_color;
+    VideoOverlay* m_overlay;
+    const ImageFloatBox m_box;
+
+    ImageFloatBox m_last_detected;
+    std::optional<OverlayBoxScope> m_last_detected_box;
+};
+
+class PokedexRegisteredWatcher : public DetectorToFinder<PokedexRegisteredDetector>{
+public:
+    PokedexRegisteredWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        const ImageFloatBox& box = ImageFloatBox(0.85, 0.923, 0.082, 0.070),
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("PokedexRegisteredWatcher", hold_duration, color, overlay, box)
+    {}
+};
+
+
+}
+}
+}
+#endif
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp
index dcc378440..f03a84883 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp
@@ -36,6 +36,30 @@ ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPosit
     throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Selection Arrow Position");
 }
 
+ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionSafariMenu position){
+    // Safari Zone menu has the same 7 slots as the overworld menu; RETIRE occupies slot 0,
+    // shifting POKEDEX..EXIT each down one relative to the overworld enum.
+    switch (position){
+    case SelectionArrowPositionSafariMenu::RETIRE:
+        return ImageFloatBox(0.727692, 0.0523077, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::POKEDEX:
+        return ImageFloatBox(0.727692, 0.1457692, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::POKEMON:
+        return ImageFloatBox(0.727692, 0.2392307, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::BAG:
+        return ImageFloatBox(0.727692, 0.3378846, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::TRAINER:
+        return ImageFloatBox(0.727692, 0.4261538, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::OPTION:
+        return ImageFloatBox(0.727692, 0.5248076, 0.0369231, 0.0778846);
+    case SelectionArrowPositionSafariMenu::EXIT:
+        return ImageFloatBox(0.727692, 0.6182692, 0.0369231, 0.0778846);
+    default:
+        break;
+    }
+    throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Selection Arrow Position");
+}
+
 ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionConfirmationMenu position){
     switch (position){
     case SelectionArrowPositionConfirmationMenu::YES:
@@ -96,6 +120,16 @@ SelectionArrowDetector::SelectionArrowDetector(
     , m_arrow_box(arrow_box_for_position(position))
 {
 }
+SelectionArrowDetector::SelectionArrowDetector(
+    Color color,
+    VideoOverlay* overlay,
+    SelectionArrowPositionSafariMenu position
+)
+    : m_color(color)
+    , m_overlay(overlay)
+    , m_arrow_box(arrow_box_for_position(position))
+{
+}
 void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{
     const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
     items.add(m_color, GAME_BOX.inner_to_outer(m_arrow_box));
diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h
index d9f32c682..49564d77b 100644
--- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h
+++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h
@@ -14,8 +14,19 @@ namespace PokemonAutomation{
 namespace NintendoSwitch{
 namespace PokemonFRLG{
 
-const int START_MENU_OPTION_COUNT = 7;
 // The order of these enums should be the same as the order of options in the game menu, from top to bottom, for ease of use with loops.
+const int SAFARI_START_MENU_OPTION_COUNT = 7;
+enum class SelectionArrowPositionSafariMenu {
+    RETIRE,
+    POKEDEX,
+    POKEMON,
+    BAG,
+    TRAINER,
+    OPTION,
+    EXIT
+};
+
+const int START_MENU_OPTION_COUNT = 7;
 enum class SelectionArrowPositionStartMenu{
     POKEDEX,
     POKEMON,
@@ -45,6 +56,12 @@ class SelectionArrowDetector : public StaticScreenDetector{
         SelectionArrowPositionStartMenu position
     );
 
+    SelectionArrowDetector(
+        Color color,
+        VideoOverlay* overlay,
+        SelectionArrowPositionSafariMenu position
+    );
+
     SelectionArrowDetector(
         Color color,
         VideoOverlay* overlay,
@@ -52,14 +69,16 @@ class SelectionArrowDetector : public StaticScreenDetector{
     );
 
     static ImageFloatBox arrow_box_for_position(SelectionArrowPositionStartMenu position);
-    
+
+    static ImageFloatBox arrow_box_for_position(SelectionArrowPositionSafariMenu position);
+
     static ImageFloatBox arrow_box_for_position(SelectionArrowPositionConfirmationMenu position);
 
     const ImageFloatBox& last_detected() const { return m_last_detected; }
 
     virtual void make_overlays(VideoOverlaySet& items) const override;
 
-    //  This is not const so that detectors can save/cache state.
+    // This is not const so that detectors can save/cache state.
     virtual bool detect(const ImageViewRGB32& screen) override;
 
 private:
@@ -91,6 +110,15 @@ class SelectionArrowWatcher : public DetectorToFinder<SelectionArrowDetector>{
         : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position))
     {
     }
+    SelectionArrowWatcher(
+        Color color,
+        VideoOverlay* overlay,
+        SelectionArrowPositionSafariMenu position,
+        std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250)
+    )
+        : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position))
+    {
+    }
     SelectionArrowWatcher(
         Color color,
         VideoOverlay* overlay,
diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp
index 91b8a670e..c8eb7f53a 100644
--- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp
@@ -609,7 +609,7 @@ void heal_at_pokecenter(ConsoleHandle& console, ProControllerContext& context){
     }
 }
 
-void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context){
+void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context){
     uint16_t errors = 0;
     bool start_menu_is_open = false;
     while (true){
@@ -637,7 +637,12 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext
 
         switch (ret){
         case 0:
-            ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::POKEMON);
+            if (menu_context == StartMenuContext::SAFARI_ZONE){
+                ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::POKEMON);
+            } else {
+                ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::POKEMON);
+            }
+
             if (ret < 0){
                 console.log("Failed to navigate to POKEMON on the start menu.");
                 errors++;
@@ -649,6 +654,7 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext
                 context.wait_for_all_requests();
                 pbf_press_button(context, BUTTON_A, 200ms, 1300ms);
             }
+
             continue;
         case 1:
             console.log("Party menu opened.");
diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h
index d593bffd9..b035c87c6 100644
--- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h
+++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h
@@ -55,7 +55,11 @@ void leave_pokecenter(ConsoleHandle& console, ProControllerContext& context);
 void heal_at_pokecenter(ConsoleHandle& console, ProControllerContext& context);
 
 // Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the party screen
-void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context);
+enum class StartMenuContext {
+    STANDARD,
+    SAFARI_ZONE
+};
+void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD);
 
 // Go to home to check that scaling is 100%. Then resume game.
 void home_black_border_check(ConsoleHandle& console, ProControllerContext& context);
diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp
index fa60825a3..c7b21b27a 100644
--- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp
@@ -10,6 +10,7 @@
 
 #include "PokemonFRLG_Settings.h"
 
+#include "Programs/Farming/PokemonFRLG_LuckyEggFarmer.h"
 #include "Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h"
 #include "Programs/Farming/PokemonFRLG_PickupFarmer.h"
 #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h"
@@ -41,6 +42,7 @@ std::vector<PanelEntry> PanelListFactory::make_panels() const{
     ret.emplace_back("---- Farming ----");
     ret.emplace_back(make_single_switch_program<NuggetBridgeFarmer_Descriptor, NuggetBridgeFarmer>());
     if (PreloadSettings::instance().DEVELOPER_MODE){
+        ret.emplace_back(make_single_switch_program<LuckyEggFarmer_Descriptor, LuckyEggFarmer>());
         ret.emplace_back(make_single_switch_program<PickupFarmer_Descriptor, PickupFarmer>());
     }
 
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp
new file mode 100644
index 000000000..05ab64ed1
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp
@@ -0,0 +1,517 @@
+/*  Lucky Egg Farmer
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#include "Common/Cpp/Options/ButtonOption.h"
+#include "CommonFramework/ImageTools/ImageBoxes.h"
+#include "CommonFramework/Language.h"
+#include "CommonFramework/Notifications/ProgramNotifications.h"
+#include "CommonFramework/ProgramStats/StatsTracking.h"
+#include "CommonFramework/VideoPipeline/VideoFeed.h"
+#include "CommonTools/Async/InferenceRoutines.h"
+#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
+#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h"
+#include "Pokemon/Pokemon_Strings.h"
+#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h"
+#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h"
+#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h"
+#include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h"
+#include "PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h"
+#include "PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h"
+#include "PokemonFRLG/PokemonFRLG_Navigation.h"
+#include "PokemonFRLG_LuckyEggFarmer.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+LuckyEggFarmer_Descriptor::LuckyEggFarmer_Descriptor()
+    : SingleSwitchProgramDescriptor(
+        "PokemonFRLG:LuckyEggFarmer",
+        Pokemon::STRING_POKEMON + " FRLG", "Lucky Egg Farmer",
+        "Programs/PokemonFRLG/LuckyEggFarmer.html",
+        "Farm the Lucky Egg from Chansey.",
+        ProgramControllerClass::StandardController_RequiresPrecision,
+        FeedbackType::REQUIRED,
+        AllowCommandsWhenRunning::DISABLE_COMMANDS
+    )
+{}
+
+struct LuckyEggFarmer_Descriptor::Stats : public StatsTracker {
+    public:
+    Stats()
+        : chanseys(m_stats["Chanseys Caught"])
+        , eggs(m_stats["Lucky Eggs Found"])
+        , shinies(m_stats["Shinies"])
+        , errors(m_stats["Errors"])
+    {
+        m_display_order.emplace_back("Chanseys Caught");
+        m_display_order.emplace_back("Lucky Eggs Found");
+        m_display_order.emplace_back("Shinies");
+        m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO);
+    }
+
+    std::atomic<uint64_t>& chanseys;
+    std::atomic<uint64_t>& eggs;
+    std::atomic<uint64_t>& shinies;
+    std::atomic<uint64_t>& errors;
+};
+
+std::unique_ptr<StatsTracker> LuckyEggFarmer_Descriptor::make_stats() const{
+    return std::unique_ptr<StatsTracker>(new Stats());
+}
+
+LuckyEggFarmer::LuckyEggFarmer()
+    : LANGUAGE(
+        "<b>Game Language:</b>",
+        {
+            Language::English,
+            Language::Japanese,
+            Language::Spanish,
+            Language::French,
+            Language::German,
+            Language::Italian,
+            Language::Korean,
+            Language::ChineseSimplified,
+            Language::ChineseTraditional,
+        },
+        LockMode::LOCK_WHILE_RUNNING,
+        true
+        )
+    , STOP_AFTER_CURRENT("Reset")
+    , TAKE_VIDEO("<b>Take Video:</b><br>Record a video when the shiny is found.", LockMode::UNLOCK_WHILE_RUNNING, true)
+    , GO_HOME_WHEN_DONE(false)
+    , NOTIFICATION_SHINY(
+        "Shiny found",
+        true, true, ImageAttachmentMode::JPG,
+        { "Notifs", "Showcase" }
+    )
+    , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600))
+    , NOTIFICATIONS({
+        &NOTIFICATION_SHINY,
+        &NOTIFICATION_STATUS_UPDATE,
+        &NOTIFICATION_PROGRAM_FINISH,
+        &NOTIFICATION_ERROR_FATAL,
+    })
+{
+    PA_ADD_OPTION(STOP_AFTER_CURRENT);
+    PA_ADD_OPTION(TAKE_VIDEO);
+    PA_ADD_OPTION(GO_HOME_WHEN_DONE);
+
+    PA_ADD_OPTION(NOTIFICATIONS);
+}
+
+bool LuckyEggFarmer::navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context){
+    BlackScreenWatcher zone_exit(COLOR_RED);
+
+    int ret = run_until<ProControllerContext>(
+        console, context,
+        [](ProControllerContext& context) {
+            ssf_press_button(context, BUTTON_B, 0ms, 4045ms);
+            pbf_press_dpad(context, DPAD_UP, 100ms, 0ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 1966ms, 0ms);
+            pbf_press_dpad(context, DPAD_UP, 1800ms, 0ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 450ms, 0ms);
+            pbf_wait(context, 1000ms);
+        },
+        { zone_exit }
+    );
+
+    context.wait_for_all_requests();
+
+    if (ret != 0){
+        console.log("Failed to detect zone transition after initial navigation.");
+        return false;
+    }
+
+    BlackScreenOverWatcher overworld_entered(COLOR_RED);
+
+    ret = wait_until(
+        console, context,
+        std::chrono::milliseconds(2000),
+        { overworld_entered }
+    );
+
+    if (ret != 0){
+        console.log("Failed to detect overworld after first zone transition.");
+        return false;
+    }
+
+    console.log("Exiting first zone...");
+
+    pbf_wait(context, 1000ms);
+    context.wait_for_all_requests();
+
+    ret = run_until<ProControllerContext>(
+        console, context,
+        [](ProControllerContext& context) {
+            ssf_press_button(context, BUTTON_B, 0ms, 143005ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 1600ms, 0ms);
+            pbf_press_dpad(context, DPAD_DOWN, 100ms, 0ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 2650ms, 0ms);
+            pbf_press_dpad(context, DPAD_UP, 600ms, 0ms);
+            pbf_press_dpad(context, DPAD_LEFT, 920ms, 0ms);
+            pbf_press_dpad(context, DPAD_DOWN, 325ms, 0ms);
+            pbf_press_dpad(context, DPAD_LEFT, 800ms, 0ms);
+            pbf_press_dpad(context, DPAD_UP, 2000ms, 0ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 975ms, 0ms);
+            pbf_press_dpad(context, DPAD_DOWN, 300ms, 0ms);
+            pbf_press_dpad(context, DPAD_RIGHT, 550ms, 0ms);
+            pbf_press_dpad(context, DPAD_UP, 675ms, 0ms);
+            pbf_press_dpad(context, DPAD_LEFT, 2000ms, 0ms);
+            pbf_press_dpad(context, DPAD_DOWN, 130ms, 0ms);
+            pbf_press_dpad(context, DPAD_LEFT, 3000ms, 0ms);
+        },
+        { zone_exit }
+    );
+
+    if (ret != 0){
+        console.log("Failed to detect zone transition while exiting second zone.");
+        return false;
+    }
+
+    context.wait_for_all_requests();
+
+    ret = wait_until(
+        console, context,
+        std::chrono::milliseconds(2000),
+        { overworld_entered }
+    );
+
+    if (ret != 0){
+        console.log("Failed to detect overworld after second zone transition.");
+        return false;
+    }
+
+    console.log("Exiting second zone...");
+
+    pbf_wait(context, 1000ms);
+    context.wait_for_all_requests();
+
+    ssf_press_button(context, BUTTON_B, 0ms, 2800ms);
+    pbf_press_dpad(context, DPAD_LEFT, 2000ms, 0ms);
+    pbf_press_dpad(context, DPAD_UP, 800ms, 0ms);
+
+    context.wait_for_all_requests();
+
+    return true;
+}
+
+void LuckyEggFarmer::swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context){
+    open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE);
+    pbf_press_button(context, BUTTON_A, 250ms, 100ms);
+    pbf_press_dpad(context, DPAD_UP, 250ms, 100ms);
+    pbf_press_dpad(context, DPAD_UP, 250ms, 100ms);
+    pbf_press_dpad(context, DPAD_UP, 250ms, 100ms);
+    pbf_press_button(context, BUTTON_A, 250ms, 100ms);
+    pbf_press_dpad(context, DPAD_RIGHT, 250ms, 100ms);
+    pbf_press_button(context, BUTTON_A, 250ms, 100ms);
+    pbf_wait(context, 1000ms);
+    pbf_mash_button(context, BUTTON_B, 1500ms);
+    context.wait_for_all_requests();
+}
+
+bool LuckyEggFarmer::find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context) {
+    ssf_press_button(context, BUTTON_B, 0ms, 400ms);
+    pbf_press_dpad(context, DPAD_RIGHT, 400ms, 0ms);
+    pbf_wait(context, 100ms);
+    context.wait_for_all_requests();
+
+    BlackScreenWatcher battle_entered(COLOR_RED);
+
+    // This could be removed if spin in place stops drifting.
+    AdvanceWhiteDialogWatcher out_of_steps(COLOR_RED);
+
+    while (true) {
+        int ret = run_until<ProControllerContext>(
+            env.console, context,
+            [](ProControllerContext& context) {
+                pbf_move_left_joystick(context, { +1, 0 }, 50ms, 150ms);
+                context.wait_for_all_requests();
+                pbf_move_left_joystick(context, { -1, 0 }, 33ms, 150ms);
+                context.wait_for_all_requests();
+            },
+            { battle_entered, out_of_steps }
+        );
+
+        pbf_wait(context, 100ms);
+        context.wait_for_all_requests();
+
+        if (ret == 0) {
+            env.log("Battle entered.");
+            return true;
+        }
+        else if (ret == 1) {
+            env.log("Out of steps dialog detected. Resetting...");
+            return false;
+        }
+    }
+
+    return false;
+}
+
+bool LuckyEggFarmer::is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
+    std::set<std::string> subset = { "nidoran-f", "nidoran-m", "nidorino", "nidorina", "exeggcute", "rhyhorn", "venomoth", "chansey", "tauros" };
+
+    WildEncounterReader reader(COLOR_RED);
+    VideoOverlaySet overlays(env.console.overlay());
+    reader.make_overlays(overlays);
+
+    env.log("Reading name...");
+    VideoSnapshot screen = env.console.video().snapshot();
+    PokemonFRLG_WildEncounter encounter = reader.read_encounter(env.logger(), LANGUAGE, screen, subset);
+    env.log("Name: " + encounter.name);
+
+    if (encounter.name != "chansey") {
+        env.log("Not a Chansey. Fleeing...");
+        flee_battle(env.console, context);
+        context.wait_for_all_requests();
+        return false;
+    }
+    else {
+        env.log("Chansey found!");
+        return true;
+    }
+}
+
+bool LuckyEggFarmer::attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left) {
+    //TODO: Optimal bait/ball throwing
+
+    while (true)
+    {
+        BattleSelectionArrowWatcher nickname_question_arrow(
+            COLOR_RED,
+            &env.console.overlay(),
+            BattleConfirmationOption::YES
+        );
+
+        BattleSelectionArrowWatcher battle_arrow(
+            COLOR_RED,
+            &env.console.overlay(),
+            SafariBattleMenuOption::BALL
+        );
+
+        BlackScreenWatcher battle_end(COLOR_RED);
+
+        AdvanceBattleDialogWatcher advance_battle_dialog(COLOR_RED);
+
+        PokedexRegisteredWatcher pokedex_registered(COLOR_RED, &env.console.overlay());
+
+        WhiteDialogWatcher in_safari_zone_building(COLOR_RED);
+
+        WallClock start = current_time();
+        while (true)
+        {
+            if (current_time() - start > std::chrono::seconds(20)) {
+                env.log("No battle activity detected for 20 seconds. Assuming battle ended and in the overworld.");
+
+                //Check for safari zone building dialog?
+
+                return false;
+            }
+
+            int ret = wait_until(
+                env.console, context,
+                std::chrono::milliseconds(2000),
+                { nickname_question_arrow, battle_arrow, battle_end, advance_battle_dialog }
+            );
+
+            context.wait_for_all_requests();
+
+            if (ret == 0 || ret == 3) {
+                env.log("Caught a Chansey!");
+
+                while (true)
+                {
+                    int ret2 = wait_until(
+                        env.console, context,
+                        std::chrono::milliseconds(2000),
+                        { nickname_question_arrow, advance_battle_dialog, pokedex_registered, in_safari_zone_building }
+                    );
+
+                    if (ret2 == 0 || ret2 == 3) {
+                        pbf_mash_button(context, BUTTON_B, 2000ms);
+                        context.wait_for_all_requests();
+                        break;
+                    }
+                    else if (ret2 == 1 || ret2 == 2) {
+                        pbf_press_button(context, BUTTON_B, 200ms, 0ms);
+                        context.wait_for_all_requests();
+                    }
+                }
+
+                pbf_mash_button(context, BUTTON_B, 1500ms);
+                context.wait_for_all_requests();
+                return true;
+            }
+            else if (ret == 1) {
+                balls_left--;
+                env.log("Detected battle arrow. Balls left: " + std::to_string(balls_left));
+                pbf_press_button(context, BUTTON_A, 200ms, 200ms);
+                context.wait_for_all_requests();
+                break;
+            }
+            else if (ret == 2) {
+                env.log("Failed to catch Chansey.");
+                pbf_wait(context, 1000ms);
+                context.wait_for_all_requests();
+                return false;
+            }
+        }
+    }
+}
+
+bool LuckyEggFarmer::check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context) {
+    open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE);
+    PartyHeldItemDetector held_item_detector(COLOR_RED, &console.overlay(), ImageFloatBox(0.432, 0.3, 0.030, 0.485));
+    if (held_item_detector.detect(console.video().snapshot())) {
+        return true;
+    }
+
+    return false;
+}
+
+void LuckyEggFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
+
+    home_black_border_check(env.console, context);
+
+    LuckyEggFarmer_Descriptor::Stats& stats = env.current_stats<LuckyEggFarmer_Descriptor::Stats>();
+    DeferredStopButtonOption::ResetOnExit reset_on_exit(STOP_AFTER_CURRENT);
+
+    while (true){
+        send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE);
+        
+        pbf_press_dpad(context, DPAD_UP, 200ms, 0ms);
+
+        while (true)
+        {
+            BlackScreenWatcher safari_zone_exit(COLOR_RED);
+
+            int ret = run_until<ProControllerContext>(
+                env.console, context,
+                [](ProControllerContext& context) {
+                    pbf_press_button(context, BUTTON_A, 200ms, 200ms);
+                },
+                { safari_zone_exit }
+            );
+
+            if (ret == 0) {
+                break;
+            }
+        }
+
+        while (true) {
+            BlackScreenOverWatcher overworld_entered(COLOR_RED);
+
+            int ret = wait_until(
+                env.console, context,
+                std::chrono::milliseconds(2000),
+                { overworld_entered }
+            );
+
+            if (ret == 0) {
+                break;
+            }
+        }
+
+        // There is a small delay from seeing the overworld to being able to actually move.
+        pbf_wait(context, 2000ms);
+        context.wait_for_all_requests();
+
+        if (!navigate_to_chansey(env.console, context)){
+            env.console.log("Navigation failed. Resetting...");
+            soft_reset(env.console, context);
+            continue;
+        }
+
+        swap_lead_pokemon(env.console, context);
+
+        int chancy_count = 0;
+        int balls_left = 30;
+
+        while (chancy_count < 4) {
+            if (!find_encounter(env, context)) {
+                break;
+            }
+
+            bool encounter_shiny = handle_encounter(env.console, context, true);
+            if (encounter_shiny) {
+                stats.shinies++;
+                env.update_stats();
+                send_program_notification(
+                    env,
+                    NOTIFICATION_SHINY,
+                    COLOR_YELLOW,
+                    "Shiny found!",
+                    {}, "",
+                    env.console.video().snapshot(),
+                    true
+                );
+                if (TAKE_VIDEO) {
+                    pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms);
+                }
+                return;
+            }
+
+            if (!is_chansey(env, context)) {
+                continue;
+            }
+
+            bool caught = attempt_catch(env, context, balls_left);
+
+            if (caught){
+                stats.chanseys++;
+                env.update_stats();
+                chancy_count++;
+            }
+
+            pbf_wait(context, 500ms);
+            context.wait_for_all_requests();
+
+            WhiteDialogDetector dialog(COLOR_RED);
+            bool in_safari_zone_building = dialog.detect(env.console.video().snapshot());
+
+            if (in_safari_zone_building && !caught) {
+                break;
+            }
+
+            if (in_safari_zone_building) {
+                pbf_mash_button(context, BUTTON_B, 500ms);
+                context.wait_for_all_requests();
+            }
+
+            if (caught) {
+                if (check_for_lucky_egg(env.console, context)) {
+                    env.log("Lucky Egg found!");
+                    stats.eggs++;
+                    env.update_stats();
+                    return;
+                }
+                env.log("Lucky Egg not found. Continuing to farm...");
+                pbf_mash_button(context, BUTTON_B, 1500ms);
+                context.wait_for_all_requests();
+            }
+
+            if (balls_left <= 0) {
+                env.log("Out of Safari balls. Resetting...");
+                break;
+            }
+        }
+
+        soft_reset(env.console, context);
+
+        if (STOP_AFTER_CURRENT.should_stop()){
+            break;
+        }
+    }
+
+    send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
+    GO_HOME_WHEN_DONE.run_end_of_program(context);
+}
+
+
+}
+}
+}
\ No newline at end of file
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h
new file mode 100644
index 000000000..dc426391c
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h
@@ -0,0 +1,67 @@
+/*  Lucky Egg Farmer
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#ifndef PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H
+#define PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H
+
+#include "Common/Cpp/Options/ButtonOption.h"
+#include "CommonTools/Options/LanguageOCROption.h"
+#include "CommonFramework/Notifications/EventNotificationOption.h"
+#include "CommonFramework/Notifications/EventNotificationsTable.h"
+#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h"
+#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h"
+#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+class LuckyEggFarmer_Descriptor : public SingleSwitchProgramDescriptor{
+public:
+    LuckyEggFarmer_Descriptor();
+    struct Stats;
+    virtual std::unique_ptr<StatsTracker> make_stats() const override;
+};
+
+class LuckyEggFarmer : public SingleSwitchProgramInstance {
+public:
+    LuckyEggFarmer();
+    virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override;
+    virtual void start_program_border_check(
+        VideoStream& stream,
+        FeedbackType feedback_type
+    ) override {
+    }
+
+private:
+
+    bool navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context);
+    void swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context);
+    bool is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context);
+    bool find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context);
+    bool attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left);
+    bool check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context);
+
+    OCR::LanguageOCROption LANGUAGE;
+
+    DeferredStopButtonOption STOP_AFTER_CURRENT;
+    BooleanCheckBoxOption TAKE_VIDEO;
+    GoHomeWhenDoneOption GO_HOME_WHEN_DONE;
+
+    EventNotificationOption NOTIFICATION_SHINY;
+    EventNotificationOption NOTIFICATION_STATUS_UPDATE;
+    EventNotificationsOption NOTIFICATIONS;
+
+};
+
+}
+}
+}
+     
+
+
+
+#endif
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp
new file mode 100644
index 000000000..7e23883d6
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp
@@ -0,0 +1,115 @@
+/*  Battle Menu Navigation
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#include "cmath"
+#include "limits"
+#include "functional"
+#include "CommonTools/Async/InferenceRoutines.h"
+#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
+#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h"
+#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h"
+#include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h"
+#include "PokemonFRLG_BattleMenuNavigation.h"
+
+namespace PokemonAutomation{
+namespace NintendoSwitch{
+namespace PokemonFRLG{
+
+
+namespace{
+
+const ImageFloatBox BATTLE_MENU_ARROW_BOX(0.768, 0.868, 0.212, 0.159);
+
+const int BATTLE_MENU_COLS = 2;
+
+bool move_cursor_2d_impl(
+    ConsoleHandle& console,
+    ProControllerContext& context,
+    const ImageFloatBox& search_box,
+    int option_count,
+    int cols,
+    std::function<ImageFloatBox(int)> box_for_index,
+    int destination_index
+){
+    BattleSelectionArrowWatcher arrow(COLOR_RED, &console.overlay(), search_box);
+
+    int ret = wait_until(
+        console, context,
+        std::chrono::seconds(2),
+        { arrow }
+    );
+
+    if (ret < 0){
+        console.log("Unable to detect battle selection arrow. Not moving cursor.", COLOR_RED);
+        return false;
+    }
+
+    double detected_cx = arrow.last_detected().x + arrow.last_detected().width / 2.0;
+    double detected_cy = arrow.last_detected().y + arrow.last_detected().height / 2.0;
+
+    int current_index = 0;
+    double closest_dist = std::numeric_limits<double>::max();
+    for (int i = 0; i < option_count; i++){
+        ImageFloatBox pos_box = box_for_index(i);
+        double cx = pos_box.x + pos_box.width / 2.0;
+        double cy = pos_box.y + pos_box.height / 2.0;
+        double dx = detected_cx - cx;
+        double dy = detected_cy - cy;
+        double dist = dx * dx + dy * dy;
+        if (dist < closest_dist){
+            closest_dist = dist;
+            current_index = i;
+        }
+    }
+
+    int current_row = current_index / cols;
+    int current_col = current_index % cols;
+    int dest_row = destination_index / cols;
+    int dest_col = destination_index % cols;
+
+    int row_diff = dest_row - current_row;
+    for (int i = 0; i < std::abs(row_diff); i++){
+        pbf_press_dpad(context, row_diff > 0 ? DPAD_DOWN : DPAD_UP, 320ms, 400ms);
+    }
+
+    int col_diff = dest_col - current_col;
+    for (int i = 0; i < std::abs(col_diff); i++){
+        pbf_press_dpad(context, col_diff > 0 ? DPAD_RIGHT : DPAD_LEFT, 320ms, 400ms);
+    }
+
+    context.wait_for_all_requests();
+    return true;
+}
+
+} // anonymous namespace
+
+
+bool move_cursor_to_option(ConsoleHandle& console, ProControllerContext& context, BattleMenuOption destination){
+    return move_cursor_2d_impl(
+        console, context,
+        BATTLE_MENU_ARROW_BOX,
+        BATTLE_MENU_OPTION_COUNT,
+        BATTLE_MENU_COLS,
+        [](int i){ return BattleSelectionArrowDetector::box_for_option(static_cast<BattleMenuOption>(i)); },
+        static_cast<int>(destination)
+    );
+}
+
+bool move_cursor_to_option(ConsoleHandle& console, ProControllerContext& context, SafariBattleMenuOption destination){
+    return move_cursor_2d_impl(
+        console, context,
+        BATTLE_MENU_ARROW_BOX,
+        SAFARI_BATTLE_MENU_OPTION_COUNT,
+        BATTLE_MENU_COLS,
+        [](int i){ return BattleSelectionArrowDetector::box_for_option(static_cast<SafariBattleMenuOption>(i)); },
+        static_cast<int>(destination)
+    );
+}
+
+
+}
+}
+}
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h
new file mode 100644
index 000000000..4d74cd592
--- /dev/null
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h
@@ -0,0 +1,43 @@
+/*  Battle Menu Navigation
+ *
+ *  From: https://github.com/PokemonAutomation/
+ *
+ */
+
+#ifndef PokemonAutomation_PokemonFRLG_BattleMenuNavigation_H
+#define PokemonAutomation_PokemonFRLG_BattleMenuNavigation_H
+
+#include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h"
+
+namespace PokemonAutomation{
+
+template <typename Type> class ControllerContext;
+
+namespace NintendoSwitch{
+
+class ConsoleHandle;
+class ProController;
+using ProControllerContext = ControllerContext<ProController>;
+
+namespace PokemonFRLG{
+
+
+// Starting from the normal battle command menu, move the selection arrow to the specified option.
+// Returns true if successful, false otherwise (e.g. if selection arrow is not detected).
+bool move_cursor_to_option(
+    ConsoleHandle& console, ProControllerContext& context,
+    BattleMenuOption destination
+);
+
+// Starting from the Safari Zone battle command menu, move the selection arrow to the specified option.
+// Returns true if successful, false otherwise (e.g. if selection arrow is not detected).
+bool move_cursor_to_option(
+    ConsoleHandle& console, ProControllerContext& context,
+    SafariBattleMenuOption destination
+);
+
+
+}
+}
+}
+#endif
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp
index bf71dd272..adc934555 100644
--- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp
@@ -60,62 +60,24 @@ void close_start_menu(ConsoleHandle& console, ProControllerContext& context){
     console.log("Start menu closed.");
 }
 
-bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionStartMenu destination){
-    SelectionArrowWatcher pokedex_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::POKEDEX
-    );
-
-    SelectionArrowWatcher pokemon_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::POKEMON
-    );
-
-    SelectionArrowWatcher bag_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::BAG
-    );
-
-    SelectionArrowWatcher trainer_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::TRAINER
-    );
+namespace {
 
-    SelectionArrowWatcher save_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::SAVE
-    );
+const ImageFloatBox MENU_ARROW_BOX(0.727692, 0.0523077, 0.0369231, 0.6438461);
 
-    SelectionArrowWatcher option_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::OPTION
-    );
+bool move_cursor_impl(
+    ConsoleHandle& console,
+    ProControllerContext& context,
+    const ImageFloatBox& search_box,
+    int option_count,
+    std::function<ImageFloatBox(int)> box_for_index,
+    int destination_index
+){
+    SelectionArrowWatcher arrow(COLOR_RED, &console.overlay(), search_box);
 
-    SelectionArrowWatcher exit_arrow = SelectionArrowWatcher(
-        COLOR_RED,
-        &console.overlay(),
-        SelectionArrowPositionStartMenu::EXIT
-    );
-
-    // The order of these watchers needs to match the order of the SelectionArrowPositionStartMenu enum for the math below to work.
     int ret = wait_until(
         console, context,
         std::chrono::seconds(2),
-        {
-            pokedex_arrow,
-            pokemon_arrow,
-            bag_arrow,
-            trainer_arrow,
-            save_arrow,
-            option_arrow,
-            exit_arrow
-        }
+        { arrow }
     );
 
     if (ret < 0){
@@ -123,26 +85,58 @@ bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& conte
         return false;
     }
 
-    int destination_index = static_cast<int>(destination);
-    int forward = (destination_index - ret + START_MENU_OPTION_COUNT) % START_MENU_OPTION_COUNT;
-    int backward = (ret - destination_index + START_MENU_OPTION_COUNT) % START_MENU_OPTION_COUNT;
+    double detected_center_y = arrow.last_detected().y + arrow.last_detected().height / 2;
+    int current_index = 0;
+    double closest_dist = std::numeric_limits<double>::max();
+    for (int i = 0; i < option_count; i++){
+        ImageFloatBox pos_box = box_for_index(i);
+        double pos_center_y = pos_box.y + pos_box.height / 2;
+        double dist = std::abs(detected_center_y - pos_center_y);
+        if (dist < closest_dist){
+            closest_dist = dist;
+            current_index = i;
+        }
+    }
+
+    int forward = (destination_index - current_index + option_count) % option_count;
+    int backward = (current_index - destination_index + option_count) % option_count;
     if (forward <= backward){
         for (int i = 0; i < forward; i++){
             pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms);
         }
-        context.wait_for_all_requests();
     }
-    else {
-        for (int i = 0; i < backward; i++)
-        {
+    else{
+        for (int i = 0; i < backward; i++){
             pbf_press_dpad(context, DPAD_UP, 320ms, 400ms);
         }
-        context.wait_for_all_requests();
     }
+    context.wait_for_all_requests();
 
     return true;
 }
 
+} // anonymous namespace
+
+bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionStartMenu destination){
+    return move_cursor_impl(
+        console, context,
+        MENU_ARROW_BOX,
+        START_MENU_OPTION_COUNT,
+        [](int i){ return SelectionArrowDetector::arrow_box_for_position(static_cast<SelectionArrowPositionStartMenu>(i)); },
+        static_cast<int>(destination)
+    );
+}
+
+bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionSafariMenu destination){
+    return move_cursor_impl(
+        console, context,
+        MENU_ARROW_BOX,
+        SAFARI_START_MENU_OPTION_COUNT,
+        [](int i){ return SelectionArrowDetector::arrow_box_for_position(static_cast<SelectionArrowPositionSafariMenu>(i)); },
+        static_cast<int>(destination)
+    );
+}
+
 void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context){
 
     WallClock start = current_time();
diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h
index ba8f3560f..581763a3f 100644
--- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h
+++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h
@@ -37,6 +37,13 @@ bool move_cursor_to_position(
     SelectionArrowPositionStartMenu destination
 );
 
+// Starting from the Safari Zone start menu, move the selection arrow to the specified position.
+// Return true if successful, false otherwise (e.g. if selection arrow is not detected).
+bool move_cursor_to_position(
+    ConsoleHandle& console, ProControllerContext& context,
+    SelectionArrowPositionSafariMenu destination
+);
+
 //  Starting from either the overworld or the main menu, save the game.
 //  This function returns in the overworld.
 void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context);
diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake
index 2479d90f9..ca940e135 100644
--- a/SerialPrograms/cmake/SourceFiles.cmake
+++ b/SerialPrograms/cmake/SourceFiles.cmake
@@ -1454,10 +1454,14 @@ file(GLOB LIBRARY_SOURCES
     Source/PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h
     Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.cpp
     Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h
+    Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp
+    Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h
     Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp
     Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h
     Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp
     Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h
+    Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp
+    Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h
     Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp
     Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h
     Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp
@@ -1468,6 +1472,8 @@ file(GLOB LIBRARY_SOURCES
     Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h
     Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp
     Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h
+    Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp
+    Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h
     Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.cpp
     Source/PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h
     Source/PokemonFRLG/PokemonFRLG_Navigation.cpp
@@ -1476,10 +1482,14 @@ file(GLOB LIBRARY_SOURCES
     Source/PokemonFRLG/PokemonFRLG_Panels.h
     Source/PokemonFRLG/PokemonFRLG_Settings.cpp
     Source/PokemonFRLG/PokemonFRLG_Settings.h
+    Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp
+    Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h
     Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp
     Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h
     Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp
     Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.h
+    Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp
+    Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h
     Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp
     Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h
     Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp
