Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4d019e4
FRLG Gift RNG: make the attempts callable
dayfine Nov 3, 2026
886ec34
Merge remote-tracking branch 'origin/frlg/gift-rng-callable-steps' in…
dayfine Nov 3, 2026
6701329
FRLG AutoStory: run Gift RNG for the Magikarp after 00.12
dayfine Nov 3, 2026
65d8210
Merge remote-tracking branch 'origin/frlg/autostory-route3' into HEAD
dayfine Nov 3, 2026
72430b5
FRLG AutoStory: start at 00.12b with Run Gift RNG on to run Gift RNG …
dayfine Nov 3, 2026
c62a367
Merge remote-tracking branch 'origin/frlg/autostory-route3' into HEAD
dayfine Nov 3, 2026
cd1681f
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
5c37581
FRLG AutoStory tests: loop over const char* to satisfy GCC's range-lo…
dayfine Nov 3, 2026
ed05b85
FRLG AutoStory tests: take checkpoint ids as std::string_view
dayfine Nov 3, 2026
70a583b
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
65b8921
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
f7978bd
FRLG AutoStory: start Gift RNG from the stored calibration, save it back
dayfine Nov 3, 2026
a142852
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
19f7508
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
2d660ff
FRLG AutoStory: one short line per Gift RNG reset on the overlay log
dayfine Nov 3, 2026
62e9f5e
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
8f813a5
FRLG AutoStory: 00.12b starts with the moves the lead has after Brock
dayfine Nov 3, 2026
2f01b62
Merge frlg/autostory-route3 (4cac47b1d, #119 recovery and #190 tests)…
dayfine Nov 3, 2026
be10a64
Merge remote-tracking branch 'origin/frlg/autostory-route3' into HEAD
dayfine Nov 3, 2026
6b2357d
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
b43c7ef
Merge frlg/autostory-route3 (battle loop: end on the overworld after …
dayfine Nov 3, 2026
585bd00
Merge frlg/autostory-route3 (a disabled move, and a move list that ne…
dayfine Nov 3, 2026
be50f70
Merge frlg/autostory-mvp (matchup attacks in every battle) into frlg/…
dayfine Nov 3, 2026
f261cef
Merge frlg/autostory-mvp (HP bar share, nurse for yellow HP, move pro…
dayfine Nov 3, 2026
ea1c8e2
Merge frlg/autostory-route3 (79ed4464d: verify mode from #122) into f…
dayfine Nov 3, 2026
f92424e
Merge frlg/autostory-route3 (76511ecde: battle sim from #120) into fr…
dayfine Nov 3, 2026
c70c624
Merge frlg/autostory-route3 into frlg/autostory-gift-rng
dayfine Nov 3, 2026
fc4b6d6
Merge frlg/autostory-route3 (TM moves in the summary reader) into frl…
dayfine Nov 3, 2026
c3b97ec
Merge frlg/autostory-route3 (bag rows below an item the reader can't …
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
21 changes: 14 additions & 7 deletions SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Pokemon FRLG Tests

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=792 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -43,6 +43,7 @@
#include "Programs/AutoStory/PokemonFRLG_AutoStoryBattle.h"
#include "Programs/AutoStory/PokemonFRLG_AutoStoryRngLoop.h"
#include "Programs/AutoStory/PokemonFRLG_AutoStoryTable.h"
#include "Programs/RngManipulation/PokemonFRLG_GiftRngAttempts_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlanInputs_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlanLog_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlanTracker_Tests.h"
Expand Down Expand Up @@ -670,6 +671,18 @@
}
}

// The RNG programs' searches, plans and loops.
void add_tests_RngManipulation(UnitTestDatabase& database){
add_tests_RngTargetSearch(database);
add_tests_RngPlanInputs(database);
add_tests_RngPlan(database);
add_tests_RngPlanTracker(database);
add_tests_RngPlanLog(database);
add_tests_RngSteps(database);
add_tests_StarterRngLoop(database);
add_tests_GiftRngAttempts(database);
}

}


Expand All @@ -682,7 +695,7 @@
add_tests_SummaryMovesReader(database);
}

void add_tests(UnitTestDatabase& database){

Check warning on line 698 in SerialPrograms/Source/VideoGames/PokemonFRLG/PokemonFRLG_Tests.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::add_tests( UnitTestDatabase & database): function_lines=42 (advisory above 25, limit 50)
add_tests_AdvanceWhiteDialogDetector(database);
add_tests_BoxCellOccupiedDetector(database);
add_tests_BoxCellReader(database);
Expand Down Expand Up @@ -722,13 +735,7 @@
add_tests_NamingPlan(database);
add_tests_NamingKeys(database);
add_tests_NamingText(database);
add_tests_RngTargetSearch(database);
add_tests_RngPlanInputs(database);
add_tests_RngPlan(database);
add_tests_RngPlanTracker(database);
add_tests_RngPlanLog(database);
add_tests_RngSteps(database);
add_tests_StarterRngLoop(database);
add_tests_RngManipulation(database);
}


Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,33 @@ class AutoStory : public SingleSwitchProgramInstance{
private:
// The RNG programs' handoffs the run takes, from the options.
StoryHandoffs story_handoffs() const;
// Runs `plan`'s rows, then the RNG handoff they stop at and, for a
// shiny, the rows after it, to the end checkpoint. Returns the last
// checkpoint run, or nothing once the run has stopped.
std::optional<std::string> run_story_plan(
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
StoryEnv& story, StoryExecutor& executor, StoryRunPlan plan
);
// After 00.04 saved under Squirtle's ball (row `ball_row`): the stop
// for Starter RNG, or its attempts run here and, for a shiny, the run
// on to the end checkpoint. Returns the last checkpoint run, or nothing
// once the run has stopped.
std::optional<std::string> after_starter_ball(
// for Starter RNG, or its attempts run here. Returns the rows to run
// after a shiny, or nothing once the run has stopped.
std::optional<StoryRunPlan> after_starter_ball(
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
StoryEnv& story, const StarterRngPlanned& planned, size_t ball_row
);
// Started at 00.12b (row `first`) with Run Gift RNG in AutoStory on,
// the save beside the salesman loaded: Gift RNG's attempts, then for a
// shiny the rows after them. As run_story_plan() returns.
std::optional<std::string> run_from_gift_rng_save(
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
StoryEnv& story, StoryExecutor& executor, size_t first
);
// After 00.12 saved beside the Magikarp salesman (row `salesman_row`):
// Gift RNG's attempts run here. Returns the rows to run after a shiny,
// or nothing once the run has stopped.
std::optional<StoryRunPlan> after_magikarp_salesman(
SingleSwitchProgramEnvironment& env, ProControllerContext& context,
StoryEnv& story, StoryExecutor& executor, const StarterRngPlanned& planned, size_t ball_row
StoryEnv& story, const StarterRngPlanned& planned, size_t salesman_row
);

// The start and end checkpoints, attempts, checks and language, as in
Expand All @@ -69,6 +89,8 @@ class AutoStory : public SingleSwitchProgramInstance{
// Runs SID Helper's steps and Starter RNG's attempts in the program
// instead of stopping for them.
BooleanCheckBoxOption RUN_STARTER_RNG;
// Runs Gift RNG's attempts for the Magikarp in the program after 00.12.
BooleanCheckBoxOption RUN_GIFT_RNG;
StarterRngPlanOptions RNG_PLAN;
EventNotificationOption NOTIFICATION_SHINY;
EventNotificationOption NOTIFICATION_STATUS_UPDATE;
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=924 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -891,17 +891,32 @@
NewGameIntro(console, context, player_name, NewGameIntro::End::PLAYER_NAME_TYPED).run();
}

void close_trainer_card(ConsoleHandle& console, ProControllerContext& context){
namespace{

// After the RNG program `program`: from HOME, if it went there, back to
// the game, then B until the overworld.
void back_to_overworld_after(ConsoleHandle& console, ProControllerContext& context, const std::string& program){
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.");
console.log("After " + program + ": 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);
}

}

void close_trainer_card(ConsoleHandle& console, ProControllerContext& context){
// B closes the card, then the START menu.
back_to_overworld_after(console, context, "SID Helper");
}

void leave_gift_summary(ConsoleHandle& console, ProControllerContext& context){
// B closes the summary, the party, then the START menu.
back_to_overworld_after(console, context, "Gift RNG");
}



}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ void play_new_game_to_player_name(ConsoleHandle& console, ProControllerContext&
// Returns on the overworld.
void close_trainer_card(ConsoleHandle& console, ProControllerContext& context);

// From the gift's summary Gift RNG leaves open, or from HOME (its "go home
// when done"): back to the game, the summary, the party and the START
// menu closed. Returns on the overworld.
void leave_gift_summary(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=603 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -452,6 +452,10 @@
close_trainer_card(m_env.console, m_env.context);
return Pokemon::outcome_ok();
}
if (step.variant == LEAVE_GIFT_SUMMARY){
leave_gift_summary(m_env.console, m_env.context);
return Pokemon::outcome_ok();
}
return Pokemon::unsupported_step(step);
}

Expand All @@ -473,10 +477,17 @@

}

StoryRunPlan plan_story_run(

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

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::plan_story_run( const std :: vector<StoryCheckpoint> & checkpoints , size_t first , size_t last , const StoryHandoffs & handoffs): function_lines=40 (advisory above 25, limit 50)
const std::vector<StoryCheckpoint>& checkpoints, size_t first, size_t last, const StoryHandoffs& handoffs
){
StoryRunPlan plan;
// Started at the way back in from Gift RNG with its handoff on: the run
// takes the handoff again, from the save the row before ends at.
if (first > 0 && first < checkpoints.size() && checkpoints[first].live_start
&& checkpoints[first - 1].handoff == StoryHandoff::GIFT_RNG && handoffs.gift_rng){
plan.stop = StoryHandoff::GIFT_RNG;
return plan;
}
for (size_t c = first; c <= last && c < checkpoints.size(); c++){
const StoryCheckpoint& row = checkpoints[c];
if (row.live_start && c != first){
Expand All @@ -492,6 +503,11 @@
plan.stop = StoryHandoff::STARTER_RNG;
return plan;
}
if (row.handoff == StoryHandoff::GIFT_RNG && handoffs.gift_rng){
plan.rows.emplace_back(row);
plan.stop = StoryHandoff::GIFT_RNG;
return plan;
}
if (row.handoff == StoryHandoff::STARTER_RNG && c + 1 < checkpoints.size()){
plan.rows.emplace_back(joined_rows(row, checkpoints[c + 1]));
c++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ 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";

// The GAME step that leaves the gift's summary Gift RNG leaves open for
// the overworld.
inline const std::string LEAVE_GIFT_SUMMARY = "leave_gift_summary";

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

Expand All @@ -74,6 +78,9 @@ enum class StoryHandoff{
// which is where Starter RNG starts. With the handoff off, it runs on
// into the next checkpoint as one, as the story did before the split.
STARTER_RNG,
// The checkpoint saves beside the Magikarp salesman, facing him, which
// is where Gift RNG starts.
GIFT_RNG,
};

// A shared checkpoint with what FRLG adds to it.
Expand All @@ -94,6 +101,7 @@ struct StoryCheckpoint : public Pokemon::StoryCheckpoint{
struct StoryHandoffs{
bool sid_helper = false;
bool starter_rng = false;
bool gift_rng = false;
};

// What a run from table row `first` to `last` does.
Expand All @@ -103,13 +111,16 @@ struct StoryRunPlan{
// 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.
// STARTER_RNG: the run stops after `rows`, the last of which saves at
// the starter's ball.
// the starter's ball. GIFT_RNG: likewise, beside the Magikarp salesman;
// with `rows` empty, the run started at 00.12b and takes Gift RNG from
// the save beside the salesman, where the row before `first` ends.
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, a run that reaches a handoff that is on stops for it, and
// starts there (00.12b with Gift RNG's handoff on starts at the handoff
// instead), a run that reaches a handoff that is on stops for it, and
// a STARTER_RNG row whose handoff is off is joined with the row after it,
// under its own id.
StoryRunPlan plan_story_run(
Expand Down Expand Up @@ -225,6 +236,7 @@ class StoryExecutor : public Pokemon::StoryStepExecutor{
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()).
// LEAVE_GIFT_SUMMARY: back from Gift RNG (leave_gift_summary()).
virtual Pokemon::Outcome game(Pokemon::RunState& state, const StoryStep& step) override;

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ std::string starter_rng_loop_message(const StarterRngLoopResult& result){
}


std::string gift_rng_loop_message(const StarterRngLoopResult& result){
const std::string save = "The save is still beside the salesman, facing him: start AutoStory at 00.12b with Run Gift "
"RNG in AutoStory on to try again, or run Gift RNG (Magikarp) from it by hand with an attempt from the log, then "
"start AutoStory at 00.12b with that option off.";
switch (result.end){
case StarterRngLoopEnd::SHINY:
return "Gift RNG: shiny Magikarp, on the planned target. 00.12b saves it.";
case StarterRngLoopEnd::SHINY_OFF_TARGET:
return "Gift RNG: shiny Magikarp, but not the planned target. 00.12b saves it.";
case StarterRngLoopEnd::TARGET:
return "Gift RNG hit the target (shiny not required). Nothing was saved after it: start AutoStory at 00.12b "
"to save the Magikarp where it stands.";
case StarterRngLoopEnd::OUT_OF_ATTEMPTS:
return std::format(
"Gift RNG: every planned attempt hit its target and none was shiny, so the RNG plan's SID doesn't fit. "
"SIDs left: {}. {}", join_sids(result.remaining_sids), save
);
case StarterRngLoopEnd::MISSED_EVERY_ATTEMPT_LEFT:
return "Gift RNG: every attempt left missed its target more often than the retries allow, or Gift RNG refused "
"its timings (see the log). Check the RNG plan's sound setting. " + save;
case StarterRngLoopEnd::OUT_OF_TIME:
return std::format("Gift RNG: out of time after {} attempts. {}", result.attempts_run, save);
}
throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown Gift RNG loop end.");
}


CalibrationSavingSteps::CalibrationSavingSteps(
StarterRngLoopSteps& steps, RngStopCalibrationOptions& calibration, FloatingPointOption& ingame
)
Expand All @@ -88,8 +115,25 @@ StarterRngResult CalibrationSavingSteps::run_attempt(const RngAttempt& attempt,
}


std::vector<StoryCheckpoint> rows_after_starter_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t ball_row, size_t last
namespace{

// `plan` with the rows from `first` to `last` as plan_story_run() takes
// them after it, and the handoff they stop at.
StoryRunPlan then_rows(
StoryRunPlan plan, const std::vector<StoryCheckpoint>& checkpoints, size_t first, size_t last, const StoryHandoffs& handoffs
){
if (last >= first){
StoryRunPlan rest = plan_story_run(checkpoints, first, last, handoffs);
plan.rows.insert(plan.rows.end(), rest.rows.begin(), rest.rows.end());
plan.stop = rest.stop;
}
return plan;
}

}

StoryRunPlan rows_after_starter_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t ball_row, size_t last, const StoryHandoffs& handoffs
){
if (ball_row + 1 >= checkpoints.size()){
throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No checkpoint after the starter's ball.");
Expand All @@ -104,12 +148,24 @@ std::vector<StoryCheckpoint> rows_after_starter_rng(
row.steps.erase(row.steps.begin(), taken + 1);
row.title = "after Starter RNG in AutoStory: the rival's battle";

std::vector<StoryCheckpoint> rows{std::move(row)};
if (last > ball_row + 1){
StoryRunPlan rest = plan_story_run(checkpoints, ball_row + 2, last, StoryHandoffs{});
rows.insert(rows.end(), rest.rows.begin(), rest.rows.end());
StoryRunPlan plan;
plan.rows.emplace_back(std::move(row));
return then_rows(std::move(plan), checkpoints, ball_row + 2, last, handoffs);
}

StoryRunPlan rows_after_gift_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t salesman_row, size_t last, const StoryHandoffs& handoffs
){
if (salesman_row + 1 >= checkpoints.size()){
throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "No checkpoint after the Magikarp salesman.");
}
const StoryCheckpoint& row = checkpoints[salesman_row + 1];
if (row.steps.empty() || row.steps.front().variant != LEAVE_GIFT_SUMMARY){
throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, row.id + " doesn't start by leaving the gift's summary.");
}
return rows;
StoryRunPlan plan;
plan.rows.emplace_back(row);
return then_rows(std::move(plan), checkpoints, salesman_row + 2, last, handoffs);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
*
* From: https://github.com/PokemonAutomation/
*
* AutoStory's Starter RNG stop, run in the program with the Starter RNG
* loop (RngManipulation/PokemonFRLG_StarterRngLoop.h) from the save under
* Squirtle's ball: the plan it runs, what the run says when the loop
* AutoStory's RNG stops, run in the program with the RNG plan loop
* (RngManipulation/PokemonFRLG_StarterRngLoop.h): Starter RNG from the
* save under Squirtle's ball, and Gift RNG from the save beside Route 4's
* Magikarp salesman. The plan each runs, what the run says when the loop
* ends, and the rows it takes after a shiny.
*
*/
Expand All @@ -26,14 +27,16 @@ namespace NintendoSwitch{
namespace PokemonFRLG{


// The Starter RNG plan, for the TID it was made for.
// An RNG plan (Starter RNG's or Gift RNG's), for the TID it was made for.
struct StarterRngPlanned{
uint16_t tid = 0;
std::vector<RngAttempt> attempts;
};

// What the run says when the loop ends with `result`.
std::string starter_rng_loop_message(const StarterRngLoopResult& result);
// As starter_rng_loop_message(), for Gift RNG's Magikarp.
std::string gift_rng_loop_message(const StarterRngLoopResult& result);


// `steps`, with the calibration of each reset also kept in the RNG plan's
Expand All @@ -56,10 +59,21 @@ class CalibrationSavingSteps : public StarterRngLoopSteps{
// The rows a run takes after a shiny from the loop, which leaves the player
// under Squirtle's ball with the rival's ball taken: the row after
// `ball_row` (00.04b) from its step after "rival_takes_bulbasaur", then
// the rows after it to `last` as plan_story_run() takes them. Throws
// InternalProgramError for a table without that row or step.
std::vector<StoryCheckpoint> rows_after_starter_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t ball_row, size_t last
// the rows after it to `last` as plan_story_run() takes them with
// `handoffs`, with the handoff they stop at. Throws InternalProgramError
// for a table without that row or step.
StoryRunPlan rows_after_starter_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t ball_row, size_t last, const StoryHandoffs& handoffs
);

// The rows a run takes after a shiny Magikarp from the loop, which leaves
// its summary open beside the salesman: the row after `salesman_row`
// (00.12b), which leaves the summary and saves, then the rows after it to
// `last` as plan_story_run() takes them with `handoffs`. Throws
// InternalProgramError for a table whose next row doesn't leave the
// summary.
StoryRunPlan rows_after_gift_rng(
const std::vector<StoryCheckpoint>& checkpoints, size_t salesman_row, size_t last, const StoryHandoffs& handoffs
);


Expand Down
Loading
Loading