From cc08d4d9a917a939c288c2515ed1cee9da0740ed Mon Sep 17 00:00:00 2001
From: Gin <>
Date: Sun, 11 Jan 2026 13:53:32 -0800
Subject: [PATCH 1/4] apply character reduction before unicode normalization
---
.../Source/CommonTools/OCR/OCR_StringNormalization.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
index ea0244479..3250a85f1 100644
--- a/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
+++ b/SerialPrograms/Source/CommonTools/OCR/OCR_StringNormalization.cpp
@@ -19,6 +19,7 @@ namespace OCR{
std::u32string normalize_utf32(const std::string& text){
QString qstr = QString::fromStdString(text);
+ qstr = QString::fromStdU32String(run_character_reductions(qstr.toStdU32String()));
qstr = qstr.normalized(QString::NormalizationForm_KD);
std::u32string u32 = remove_non_alphanumeric(qstr.toStdU32String());
u32 = run_character_reductions(u32);
From 3f26219f95cdcde46a21e7325e6b2740d116057d Mon Sep 17 00:00:00 2001
From: Gin <>
Date: Sun, 11 Jan 2026 22:38:00 -0800
Subject: [PATCH 2/4] add Latias hunt
---
...kemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 163 ++++++++++++++++++
...PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 1 +
2 files changed, 164 insertions(+)
diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
index cbaf44663..ca115a814 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
@@ -78,6 +78,7 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary()
, LEGENDARY("Legendary " + STRING_POKEMON + ":",
{
{Legendary::LATIOS, "latios", "Latios"},
+ {Legendary::LATIAS, "latias", "Latias"},
{Legendary::COBALION, "cobalion", "Cobalion"},
{Legendary::TERRAKION, "terrakion", "Terrakion"},
{Legendary::VIRIZION, "virizion", "Virizion"},
@@ -138,6 +139,166 @@ void hunt_latios(
}
+void hunt_latias(
+ SingleSwitchProgramEnvironment& env,
+ ProControllerContext& context,
+ ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats,
+ SimpleIntegerOption& MIN_CALORIE_TO_CATCH)
+{
+ // Start at the ladder and use it to fix the position and camera angle
+ detect_warp_pad(env.console, context); // This should be renamed
+ pbf_press_button(context, BUTTON_A, 160ms, 1200ms);
+ pbf_move_left_joystick(context, {0, -0.5}, 80ms, 1200ms);
+
+ // Roll backwards to align against the roof edge
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll left on the overhang to get to the far corner
+ ssf_press_left_joystick(context, {-0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Slightly longer due to falling
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Roll to align against bulkhead
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll down the corrider next to the bulkhead
+ ssf_press_left_joystick(context, {0, -0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll towards the corner of the rooftop
+ ssf_press_left_joystick(context, {-0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Rolling at this angle puts you at the very edge of the roof, at the start of the reset path
+ ssf_press_left_joystick(context, {-0.20, +0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Falls down 2 levels
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+
+ // Adjust the camera angle to face the direction of the reset path
+ pbf_move_left_joystick(context, {+0.357, +0.5}, 500ms, 500ms);
+ pbf_press_button(context, BUTTON_L, 80ms, 160ms);
+
+ // Climb up from the overhang to the lower rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Start of actual reset cycle
+ while (true){
+ // Roll to the edge fo the lower rooftop
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb onto the upper rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Roll forward and align against the far chimney
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ // Lataias spawns here
+
+ // Roll back to the start
+ ssf_press_left_joystick(context, {0, -0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1200ms);
+ // Latias despawns here
+
+ // Turn back to around
+ ssf_press_left_joystick(context, {0, +0.5}, 0ms, 500ms, 0ms);
+
+ const uint16_t min_calorie = MIN_CALORIE_TO_CATCH + (15 + 30) * 10;
+ if (check_calorie(env.console, context, min_calorie)){
+ break;
+ }
+ }
+
+ // Roll and climb onto the upper rooftop
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_move_left_joystick(context, {0, +1}, 500ms, 1200ms);
+
+ // Roll to the right, next to the rooftop with the bulkhead
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb onto the rooftop with the bulkhead
+ pbf_move_left_joystick(context, {0, -1}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll right towards the next rooftop
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Adjust the camera angle back to the original angle
+ pbf_move_left_joystick(context, {-0.357, +0.5}, 80ms, 160ms);
+ pbf_press_button(context, BUTTON_L, 80ms, 160ms);
+
+ // Climb onto the next rooftop
+ ssf_press_left_joystick(context, {+0.5, 0}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll towards the Munna rooftop
+ pbf_move_left_joystick(context, {+1, 0}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Climb the Munna rooftop and roll forward
+ pbf_move_left_joystick(context, {+1, 0}, 500ms, 1200ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ // Roll back towards the ladder
+ ssf_press_left_joystick(context, {0, +0.5}, 0ms, 500ms, 0ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+ pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
+
+ context.wait_for_all_requests();
+
+ // Snap to the ladder by detecting the A button prompt
+ ButtonWatcher ButtonA(
+ COLOR_RED,
+ ButtonType::ButtonA,
+ {0.4, 0.1, 0.2, 0.8},
+ &env.console.overlay(),
+ Milliseconds(100)
+ );
+ const int ret = run_until(
+ env.console, context,
+ [&](ProControllerContext& context){
+ pbf_move_left_joystick(context, {+0.5, +1}, 5000ms, 0ms);
+ },
+ {{ButtonA}}
+ );
+ if (ret < 0){
+ OperationFailedException::fire(
+ ErrorReport::SEND_ERROR_REPORT,
+ "detect_warp_pad(): Cannot detect ladder after 5 seconds",
+ env.console
+ );
+ } else {
+ env.console.log("Detected ladder.");
+ }
+
+ // Run to Latias to trigger potential shiny sound
+ env.log("Move to check Latias.");
+ env.add_overlay_log("To Check Latias");
+ pbf_press_button(context, BUTTON_A, 160ms, 80ms);
+ ssf_press_left_joystick(context, {0, +1}, 0ms, 4000ms, 0ms);
+ pbf_mash_button(context, BUTTON_Y, 4000ms);
+ context.wait_for_all_requests();
+}
+
+
void hunt_cobalion(
SingleSwitchProgramEnvironment& env,
ProControllerContext& context,
@@ -460,6 +621,8 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env,
[&](ProControllerContext& context){
if (LEGENDARY == Legendary::LATIOS){
hunt_latios(env, context, stats, MIN_CALORIE_TO_CATCH);
+ } else if (LEGENDARY == Legendary::LATIAS){
+ hunt_latias(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){
diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
index 1b2db114c..39cfcde04 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h
@@ -36,6 +36,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{
enum class Legendary{
LATIOS,
+ LATIAS,
COBALION,
TERRAKION,
VIRIZION,
From 0765e26008d7204b9a8d10692a4f78ad1b1db712 Mon Sep 17 00:00:00 2001
From: Gin <>
Date: Sun, 11 Jan 2026 22:42:00 -0800
Subject: [PATCH 3/4] m
---
.../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
index ca115a814..45bfc9682 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
@@ -202,6 +202,10 @@ void hunt_latias(
pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
// Lataias spawns here
+ context.wait_for_all_requests();
+ stats.spawns++;
+ env.update_stats();
+
// Roll back to the start
ssf_press_left_joystick(context, {0, -0.5}, 0ms, 500ms, 0ms);
pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
@@ -263,6 +267,8 @@ void hunt_latias(
pbf_press_button(context, BUTTON_Y, 100ms, 1000ms);
context.wait_for_all_requests();
+ stats.spawns++;
+ env.update_stats();
// Snap to the ladder by detecting the A button prompt
ButtonWatcher ButtonA(
From 0912c62eeb20394c2e63535cca507fba057e2e9f Mon Sep 17 00:00:00 2001
From: Gin <>
Date: Sun, 11 Jan 2026 22:46:02 -0800
Subject: [PATCH 4/4] fix timing
---
.../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 45bfc9682..6b35f7252 100644
--- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
+++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp
@@ -217,7 +217,7 @@ void hunt_latias(
// Turn back to around
ssf_press_left_joystick(context, {0, +0.5}, 0ms, 500ms, 0ms);
- const uint16_t min_calorie = MIN_CALORIE_TO_CATCH + (15 + 30) * 10;
+ const uint16_t min_calorie = MIN_CALORIE_TO_CATCH + 55 * 10;
if (check_calorie(env.console, context, min_calorie)){
break;
}