Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d9945ed
FRLG AutoStory: stop for SID Helper, and come back in after it
dayfine Nov 2, 2026
104b66c
Merge commit 'HEAD@{1}' into HEAD
dayfine Nov 2, 2026
1e57a3c
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 2, 2026
8f6383b
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 2, 2026
53eb31a
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 2, 2026
8142b5e
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 2, 2026
8460cc3
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 3, 2026
2f98ebe
Merge remote-tracking branch 'origin/frlg/autostory-player-name' into…
dayfine Nov 3, 2026
64ab0d1
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 3, 2026
ac15118
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 3, 2026
794a285
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 3, 2026
d28c05c
Merge branch 'frlg/autostory-player-name' into frlg/autostory-sid-helper
dayfine Nov 3, 2026
177553e
Merge remote-tracking branch 'origin/frlg/autostory-player-name' into…
dayfine Nov 3, 2026
2f4b7a2
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 3, 2026
8ba4155
FRLG AutoStory tests: take the checkpoint id as std::string_view in r…
dayfine Nov 3, 2026
6b72765
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 3, 2026
047f95d
Merge frlg/autostory-player-name (021ed573a, #119 recovery) into frlg…
dayfine Nov 3, 2026
4b853a3
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 3, 2026
38c740b
Merge remote-tracking branch 'origin/frlg/autostory-player-name' into…
dayfine Nov 3, 2026
7e34680
Merge remote-tracking branch 'origin/frlg/autostory-player-name' into…
dayfine Nov 3, 2026
3261803
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 3, 2026
57a669d
Merge frlg/autostory-player-name (battle loop: end on the overworld a…
dayfine Nov 3, 2026
e27943e
Merge frlg/autostory-player-name (a disabled move, and a move list th…
dayfine Nov 3, 2026
4fc0aac
Merge frlg/autostory-mvp (matchup attacks in every battle) into frlg/…
dayfine Nov 3, 2026
0e1391d
Merge frlg/autostory-mvp (HP bar share, nurse for yellow HP, move pro…
dayfine Nov 3, 2026
f851777
Merge frlg/autostory-player-name (fb56c02c1: verify mode from #122) i…
dayfine Nov 3, 2026
886eddd
FRLG AutoStory: verify mode refuses a live-start checkpoint
dayfine Nov 3, 2026
f4f5a75
Merge frlg/autostory-player-name (29ca9d33a: battle sim from #120) in…
dayfine Nov 3, 2026
0dbd8c0
Merge frlg/autostory-player-name into frlg/autostory-sid-helper
dayfine Nov 3, 2026
1e9d266
Merge frlg/autostory-player-name (TM moves in the summary reader) int…
dayfine Nov 3, 2026
55227eb
Merge frlg/autostory-player-name (bag rows below an item the reader c…
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

}

AutoStory::AutoStory()

Check warning on line 56 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::AutoStory::AutoStory(): function_lines=35 (advisory above 25, limit 50)
: RUN(checkpoint_database())
, NEW_GAME_FROM_TITLE(
"<b>New game from the title screen:</b><br>The game is on its title screen with no save data. "
Expand All @@ -68,6 +68,15 @@
LockMode::LOCK_WHILE_RUNNING,
"Kosmos", ""
)
, STOP_FOR_SID_HELPER(
"<b>Stop for SID Helper:</b><br>Off by default. At 00.01, stop on the player's keyboard with the name typed and OK not pressed, "
"where the SID Helper program starts. After SID Helper, start AutoStory at 00.01b, with the game as SID Helper left it "
"(the Trainer Card, or HOME): it closes the card, sets the options and saves in the bedroom.<br>"
"SID Helper needs the text speed and button settings already saved. On a cartridge with that save, choose NEW GAME "
"by hand and start AutoStory on the first message.",
LockMode::LOCK_WHILE_RUNNING,
false
)
, NOTIFICATIONS({
&NOTIFICATION_PROGRAM_FINISH,
&NOTIFICATION_ERROR_FATAL,
Expand All @@ -76,6 +85,7 @@
PA_ADD_OPTION(RUN);
PA_ADD_OPTION(NEW_GAME_FROM_TITLE);
PA_ADD_OPTION(PLAYER_NAME);
PA_ADD_OPTION(STOP_FOR_SID_HELPER);
PA_ADD_OPTION(NOTIFICATIONS);
}

Expand Down Expand Up @@ -156,6 +166,11 @@
"Verify mode reloads the checkpoint's save between runs: " + checkpoint.id + " starts a new game and has none."
);
}
if (checkpoint.live_start){
throw UserSetupError(env.console,
"Verify mode reloads the checkpoint's save between runs: " + checkpoint.id + " starts from the game as it is, with no save."
);
}
env.console.log("Verify mode: loading the save for " + checkpoint.id + ".");
soft_reset(env.console, context);
story.has_save = true;
Expand Down Expand Up @@ -212,7 +227,51 @@
}
}

static void throw_if_failed(const Pokemon::SegmentReport& report){
if (report.completed){
return;
}
throw OperationFailedException(
ErrorReportMode::NO_ERROR_REPORT,
std::format(
"AutoStory stopped at {} {}: {}",
report.failed_checkpoint, report.failed_event, Pokemon::to_string(report.failure)
)
);
}

// Gets the game to where checkpoint `first` starts: a new game from the
// title screen, the game as another program left it, or the checkpoint's
// save loaded.
static void prepare_start(
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
const std::vector<StoryCheckpoint>& checkpoints, size_t first, StoryEnv& story
){
const StoryCheckpoint& checkpoint = checkpoints[first];
if (!checkpoint.start){
return;
}
if (checkpoint.live_start){
env.console.log(checkpoint.id + " starts from the game as it is: no save to load.");
return;
}
env.console.log("Loading the save for " + checkpoint.id + ".");
soft_reset(env.console, context);
story.has_save = true;
check_start_save(env, story, checkpoints[first]);
read_lead_moves(story, checkpoints[first].menu);
read_bag_supplies(story, checkpoints[first].menu);
}

// The run stopped for an RNG program, as its option asked: the user's
// turn, with what to do next.
static void stop_for_handoff(SingleSwitchProgramEnvironment& env, EventNotificationOption& notification, const std::string& message){
env.console.log(message, COLOR_BLUE);
env.console.overlay().add_log(message);
send_program_finished_notification(env, notification, message);
}

void AutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){

Check warning on line 274 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStory.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::AutoStory::program( SingleSwitchProgramEnvironment & env , ProControllerContext & context): function_lines=40 (advisory above 25, limit 50)
if (const std::optional<Pokemon::VerifyPolicy> verify = RUN.VERIFY.policy()){
verify_story_checkpoint(env, context, RUN, *verify, NOTIFICATION_PROGRAM_FINISH);
return;
Expand All @@ -222,40 +281,34 @@
const size_t last = RUN.end_index();
const std::string player_name = PLAYER_NAME;
check_setup(env.console, checkpoints, first, last, NEW_GAME_FROM_TITLE, player_name);
const StoryRunPlan plan = plan_story_run(checkpoints, first, last, StoryHandoffs{.sid_helper = STOP_FOR_SID_HELPER});
if (plan.stop == StoryHandoff::SID_HELPER){
play_new_game_to_player_name(env.console, context, player_name);
stop_for_handoff(env, NOTIFICATION_PROGRAM_FINISH,
"Stopped for SID Helper: the player's name is typed and OK is not pressed. "
"Run SID Helper now, then start AutoStory at 00.01b."
);
return;
}

StoryEnv story{env.console, context, checkpoints[first].supplies, checkpoints[first].supplies, false, RUN.checks()};
story.player_name = player_name;
if (!(first == 0 && NEW_GAME_FROM_TITLE)){
env.console.log("Loading the save for " + checkpoints[first].id + ".");
soft_reset(env.console, context);
story.has_save = true;
check_start_save(env, story, checkpoints[first]);
read_lead_moves(story, checkpoints[first].menu);
read_bag_supplies(story, checkpoints[first].menu);
}
prepare_start(env, context, checkpoints, first, story);

StoryExecutor executor(story);
executor.set_checks(RUN.checks());
executor.set_language(RUN.language());
Pokemon::Segment segment = make_story_segment(checkpoints, executor);
Pokemon::Segment segment = make_story_segment(plan.rows, executor);
Pokemon::SegmentHooks hooks = story_hooks(env, story);

Pokemon::RunState state;
const Pokemon::SegmentReport report = run_segment(
state, segment, Pokemon::SegmentRange{checkpoints[first].id, checkpoints[last].id}, RUN.policy(), hooks
state, segment, Pokemon::SegmentRange{plan.rows.front().id, plan.rows.back().id}, RUN.policy(), hooks
);
Pokemon::record_story_stats(env.current_stats<Pokemon::AutoStoryStats>(), report);
env.update_stats();
if (!report.completed){
throw OperationFailedException(
ErrorReportMode::NO_ERROR_REPORT,
std::format(
"AutoStory stopped at {} {}: {}",
report.failed_checkpoint, report.failed_event, Pokemon::to_string(report.failure)
)
);
}
env.console.log("AutoStory: done through " + checkpoints[last].id + ".");
throw_if_failed(report);
env.console.log("AutoStory: done through " + plan.rows.back().id + ".");
send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class AutoStory : public SingleSwitchProgramInstance{
Pokemon::StoryRunOptions RUN;
BooleanCheckBoxOption NEW_GAME_FROM_TITLE;
StringOption PLAYER_NAME;
// The RNG programs the run stops for (StoryHandoff).
BooleanCheckBoxOption STOP_FOR_SID_HELPER;
EventNotificationsOption NOTIFICATIONS;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* AutoStory Actions

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryActions.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=909 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand All @@ -16,6 +16,8 @@
#include "CommonTools/Async/InferenceRoutines.h"
#include "CommonTools/Images/SolidColorTest.h"
#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
#include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h"
#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h"
#include "VideoGames/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h"
#include "VideoGames/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h"
#include "VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_ItemListReader.h"
Expand Down Expand Up @@ -179,10 +181,18 @@
// GREEN.
class NewGameIntro{
public:
NewGameIntro(ConsoleHandle& console, ProControllerContext& context, std::string player_name)
// Where the intro stops: the bedroom, or the player's keyboard with the
// name typed and OK not pressed (for SID Helper).
enum class End{
BEDROOM,
PLAYER_NAME_TYPED,
};

NewGameIntro(ConsoleHandle& console, ProControllerContext& context, std::string player_name, End end)
: m_console(console)
, m_context(context)
, m_player_name(std::move(player_name))
, m_end(end)
{}

void run(){
Expand All @@ -192,7 +202,7 @@
m_last_press = m_last_dialog;
while (current_time() < deadline){
if (step()){
m_console.log("New game: the bedroom.");
m_console.log(m_end == End::BEDROOM ? "New game: the bedroom." : "New game: the player's name is typed, OK not pressed.");
return;
}
}
Expand Down Expand Up @@ -223,7 +233,7 @@
}

// True once the bedroom shows.
bool step(){

Check warning on line 236 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryActions.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::NewGameIntro::step(): function_lines=32 (advisory above 25, limit 50)
m_context.wait_for_all_requests();
const WallClock now = current_time();
VideoSnapshot screen = m_console.video().snapshot();
Expand All @@ -231,6 +241,12 @@
pbf_wait(m_context, POLL);
return false;
}
if (m_end == End::PLAYER_NAME_TYPED && m_keyboards_seen == 0 && NamingScreenDetector(COLOR_RED).detect(screen)){
m_keyboards_seen++;
clear_stray_keys();
type_player_name(false);
return true;
}
if (names(screen)){
m_last_dialog = current_time();
m_last_press = m_last_dialog;
Expand Down Expand Up @@ -320,30 +336,46 @@
return false;
}

void enter_name(bool player){
// B deletes whatever a stray A typed; the cursor stays on its key.
void clear_stray_keys(){
for (int c = 0; c < 8; c++){
press(m_context, BUTTON_B, 340ms);
}
}

void enter_name(bool player){
clear_stray_keys();
if (!player || m_player_name.empty()){
m_console.log("New game: the keyboard, confirming an empty name.");
press(m_context, BUTTON_PLUS, 500ms);
press(m_context, BUTTON_A, 2000ms);
m_context.wait_for_all_requests();
return;
}
if (!type_name(m_console, m_context, m_player_name, NamingScreenKind::PLAYER)){
type_player_name(true);
// Past the keyboard's fade, so it isn't taken for the rival's.
pbf_wait(m_context, 2000ms);
m_context.wait_for_all_requests();
}

void type_player_name(bool confirm){
if (m_player_name.empty()){
return;
}
const bool typed = confirm
? type_name(m_console, m_context, m_player_name, NamingScreenKind::PLAYER)
: type_name_without_confirming(m_console, m_context, m_player_name, NamingScreenKind::PLAYER);
if (!typed){
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT, "play_new_game_intro(): the keyboard closed before the name was typed.", m_console
);
}
// Past the keyboard's fade, so it isn't taken for the rival's.
pbf_wait(m_context, 2000ms);
m_context.wait_for_all_requests();
}

ConsoleHandle& m_console;
ProControllerContext& m_context;
const std::string m_player_name;
const End m_end;
WallClock m_last_dialog;
WallClock m_last_press;
bool m_dialog_seen = false;
Expand Down Expand Up @@ -851,7 +883,23 @@

void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name){
console.log("New game: from the title screen.");
NewGameIntro(console, context, player_name).run();
NewGameIntro(console, context, player_name, NewGameIntro::End::BEDROOM).run();
}

void play_new_game_to_player_name(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name){
console.log("New game: from the title screen to the player's keyboard, for SID Helper.");
NewGameIntro(console, context, player_name, NewGameIntro::End::PLAYER_NAME_TYPED).run();
}

void close_trainer_card(ConsoleHandle& console, ProControllerContext& context){
context.wait_for_all_requests();
VideoSnapshot screen = console.video().snapshot();
if (screen && HomeMenuDetector(console, COLOR_RED).detect(screen)){
console.log("After SID Helper: on HOME, back to the game.");
resume_game_from_home(console, context);
}
// B closes the card, then the START menu.
mash_until_overworld(console, context, 20000ms);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ void buy_items(
// (naming_text_error()).
void play_new_game_intro(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name);

// play_new_game_intro() up to the player's keyboard: `player_name` typed,
// OK not pressed, which is where SID Helper starts. Empty types nothing.
void play_new_game_to_player_name(ConsoleHandle& console, ProControllerContext& context, const std::string& player_name);

// From the Trainer Card SID Helper leaves open, or from HOME (its "go
// home when done"): back to the game, the card and the START menu closed.
// Returns on the overworld.
void close_trainer_card(ConsoleHandle& console, ProControllerContext& context);



}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* AutoStory Events

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/Programs/AutoStory/PokemonFRLG_AutoStoryEvents.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=556 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -443,6 +443,34 @@
return get_ready(m_env, state, step);
}

Outcome StoryExecutor::game(RunState&, const StoryStep& step){
if (step.variant == CLOSE_TRAINER_CARD){
close_trainer_card(m_env.console, m_env.context);
return Pokemon::outcome_ok();
}
return Pokemon::unsupported_step(step);
}


StoryRunPlan plan_story_run(
const std::vector<StoryCheckpoint>& checkpoints, size_t first, size_t last, const StoryHandoffs& handoffs
){
StoryRunPlan plan;
for (size_t c = first; c <= last && c < checkpoints.size(); c++){
const StoryCheckpoint& row = checkpoints[c];
if (row.live_start && c != first){
continue;
}
if (row.handoff == StoryHandoff::SID_HELPER && handoffs.sid_helper){
plan.rows.clear();
plan.stop = StoryHandoff::SID_HELPER;
return plan;
}
plan.rows.emplace_back(row);
}
return plan;
}


std::string check_save_place(StoryEnv& env, const std::string& start_map){
if (save_card_place(start_map).empty()){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,57 @@ struct StoryPose{
// has the Running Shoes (00.10 on).
inline const std::string RUN_VARIANT = "run";

// The GAME step that closes the Trainer Card SID Helper leaves open.
inline const std::string CLOSE_TRAINER_CARD = "close_trainer_card";

// How `step`, a ROUTE or LEGS, moves the player: RUN_VARIANT runs.
MoveSpeed move_speed(const StoryStep& step);


// An upstream RNG program the user may run part-way through the story.
// Each is off unless its option is on.
enum class StoryHandoff{
NONE,
// The checkpoint's new game stops on the player's keyboard, the name
// typed and OK not pressed, which is where SID Helper starts.
SID_HELPER,
};

// A shared checkpoint with what FRLG adds to it.
struct StoryCheckpoint : public Pokemon::StoryCheckpoint{
// The healing items at its start, for a run that loads its save.
Supplies supplies;
// The START menu at its end, when it saves.
StartMenu menu = StartMenu::FULL;
// Starts from the game as another program left it, loading no save:
// the way back into the story after an RNG program. A run that starts
// before it passes over it.
bool live_start = false;
// The RNG program the user may run at this checkpoint.
StoryHandoff handoff = StoryHandoff::NONE;
};

// The handoffs a run stops for (AutoStory's options).
struct StoryHandoffs{
bool sid_helper = false;
};

// What a run from table row `first` to `last` does.
struct StoryRunPlan{
// The checkpoints to run, in order.
std::vector<StoryCheckpoint> rows;
// NONE: the run ends after `rows`. SID_HELPER: `rows` is empty; the run
// plays the new game to the player's keyboard and stops there.
StoryHandoff stop = StoryHandoff::NONE;
};

// The rows of `checkpoints` from `first` to `last`, both inclusive, as a
// run with `handoffs` takes them: live-start rows only when the run
// starts there, and a run that reaches a handoff that is on stops for it.
StoryRunPlan plan_story_run(
const std::vector<StoryCheckpoint>& checkpoints, size_t first, size_t last, const StoryHandoffs& handoffs
);


// The maps the story plans on: every map to Brock, with the objects the
// game hides after the Hall of Fame removed, as the lab's frlg_route does.
Expand Down Expand Up @@ -175,6 +214,8 @@ class StoryExecutor : public Pokemon::StoryStepExecutor{
// The Viridian Mart, before the Boulder Badge: Potions, Antidotes and Parlyz Heals.
virtual Pokemon::Outcome shop(Pokemon::RunState& state, const StoryStep& step) override;
virtual Pokemon::Outcome ready(Pokemon::RunState& state, const StoryStep& step) override;
// CLOSE_TRAINER_CARD: back from SID Helper (close_trainer_card()).
virtual Pokemon::Outcome game(Pokemon::RunState& state, const StoryStep& step) override;

private:
StoryEnv& m_env;
Expand Down
Loading
Loading