Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
823f531
Common: add MoveOnlyFunction, a move-only callable wrapper
dayfine Nov 1, 2026
3209523
Pokemon navigation: grid types and leg planner over a game's step model
dayfine Nov 1, 2026
c246d3d
ScreenMachine: a closed loop over known screens
dayfine Nov 1, 2026
f3a8943
Pokemon navigation: run story checkpoints as events
dayfine Nov 1, 2026
19a0303
Pokemon navigation: story events take a game-defined position
dayfine Nov 1, 2026
0839f86
Merge base/upstream-main (b3673f2a9) into nav/shared
dayfine Nov 1, 2026
6549f98
Pokemon navigation: a segment is a table of event checkpoints
dayfine Nov 1, 2026
d94af62
Pokemon navigation: one JSON map schema and loader for both games
dayfine Nov 1, 2026
5e31505
Pokemon navigation: name the map source's JSON path for GCC
dayfine Nov 1, 2026
7008f8c
Pokemon navigation: weigh open stops after a wild step
dayfine Nov 1, 2026
b426f74
CMake: build data files into the program as text (pa_embed_text_files)
dayfine Nov 1, 2026
c7a2136
Pokemon navigation: the route log lines, shared by the games' walkers
dayfine Nov 1, 2026
716bb5a
Pokemon navigation: route log tests as small tables, for the function…
dayfine Nov 1, 2026
a38e457
Pokemon navigation: the leg walker, with its stop hooks
dayfine Nov 1, 2026
4991c08
Merge autostory/r3-shared-stats (571831682, #45) into nav/shared: the…
dayfine Nov 1, 2026
6c46f7c
Merge autostory/r3-shared-stats (7cc66e3fc: #43 carries its select-da…
dayfine Nov 1, 2026
84093b0
Pokemon AutoStory: the story engine moves out of Pokemon/Navigation
dayfine Nov 1, 2026
a6a0920
Pokemon navigation: the class shapes code health asks for
dayfine Nov 1, 2026
af1b526
Pokemon AutoStory: story lines on the overlay; an event that throws i…
dayfine Nov 1, 2026
b0ebe0b
Pokemon MapData: the fields BDSP and FRLG maps need
dayfine Nov 2, 2026
3b96255
Merge origin/autostory/r3-shared-stats into nav/shared
dayfine Nov 2, 2026
7cdb95b
Merge autostory/r3-shared-stats into nav/shared
dayfine Nov 2, 2026
16e8456
Pokemon MapData: allow_running, for maps where the player can't run
dayfine Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions SerialPrograms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ include(cmake/CoreLib.cmake)
# Create the GUI library
add_library(SerialProgramsLib STATIC ${CORE_LIBRARY_SOURCES} ${LIBRARY_SOURCES})

# Data files built into the program (see cmake/EmbedTextFiles.cmake).
include(cmake/EmbedTextFiles.cmake)
pa_embed_text_files(SerialProgramsLib Source/Pokemon/Navigation/TestMaps
Pokemon/Navigation/Pokemon_Navigation_Tests.h PokemonAutomation::Pokemon navigation_test_map_json)


# Create binary target
if (APPLE)
Expand Down
97 changes: 97 additions & 0 deletions SerialPrograms/Source/CommonTools/ScreenMachine/ScreenMachine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* Screen Machine
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include "CommonFramework/ImageTools/ImageDiff.h"
#include "ScreenMachine.h"

namespace PokemonAutomation{

using namespace std::chrono_literals;


namespace{

const ScreenState* classify(const std::vector<ScreenState>& states, const ImageViewRGB32& screen){
for (const ScreenState& state : states){
if (state.matches(screen)){
return &state;
}
}
return nullptr;
}

}


const ScreenState& read_screen_state(

Check warning on line 30 in SerialPrograms/Source/CommonTools/ScreenMachine/ScreenMachine.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::read_screen_state( VideoStream & stream , CancellableScope & scope , StepTrace & trace , const std :: vector<ScreenState> & states , VideoSnapshot & frame): function_lines=33 (advisory above 25, limit 50)
VideoStream& stream, CancellableScope& scope, StepTrace& trace,
const std::vector<ScreenState>& states, VideoSnapshot& frame
){
const ScreenState* last = nullptr;
WallClock video_lost = WallClock::max();
for (size_t c = 0; c < 20; c++){
VideoSnapshot screen = stream.video().snapshot();
if (!screen){
// The app resets a frozen capture by itself, which takes a second or two.
const WallClock now = current_time();
video_lost = std::min(video_lost, now);
if (now - video_lost > std::chrono::seconds(10)){
trace.fail("No video from the capture card for 10s.");
}
c--;
scope.wait_for(500ms);
continue;
}
video_lost = WallClock::max();
const ScreenState* now = classify(states, screen);
frame = std::move(screen);
if (now != nullptr && now == last){
return *now;
}
last = now;
scope.wait_for(150ms);
}
if (last == nullptr){
trace.fail("The screen matches none of the known states.");
}
return *last; // Still changing after 3s: go with the latest read.
}


void ScreenMachineChecks::check(const ScreenState& state, VideoSnapshot frame){

Check warning on line 65 in SerialPrograms/Source/CommonTools/ScreenMachine/ScreenMachine.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::ScreenMachineChecks::check( const ScreenState & state , VideoSnapshot frame): function_lines=29 (advisory above 25, limit 50)
const bool same_size = m_last_frame && frame &&
m_last_frame->width() == frame->width() && m_last_frame->height() == frame->height();
if (same_size && ImageMatch::pixel_RMSD(m_last_frame, frame) < 2.0){
if (++m_unchanged >= MAX_UNCHANGED){
m_trace.fail("The screen stopped changing: " + std::to_string(MAX_UNCHANGED) +
" steps on \"" + state.name + "\" did nothing.");
}
}else{
m_unchanged = 0;
}
m_last_frame = std::move(frame);

if (m_previous != nullptr && m_previous != &state && !m_previous->next.empty() &&
std::find(m_previous->next.begin(), m_previous->next.end(), state.name) == m_previous->next.end()
){
m_trace.fail("Unexpected screen after \"" + m_previous->name + "\": \"" + state.name + "\".");
}
m_in_a_row = m_previous == &state ? m_in_a_row + 1 : 1;
if (m_in_a_row > state.max_in_a_row){
m_trace.fail("Screen \"" + state.name + "\" came up " + std::to_string(m_in_a_row) +
" times in a row: the step for it is not getting anywhere.");
}
if (++m_visits[state.name] > state.max_visits){
m_trace.fail("Screen \"" + state.name + "\" came up more than " +
std::to_string(state.max_visits) + " times: the step for it is not working.");
}
m_previous = &state;
}



}
105 changes: 105 additions & 0 deletions SerialPrograms/Source/CommonTools/ScreenMachine/ScreenMachine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* Screen Machine
*
* From: https://github.com/PokemonAutomation/
*
* A closed loop over known screens: read the screen, take the one step that screen
* calls for, read again. Every step follows a read, a screen counts only once two
* frames agree, and each state lists the screens it may lead to, so a dropped or
* wrong input shows up as an unexpected screen or a state that keeps coming back.
*
*/

#ifndef PokemonAutomation_CommonTools_ScreenMachine_H
#define PokemonAutomation_CommonTools_ScreenMachine_H

#include <stdint.h>
#include <functional>
#include <map>
#include <string>
#include <vector>
#include "Common/Cpp/Time.h"
#include "Common/Cpp/CancellableScope.h"
#include "CommonFramework/ImageTypes/ImageViewRGB32.h"
#include "CommonFramework/Tools/VideoStream.h"
#include "CommonFramework/VideoPipeline/VideoFeed.h"
#include "StepTrace.h"

namespace PokemonAutomation{


struct ScreenState{
std::string name;
// Whether this is the screen. States are tried in order; the first match wins, so
// the last state should match anything.
std::function<bool(const ImageViewRGB32& screen)> matches;
// The one step to take on this screen.
std::function<void()> act;
// The screens the step may lead to besides staying here. Empty: any.
std::vector<std::string> next;
// Fail once the screen has been seen this many times.
size_t max_visits;
// End the run after this state's step.
bool terminal = false;
// Fail once the screen has been seen this many times in a row.
size_t max_in_a_row = SIZE_MAX;
};


// The state on screen, once two frames 150ms apart agree. Waits out a video
// gap of up to 10s. Fails through `trace` if no state matches.
const ScreenState& read_screen_state(
VideoStream& stream, CancellableScope& scope, StepTrace& trace,
const std::vector<ScreenState>& states, VideoSnapshot& frame
);

// The checks between reads: the screen must be one the previous state can lead
// to, no state may come up too often, and steps must keep changing the screen.
class ScreenMachineChecks{
public:
ScreenMachineChecks(StepTrace& trace)
: m_trace(trace)
{}
void check(const ScreenState& state, VideoSnapshot frame);

private:
// A step that changes nothing on screen is a dead end (e.g. a menu the states do
// not know): fail after a few instead of running out the visit limit.
static constexpr size_t MAX_UNCHANGED = 8;

StepTrace& m_trace;
std::map<std::string, size_t> m_visits;
const ScreenState* m_previous = nullptr;
VideoSnapshot m_last_frame;
size_t m_unchanged = 0;
size_t m_in_a_row = 0;
};


// Runs the states until a terminal one has acted. Returns its name. Fails through
// `trace` on an unexpected screen, a state seen too often, a stall or the timeout.
template <typename ControllerContext>
std::string run_screen_machine(
VideoStream& stream, ControllerContext& context, StepTrace& trace,
const std::vector<ScreenState>& states, Milliseconds timeout
){
ScreenMachineChecks checks(trace);
const WallClock deadline = current_time() + timeout;
while (current_time() < deadline){
context.wait_for_all_requests();
VideoSnapshot frame;
const ScreenState& state = read_screen_state(stream, context, trace, states, frame);
trace.screen(state.name);
checks.check(state, std::move(frame));
state.act();
if (state.terminal){
context.wait_for_all_requests();
return state.name;
}
}
trace.fail("Did not finish within " + std::to_string(timeout.count() / 1000) + "s.");
}



}
#endif
74 changes: 74 additions & 0 deletions SerialPrograms/Source/CommonTools/ScreenMachine/StepTrace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Step Trace
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <cstdio>
#include "Common/Cpp/PrettyPrint.h"
#include "Common/Cpp/Filesystem/Filesystem.h"
#include "CommonFramework/GlobalAutoPaths.h"
#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h"
#include "CommonFramework/VideoPipeline/VideoFeed.h"
#include "StepTrace.h"

namespace PokemonAutomation{


StepTrace::StepTrace(VideoStream& stream, std::string label)
: m_stream(stream)
, m_label(std::move(label))
, m_folder(SCREENSHOTS_PATH() + "StepTrace/" + now_to_filestring() + "-" + m_label + "/")
, m_start(current_time())
{
Filesystem::create_directories(Filesystem::Path(m_folder));
m_stream.log("[" + m_label + "] Trace screenshots: " + m_folder);
}

void StepTrace::screen(const std::string& name){
if (name == m_last){
return;
}
m_last = name;
m_recent.push_back(name);
if (m_recent.size() > 12){
m_recent.pop_front();
}
m_count++;
const double seconds = std::chrono::duration<double>(current_time() - m_start).count();
char prefix[16];
std::snprintf(prefix, sizeof(prefix), "%03zu-", m_count);
std::string path = m_folder + prefix + name + ".png";
VideoSnapshot screen = m_stream.video().snapshot();
if (screen){
screen.frame->save(path);
}else{
path = "(no video frame)";
}
m_stream.log("[" + m_label + "] screen " + std::to_string(m_count) + ": " + name +
" at " + tostr_fixed(seconds, 1) + "s, " + path);
}

void StepTrace::action(const std::string& what){
m_stream.log("[" + m_label + "] " + what);
}

std::string StepTrace::recent_screens() const{
std::string text;
for (const std::string& name : m_recent){
text += (text.empty() ? "" : " -> ") + name;
}
return text.empty() ? "(none)" : text;
}

void StepTrace::fail(const std::string& message) const{
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT,
m_label + ": " + message + " Recent screens: " + recent_screens() + ". Trace: " + m_folder,
m_stream
);
}



}
53 changes: 53 additions & 0 deletions SerialPrograms/Source/CommonTools/ScreenMachine/StepTrace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Step Trace
*
* From: https://github.com/PokemonAutomation/
*
* A step log for closed-loop routines: every change of recognized screen is
* logged and saved as a numbered screenshot, every input is logged, and a
* failure reports the screens that led up to it.
*
*/

#ifndef PokemonAutomation_CommonTools_StepTrace_H
#define PokemonAutomation_CommonTools_StepTrace_H

#include <deque>
#include <string>
#include "Common/Cpp/Time.h"
#include "CommonFramework/Tools/VideoStream.h"

namespace PokemonAutomation{


class StepTrace{
public:
// Screenshots go to <Screenshots>/StepTrace/<time>-<label>/.
StepTrace(VideoStream& stream, std::string label);

// Records the screen the routine recognized. Logs and saves a screenshot only
// when it differs from the previous one.
void screen(const std::string& name);

// Records an input or decision.
void action(const std::string& what);

// The last few screens, oldest first, e.g. "dialog -> keyboard -> dialog".
std::string recent_screens() const;

// Throws OperationFailedExceptionWithScreenshot with the recent screens appended.
[[noreturn]] void fail(const std::string& message) const;

private:
VideoStream& m_stream;
std::string m_label;
std::string m_folder;
WallClock m_start;
size_t m_count = 0;
std::string m_last;
std::deque<std::string> m_recent;
};



}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h"
#include "NintendoSwitch/Programs/Typewriter/NintendoSwitch_Typewriter_Tests.h"

#include "Pokemon/AutoStory/Pokemon_AutoStory_Tests.h"
#include "Pokemon/Navigation/Pokemon_Navigation_Tests.h"
#include "VideoGames/PokemonHome/PokemonHome_Tests.h"
#include "VideoGames/PokemonSwSh/PokemonSwSh_Tests.h"
#include "VideoGames/PokemonLA/PokemonLA_Tests.h"
Expand All @@ -49,7 +51,7 @@



UnitTestDatabase make_UNIT_TESTS_ALL(){

Check warning on line 54 in SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::ComputerPrograms::make_UNIT_TESTS_ALL(): function_lines=37 (advisory above 25, limit 50)
UnitTestDatabase ret;

add_tests_QtEventThreadPool(ret, GlobalThreadPools::computation_normal());
Expand All @@ -66,6 +68,12 @@
OCR::add_tests(ret);
Kernels::add_tests(ret);
Pokemon::add_tests_AutoStorySelect(ret);
Pokemon::add_tests_LegPlanner(ret);
Pokemon::add_tests_LegWalker(ret);
Pokemon::add_tests_MapData(ret);
Pokemon::add_tests_RouteLog(ret);
Pokemon::add_tests_StoryEvents(ret);
Pokemon::add_tests_StorySegments(ret);
NintendoSwitch::add_tests_CheckOnlineDetector(ret);
NintendoSwitch::add_tests_FailedToConnectDetector(ret);
NintendoSwitch::add_tests_UpdatePopupDetector(ret);
Expand Down
25 changes: 25 additions & 0 deletions SerialPrograms/Source/Pokemon/AutoStory/Pokemon_AutoStory_Tests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* AutoStory Tests
*
* From: https://github.com/PokemonAutomation/
*
* The unit tests of the shared story engine (Pokemon/AutoStory).
*
*/

#ifndef PokemonAutomation_Pokemon_AutoStory_Tests_H
#define PokemonAutomation_Pokemon_AutoStory_Tests_H

#include "Common/Cpp/TestRunners/UnitTestDatabase.h"

namespace PokemonAutomation{
namespace Pokemon{


void add_tests_StoryEvents(UnitTestDatabase& database);
void add_tests_StorySegments(UnitTestDatabase& database);



}
}
#endif
Loading
Loading