From 198b2071495b7333422aff373a2a3e66b01f23af Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 25 Oct 2026 02:10:00 -0700 Subject: [PATCH 1/5] AutoStory: compute start/end story indices with one shared function, with tests Depends on the shared select database commit. The six SV index helpers and the two descriptions repeated the same tutorial/main-story offset maths; story_index() does it once and is tested with SV's offsets. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../AutoStory/Pokemon_AutoStorySelect.h | 7 ++ .../VideoGames/PokemonSV/PokemonSV_Tests.cpp | 99 +++++++++++++++++++ .../AutoStory/PokemonSV_AutoStory.cpp | 74 +++++--------- 3 files changed, 132 insertions(+), 48 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h index 80b4f94338..01318b85ce 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h @@ -32,6 +32,13 @@ StringSelectDatabase make_story_select_database( } +// The index in the whole story list of entry `index_in_section` of the +// tutorial or the main story, whose first entry is `first_main_story_index`. +inline size_t story_index(bool main_story, size_t index_in_section, size_t first_main_story_index){ + return main_story ? first_main_story_index + index_in_section : index_in_section; +} + + } } #endif diff --git a/SerialPrograms/Source/VideoGames/PokemonSV/PokemonSV_Tests.cpp b/SerialPrograms/Source/VideoGames/PokemonSV/PokemonSV_Tests.cpp index d966bfe133..de82dd3b2d 100644 --- a/SerialPrograms/Source/VideoGames/PokemonSV/PokemonSV_Tests.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonSV/PokemonSV_Tests.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Pokemon/AutoStory/Pokemon_AutoStorySelect.h" #include "Inference/PokemonSV_WhiteButtonDetector.h" #include "Inference/Boxes/PokemonSV_BoxDetection.h" #include "Inference/Boxes/PokemonSV_BoxEggDetector.h" @@ -33,7 +34,105 @@ namespace PokemonSV{ +namespace{ + +// An entry of a story section, its index in the whole story list, and the +// index of the first main story entry. +struct AutoStoryIndexScenario{ + std::string name; + bool main_story; + size_t index_in_section; + size_t first_main_story_index; + size_t expected; +}; + +class Test_AutoStoryIndex : public UnitTest{ +public: + Test_AutoStoryIndex(AutoStoryIndexScenario scenario) + : UnitTest("Pokemon::AutoStoryIndex - " + scenario.name) + , m_scenario(std::move(scenario)) + {} + + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const size_t got = Pokemon::story_index( + m_scenario.main_story, m_scenario.index_in_section, m_scenario.first_main_story_index + ); + if (got != m_scenario.expected){ + return "Expected index " + std::to_string(m_scenario.expected) + ", got " + std::to_string(got); + } + return true; + } + +private: + const AutoStoryIndexScenario m_scenario; +}; + +// A stand-in for a story segment or checkpoint: only its name is read. +struct NamedEntry{ + std::string label; + std::string name() const{ return label; } +}; + +class Test_AutoStorySelectDatabase : public UnitTest{ +public: + Test_AutoStorySelectDatabase() + : UnitTest("Pokemon::AutoStorySelectDatabase - a section keeps whole-list slugs") + {} + + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + std::vector> list; + for (const char* label : {"a", "b", "c", "d"}){ + list.emplace_back(std::make_unique(NamedEntry{label})); + } + const StringSelectDatabase database = Pokemon::make_story_select_database(list, 2, 10); + const std::vector& entries = database.case_list(); + if (entries.size() != 2){ + return "Expected 2 entries (the end is clamped to the list), got " + std::to_string(entries.size()); + } + if (entries[0].slug != "2" || entries[0].display_name != "c" || entries[1].slug != "3" || entries[1].display_name != "d"){ + return "Expected slugs 2, 3 labeled c, d; got " + entries[0].slug + " " + entries[0].display_name + + ", " + entries[1].slug + " " + entries[1].display_name; + } + return true; + } +}; + +void add_tests_AutoStory(UnitTestDatabase& database){ + const AutoStoryIndexScenario scenarios[] = { + { + .name = "a tutorial entry is its own index", + .main_story = false, + .index_in_section = 3, + .first_main_story_index = 10, + .expected = 3, + }, + { + // SV: segment 10 is the first of the main story. + .name = "the first main story entry follows the tutorial", + .main_story = true, + .index_in_section = 0, + .first_main_story_index = 10, + .expected = 10, + }, + { + // SV: checkpoint 21 is the first of the main story. + .name = "a later main story checkpoint", + .main_story = true, + .index_in_section = 5, + .first_main_story_index = 21, + .expected = 26, + }, + }; + for (const AutoStoryIndexScenario& scenario : scenarios){ + database.add(scenario); + } + database.add(); +} + +} + void add_tests(UnitTestDatabase& database){ + add_tests_AutoStory(database); add_tests_WhiteButtonDetector(database); add_tests_BoxInfoDetector(database); add_tests_BoxEggDetector(database); diff --git a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp index aec8b25ff5..067b6422b9 100644 --- a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp @@ -1047,71 +1047,49 @@ std::string AutoStory::end_segment_description(){ } size_t AutoStory::get_start_segment_index(){ - size_t start = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - start = STARTPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - start = (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1) + STARTPOINT_MAINSTORY.index(); - } - - return start; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? STARTPOINT_MAINSTORY.index() : STARTPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_SEGMENT + 1 + ); } size_t AutoStory::get_end_segment_index(){ - size_t end = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - end = ENDPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - end = (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1) + ENDPOINT_MAINSTORY.index(); - } - - return end; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? ENDPOINT_MAINSTORY.index() : ENDPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_SEGMENT + 1 + ); } std::string AutoStory::start_checkpoint_description(){ - size_t checkpoint_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - checkpoint_index = START_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - checkpoint_index = START_CHECKPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1); - } + const size_t checkpoint_index = get_start_checkpoint_index(); return " Start: " + ALL_AUTO_STORY_CHECKPOINT_LIST()[checkpoint_index]->start_text(); } std::string AutoStory::end_checkpoint_description(){ - size_t checkpoint_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - checkpoint_index = END_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - checkpoint_index = END_CHECKPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1); - } + const size_t checkpoint_index = get_end_checkpoint_index(); return " End: " + ALL_AUTO_STORY_CHECKPOINT_LIST()[checkpoint_index]->end_text(); } size_t AutoStory::get_start_checkpoint_index(){ - size_t start = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - start = START_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - start = (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1) + START_CHECKPOINT_MAINSTORY.index(); - } - - return start; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? START_CHECKPOINT_MAINSTORY.index() : START_CHECKPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1 + ); } size_t AutoStory::get_end_checkpoint_index(){ - size_t end = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - end = END_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - end = (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1) + END_CHECKPOINT_MAINSTORY.index(); - } - - return end; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? END_CHECKPOINT_MAINSTORY.index() : END_CHECKPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1 + ); } From 1ea884ba71acfc68d215f4e45c4a632aa37290d4 Mon Sep 17 00:00:00 2001 From: dayfine Date: Sun, 1 Nov 2026 23:05:35 -0800 Subject: [PATCH 2/5] AutoStory: shared start/end select database and story index SV AutoStory built six near-identical start/end dropdown databases by hand and repeated the tutorial/main-story index offset maths in six helpers and two descriptions. make_story_select_database() and story_index() in Pokemon/AutoStory/Pokemon_AutoStorySelect.h do each once, for other games' AutoStory to reuse, with their tests beside them. No behaviour change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../ComputerPrograms_UnitTestRunner.cpp | 2 + .../AutoStory/Pokemon_AutoStorySelect.h | 44 +++++ .../Pokemon_AutoStorySelect_Tests.cpp | 173 ++++++++++++++++++ .../AutoStory/Pokemon_AutoStorySelect_Tests.h | 22 +++ .../AutoStory/PokemonSV_AutoStory.cpp | 165 +++++------------ SerialPrograms/cmake/SourceFiles.cmake | 3 + 6 files changed, 289 insertions(+), 120 deletions(-) create mode 100644 SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h create mode 100644 SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.cpp create mode 100644 SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index e34d1cbba1..71a0872070 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -13,6 +13,7 @@ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "Common/Qt/QtThreadPool_Tests.h" #include "Kernels/Kernels_Tests.h" +#include "Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h" #include "CommonFramework/AudioPipeline/AudioInfo_Tests.h" #include "CommonFramework/AudioPipeline/Tools/ChannelMixer.h" #include "CommonFramework/AudioPipeline/SinkPipeline_Tests.h" @@ -62,6 +63,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ add_tests_AudioFileLoader(ret); OCR::add_tests(ret); Kernels::add_tests(ret); + Pokemon::add_tests_AutoStorySelect(ret); NintendoSwitch::add_tests_CheckOnlineDetector(ret); NintendoSwitch::add_tests_FailedToConnectDetector(ret); NintendoSwitch::add_tests_UpdatePopupDetector(ret); diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h new file mode 100644 index 0000000000..01318b85ce --- /dev/null +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h @@ -0,0 +1,44 @@ +/* AutoStory Select + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Pokemon_AutoStorySelect_H +#define PokemonAutomation_Pokemon_AutoStorySelect_H + +#include +#include +#include +#include "CommonTools/Options/StringSelectOption.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + + +// A dropdown database of the story segments or checkpoints [begin, end) of +// `list`, labeled by their name() and slugged by their index in the whole +// list, so a slug picks the same entry whichever section it was chosen from. +template +StringSelectDatabase make_story_select_database( + const std::vector>& list, + size_t begin, size_t end +){ + StringSelectDatabase ret; + for (size_t i = begin; i < end && i < list.size(); i++){ + ret.add_entry(StringSelectEntry(std::to_string(i), list[i]->name())); + } + return ret; +} + + +// The index in the whole story list of entry `index_in_section` of the +// tutorial or the main story, whose first entry is `first_main_story_index`. +inline size_t story_index(bool main_story, size_t index_in_section, size_t first_main_story_index){ + return main_story ? first_main_story_index + index_in_section : index_in_section; +} + + +} +} +#endif diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.cpp b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.cpp new file mode 100644 index 0000000000..efdd49bd8f --- /dev/null +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.cpp @@ -0,0 +1,173 @@ +/* AutoStory Select Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include "Pokemon_AutoStorySelect.h" +#include "Pokemon_AutoStorySelect_Tests.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +namespace{ + +// An entry of a story section, its index in the whole story list, and the +// index of the first main story entry. +struct AutoStoryIndexScenario{ + std::string name; + bool main_story; + size_t index_in_section; + size_t first_main_story_index; + size_t expected; +}; + +class Test_AutoStoryIndex : public UnitTest{ +public: + Test_AutoStoryIndex(AutoStoryIndexScenario scenario) + : UnitTest("Pokemon::AutoStoryIndex - " + scenario.name) + , m_scenario(std::move(scenario)) + {} + + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + const size_t got = story_index( + m_scenario.main_story, m_scenario.index_in_section, m_scenario.first_main_story_index + ); + if (got != m_scenario.expected){ + return "Expected index " + std::to_string(m_scenario.expected) + ", got " + std::to_string(got); + } + return true; + } + +private: + const AutoStoryIndexScenario m_scenario; +}; + +// A stand-in for a story segment or checkpoint: only its name is read. +struct NamedEntry{ + std::string label; + std::string name() const{ return label; } +}; + +// A dropdown built from [begin, end) of a story list of two tutorial entries, +// then two of the main story (which starts at index 2), as SV's lists are +// laid out, and its (slug, label) entries: slugs are indices in the whole +// list, not in the dropdown. +struct AutoStorySelectScenario{ + std::string name; + size_t begin; + size_t end; + std::vector> expected; +}; + +class Test_AutoStorySelectDatabase : public UnitTest{ +public: + Test_AutoStorySelectDatabase(AutoStorySelectScenario scenario) + : UnitTest("Pokemon::AutoStorySelectDatabase - " + scenario.name) + , m_scenario(std::move(scenario)) + {} + + virtual UnitTestResult run(Logger&, CancellableScope&) const override{ + // unique_ptr cannot be copied out of a braced list: build it in place. + const std::vector> list = []{ + std::vector> list; + for (const char* label : {"tutorial 0", "tutorial 1", "main story 0", "main story 1"}){ + list.emplace_back(std::make_unique(NamedEntry{label})); + } + return list; + }(); + const StringSelectDatabase database = make_story_select_database(list, m_scenario.begin, m_scenario.end); + std::vector> got; + for (const StringSelectEntry& entry : database.case_list()){ + got.emplace_back(entry.slug, entry.display_name); + } + if (got != m_scenario.expected){ + return "Expected " + describe(m_scenario.expected) + ", got " + describe(got); + } + return true; + } + +private: + static std::string describe(const std::vector>& entries){ + std::string text; + for (const auto& [slug, label] : entries){ + text += (text.empty() ? "" : ", ") + slug + " \"" + label + "\""; + } + return "[" + text + "]"; + } + + const AutoStorySelectScenario m_scenario; +}; + +void add_index_tests(UnitTestDatabase& database){ + const AutoStoryIndexScenario scenarios[] = { + { + .name = "a tutorial entry is its own index", + .main_story = false, + .index_in_section = 3, + .first_main_story_index = 10, + .expected = 3, + }, + { + // SV: segment 10 is the first of the main story. + .name = "the first main story entry follows the tutorial", + .main_story = true, + .index_in_section = 0, + .first_main_story_index = 10, + .expected = 10, + }, + { + // SV: checkpoint 21 is the first of the main story. + .name = "a later main story checkpoint", + .main_story = true, + .index_in_section = 5, + .first_main_story_index = 21, + .expected = 26, + }, + }; + for (const AutoStoryIndexScenario& scenario : scenarios){ + database.add(scenario); + } +} + +void add_select_database_tests(UnitTestDatabase& database){ + const AutoStorySelectScenario select_scenarios[] = { + { + .name = "the tutorial dropdown", + .begin = 0, + .end = 2, + .expected = {{"0", "tutorial 0"}, {"1", "tutorial 1"}}, + }, + { + .name = "the main story dropdown keeps whole-list slugs", + .begin = 2, + .end = 4, + .expected = {{"2", "main story 0"}, {"3", "main story 1"}}, + }, + { + .name = "an end past the list is clamped to it", + .begin = 2, + .end = 10, + .expected = {{"2", "main story 0"}, {"3", "main story 1"}}, + }, + }; + for (const AutoStorySelectScenario& scenario : select_scenarios){ + database.add(scenario); + } +} + +} + +void add_tests_AutoStorySelect(UnitTestDatabase& database){ + add_index_tests(database); + add_select_database_tests(database); +} + + + +} +} diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h new file mode 100644 index 0000000000..5132ce6335 --- /dev/null +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h @@ -0,0 +1,22 @@ +/* AutoStory Select Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Pokemon_AutoStorySelect_Tests_H +#define PokemonAutomation_Pokemon_AutoStorySelect_Tests_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + + +void add_tests_AutoStorySelect(UnitTestDatabase& database); + + + +} +} +#endif diff --git a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp index 8167660fcb..9f943f5833 100644 --- a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp @@ -27,6 +27,7 @@ #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/AutoStory/Pokemon_AutoStorySelect.h" #include "VideoGames/PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" #include "VideoGames/PokemonSV/PokemonSV_Settings.h" #include "VideoGames/PokemonSV/Inference/Overworld/PokemonSV_DirectionDetector.h" @@ -150,51 +151,24 @@ const std::vector>& ALL_AUTO_STORY_SEGMENT_LI } -StringSelectDatabase make_all_segments_database(){ - StringSelectDatabase ret; - int index_num = 0; - for (const auto& segment : ALL_AUTO_STORY_SEGMENT_LIST()){ - ret.add_entry(StringSelectEntry(std::to_string(index_num), segment->name())); - index_num++; - } - return ret; -} const StringSelectDatabase& ALL_SEGMENTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_all_segments_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_SEGMENT_LIST(), 0, ALL_AUTO_STORY_SEGMENT_LIST().size() + ); return database; } -StringSelectDatabase make_tutorial_segments_database(){ - StringSelectDatabase ret; - const StringSelectDatabase& all_segments = ALL_SEGMENTS_SELECT_DATABASE(); - size_t start = 0; - size_t end = INDEX_OF_LAST_TUTORIAL_SEGMENT + 1; - for (size_t i = start; i < end; i++){ - const auto& segment = all_segments[i]; - ret.add_entry(segment); - } - return ret; -} - const StringSelectDatabase& TUTORIAL_SEGMENTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_tutorial_segments_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_SEGMENT_LIST(), 0, INDEX_OF_LAST_TUTORIAL_SEGMENT + 1 + ); return database; } -StringSelectDatabase make_mainstory_segments_database(){ - StringSelectDatabase ret; - const StringSelectDatabase& all_segments = ALL_SEGMENTS_SELECT_DATABASE(); - size_t start = INDEX_OF_LAST_TUTORIAL_SEGMENT + 1; - size_t end = all_segments.case_list().size(); - for (size_t i = start; i < end; i++){ - const auto& segment = all_segments[i]; - ret.add_entry(segment); - } - return ret; -} - const StringSelectDatabase& MAINSTORY_SEGMENTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_mainstory_segments_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_SEGMENT_LIST(), INDEX_OF_LAST_TUTORIAL_SEGMENT + 1, ALL_AUTO_STORY_SEGMENT_LIST().size() + ); return database; } @@ -324,51 +298,24 @@ const std::vector>& ALL_AUTO_STORY_CHECKPO return checkpoint_list; } -StringSelectDatabase make_all_checkpoints_database(){ - StringSelectDatabase ret; - int index_num = 0; - for (const auto& segment : ALL_AUTO_STORY_CHECKPOINT_LIST()){ - ret.add_entry(StringSelectEntry(std::to_string(index_num), segment->name())); - index_num++; - } - return ret; -} const StringSelectDatabase& ALL_CHECKPOINTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_all_checkpoints_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_CHECKPOINT_LIST(), 0, ALL_AUTO_STORY_CHECKPOINT_LIST().size() + ); return database; } -StringSelectDatabase make_tutorial_checkpoints_database(){ - StringSelectDatabase ret; - const StringSelectDatabase& all_checkpoints = ALL_CHECKPOINTS_SELECT_DATABASE(); - size_t start = 0; - size_t end = INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1; - for (size_t i = start; i < end; i++){ - const auto& segment = all_checkpoints[i]; - ret.add_entry(segment); - } - return ret; -} - const StringSelectDatabase& TUTORIAL_CHECKPOINTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_tutorial_checkpoints_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_CHECKPOINT_LIST(), 0, INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1 + ); return database; } -StringSelectDatabase make_mainstory_checkpoints_database(){ - StringSelectDatabase ret; - const StringSelectDatabase& all_checkpoints = ALL_CHECKPOINTS_SELECT_DATABASE(); - size_t start = INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1; - size_t end = all_checkpoints.case_list().size(); - for (size_t i = start; i < end; i++){ - const auto& segment = all_checkpoints[i]; - ret.add_entry(segment); - } - return ret; -} - const StringSelectDatabase& MAINSTORY_CHECKPOINTS_SELECT_DATABASE(){ - static StringSelectDatabase database = make_mainstory_checkpoints_database(); + static StringSelectDatabase database = make_story_select_database( + ALL_AUTO_STORY_CHECKPOINT_LIST(), INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1, ALL_AUTO_STORY_CHECKPOINT_LIST().size() + ); return database; } @@ -1101,71 +1048,49 @@ std::string AutoStory::end_segment_description(){ } size_t AutoStory::get_start_segment_index(){ - size_t start = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - start = STARTPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - start = (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1) + STARTPOINT_MAINSTORY.index(); - } - - return start; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? STARTPOINT_MAINSTORY.index() : STARTPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_SEGMENT + 1 + ); } size_t AutoStory::get_end_segment_index(){ - size_t end = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - end = ENDPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - end = (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1) + ENDPOINT_MAINSTORY.index(); - } - - return end; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? ENDPOINT_MAINSTORY.index() : ENDPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_SEGMENT + 1 + ); } std::string AutoStory::start_checkpoint_description(){ - size_t checkpoint_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - checkpoint_index = START_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - checkpoint_index = START_CHECKPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1); - } + const size_t checkpoint_index = get_start_checkpoint_index(); return " Start: " + ALL_AUTO_STORY_CHECKPOINT_LIST()[checkpoint_index]->start_text(); } std::string AutoStory::end_checkpoint_description(){ - size_t checkpoint_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - checkpoint_index = END_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - checkpoint_index = END_CHECKPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1); - } + const size_t checkpoint_index = get_end_checkpoint_index(); return " End: " + ALL_AUTO_STORY_CHECKPOINT_LIST()[checkpoint_index]->end_text(); } size_t AutoStory::get_start_checkpoint_index(){ - size_t start = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - start = START_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - start = (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1) + START_CHECKPOINT_MAINSTORY.index(); - } - - return start; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? START_CHECKPOINT_MAINSTORY.index() : START_CHECKPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1 + ); } size_t AutoStory::get_end_checkpoint_index(){ - size_t end = 0; - - if (STORY_SECTION == StorySection::TUTORIAL){ - end = END_CHECKPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - end = (INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1) + END_CHECKPOINT_MAINSTORY.index(); - } - - return end; + const bool main_story = STORY_SECTION == StorySection::MAIN_STORY; + return story_index( + main_story, + main_story ? END_CHECKPOINT_MAINSTORY.index() : END_CHECKPOINT_TUTORIAL.index(), + INDEX_OF_LAST_TUTORIAL_CHECKPOINT + 1 + ); } diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index b64c8b92f0..0c79904641 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1465,6 +1465,9 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Options/Pokemon_NameSelectWidget.h Source/Pokemon/Options/Pokemon_StatsHuntFilter.cpp Source/Pokemon/Options/Pokemon_StatsHuntFilter.h + Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h + Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.cpp + Source/Pokemon/AutoStory/Pokemon_AutoStorySelect_Tests.h Source/Pokemon/Pokemon_AdvRng.cpp Source/Pokemon/Pokemon_AdvRng.h Source/Pokemon/Pokemon_BdspRng.cpp From 2b03556251d699fb7f0f9ff85ac6ee28e031b147 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 11:00:49 -0800 Subject: [PATCH 3/5] AutoStory: segment descriptions use the shared index getters start_segment_description() and end_segment_description() computed the tutorial/main-story segment offset inline. They now call get_start_segment_index() / get_end_segment_index(), like the checkpoint descriptions, which removes the last two inline copies of that offset (pa-devbot review on PokemonAutomation/Arduino-Source#1828). No behavior change: both getters return the same index for tutorial and main story. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Programs/AutoStory/PokemonSV_AutoStory.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp index 9f943f5833..130271139b 100644 --- a/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp +++ b/SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp @@ -1028,23 +1028,11 @@ void AutoStory::test_checkpoints( } std::string AutoStory::start_segment_description(){ - size_t segment_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - segment_index = STARTPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - segment_index = STARTPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1); - } - return " " + ALL_AUTO_STORY_SEGMENT_LIST()[segment_index]->start_text(); + return " " + ALL_AUTO_STORY_SEGMENT_LIST()[get_start_segment_index()]->start_text(); } std::string AutoStory::end_segment_description(){ - size_t segment_index = 0; - if (STORY_SECTION == StorySection::TUTORIAL){ - segment_index = ENDPOINT_TUTORIAL.index(); - }else if (STORY_SECTION == StorySection::MAIN_STORY){ - segment_index = ENDPOINT_MAINSTORY.index() + (INDEX_OF_LAST_TUTORIAL_SEGMENT + 1); - } - return " " + ALL_AUTO_STORY_SEGMENT_LIST()[segment_index]->end_text(); + return " " + ALL_AUTO_STORY_SEGMENT_LIST()[get_end_segment_index()]->end_text(); } size_t AutoStory::get_start_segment_index(){ From 057306ff58dfa0c11f88f6ec676356b6fb338739 Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 11:39:20 -0800 Subject: [PATCH 4/5] AutoStory: note story_index's two-section assumption story_index() maps a tutorial or main-story position to the flat story list. Note that this assumes two consecutive sections, which is all SV AutoStory covers, and that a branching postgame or DLC needs another model. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h index 01318b85ce..44657e26c3 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h @@ -34,6 +34,9 @@ StringSelectDatabase make_story_select_database( // The index in the whole story list of entry `index_in_section` of the // tutorial or the main story, whose first entry is `first_main_story_index`. +// NOTE: assumes two consecutive sections (tutorial, then the main story), +// which is all SV AutoStory covers. A postgame or DLC that branches off +// the main story will need a different model than one global index. inline size_t story_index(bool main_story, size_t index_in_section, size_t first_main_story_index){ return main_story ? first_main_story_index + index_in_section : index_in_section; } From 101f5744f9a59d9ee335ec9d40f52fdada70be1a Mon Sep 17 00:00:00 2001 From: dayfine Date: Mon, 2 Nov 2026 12:00:05 -0800 Subject: [PATCH 5/5] AutoStory: describe sections as break points in story_index's NOTE Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h index 44657e26c3..84276b0b86 100644 --- a/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h +++ b/SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStorySelect.h @@ -34,9 +34,11 @@ StringSelectDatabase make_story_select_database( // The index in the whole story list of entry `index_in_section` of the // tutorial or the main story, whose first entry is `first_main_story_index`. -// NOTE: assumes two consecutive sections (tutorial, then the main story), -// which is all SV AutoStory covers. A postgame or DLC that branches off -// the main story will need a different model than one global index. +// NOTE: a section ends where the user must step in (e.g. level up the +// Pokemon between the tutorial and the main story). SV has two sections. +// Later content (postgame, DLC) appends to the last section, run only after +// the main story; if it needs its own break (e.g. grinding before a DLC), +// it becomes a third section and `main_story` here becomes a section index. inline size_t story_index(bool main_story, size_t index_in_section, size_t first_main_story_index){ return main_story ? first_main_story_index + index_in_section : index_in_section; }