Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b613356
FRLG: make Starter RNG's attempts and SID Helper's steps callable
dayfine Nov 2, 2026
6b74b60
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
ef72d7a
FRLG Starter RNG: an on_reset hook in StarterRngAttempts' settings
dayfine Nov 3, 2026
ac75f77
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
78eaf2d
FRLG Starter RNG: tell whether a shiny starter was the target
dayfine Nov 3, 2026
ca6bc1b
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
65a95ca
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
a7b0e1f
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
eb92b89
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
9534663
Merge remote-tracking branch 'origin/frlg/autostory-rng-plan' into HEAD
dayfine Nov 3, 2026
bdab0b6
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
574746e
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
1f60f2b
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
418815d
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
e7ba98f
Merge branch 'frlg/autostory-rng-plan' into frlg/rng-callable-steps
dayfine Nov 3, 2026
560878f
Merge remote-tracking branch 'origin/frlg/autostory-rng-plan' into HEAD
dayfine Nov 3, 2026
c399a30
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
6ae923a
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
2191940
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
0868caa
FRLG Starter RNG: report each reset's reads to the caller
dayfine Nov 3, 2026
c9cf604
FRLG Starter RNG: name on_read in the program's settings
dayfine Nov 3, 2026
2fd7af9
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
256f7c0
Merge frlg/autostory-rng-plan (4463facec, #119 recovery) into frlg/rn…
dayfine Nov 3, 2026
645004d
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
f65a66e
Merge remote-tracking branch 'origin/frlg/autostory-rng-plan' into HEAD
dayfine Nov 3, 2026
776045b
Merge remote-tracking branch 'origin/frlg/autostory-rng-plan' into HEAD
dayfine Nov 3, 2026
05b2e24
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
4f396de
Merge frlg/autostory-rng-plan (battle loop: end on the overworld afte…
dayfine Nov 3, 2026
c43506d
Merge frlg/autostory-rng-plan (a disabled move, and a move list that …
dayfine Nov 3, 2026
71807b3
Merge frlg/autostory-mvp (matchup attacks in every battle) into frlg/…
dayfine Nov 3, 2026
1a90b05
Merge frlg/autostory-mvp (HP bar share, nurse for yellow HP, move pro…
dayfine Nov 3, 2026
8619597
Merge frlg/autostory-rng-plan (a3dbfdd35: verify mode from #122) into…
dayfine Nov 3, 2026
9959dbb
Merge frlg/autostory-rng-plan (2884d2022: battle sim from #120) into …
dayfine Nov 3, 2026
5cd454e
Merge frlg/autostory-rng-plan into frlg/rng-callable-steps
dayfine Nov 3, 2026
13a5d8b
Merge frlg/autostory-rng-plan (TM moves in the summary reader) into f…
dayfine Nov 3, 2026
403bc0e
Merge frlg/autostory-rng-plan (bag rows below an item the reader can'…
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
@@ -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=781 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -46,6 +46,7 @@
#include "Programs/RngManipulation/PokemonFRLG_RngPlanLog_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlanTracker_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngPlan_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngSteps_Tests.h"
#include "Programs/RngManipulation/PokemonFRLG_RngTargetSearch_Tests.h"
#include "Tests/TestUtils.h"

Expand Down Expand Up @@ -679,7 +680,7 @@
add_tests_SummaryMovesReader(database);
}

void add_tests(UnitTestDatabase& database){

Check warning on line 683 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=46 (advisory above 25, limit 50)
add_tests_AdvanceWhiteDialogDetector(database);
add_tests_BoxCellOccupiedDetector(database);
add_tests_BoxCellReader(database);
Expand Down Expand Up @@ -723,6 +724,7 @@
add_tests_RngPlan(database);
add_tests_RngPlanTracker(database);
add_tests_RngPlanLog(database);
add_tests_RngSteps(database);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
/* RNG Steps Tests
*
* From: https://github.com/PokemonAutomation/
*
* SID Helper's intro timing per language, and Starter RNG's per-starter
* tables, as SID Helper and Starter RNG computed them before the steps
* moved out of the programs; and whether a shiny was the target.
*
*/

#include <format>
#include <optional>
#include <utility>
#include <string>
#include <vector>
#include "CommonFramework/Language.h"
#include "PokemonFRLG_SidSteps.h"
#include "PokemonFRLG_StarterRngAttempts.h"
#include "PokemonFRLG_RngSteps_Tests.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{

using namespace Pokemon;


namespace{

struct SidTimingRow{
Language language;
uint32_t target_advances;
uint64_t delay_ms;
bool extra_press_at_end;
};

// (target_advances - 2 * final text frames + 7) frames of 1000 / 59.999977 ms,
// halved since advances pass 2 by 2.
const std::vector<SidTimingRow> SID_TIMING_ROWS = {
{.language = Language::English, .target_advances = 3001, .delay_ms = 20916, .extra_press_at_end = false},
{.language = Language::English, .target_advances = 2275, .delay_ms = 14866, .extra_press_at_end = false},
{.language = Language::Japanese, .target_advances = 3001, .delay_ms = 21833, .extra_press_at_end = true},
{.language = Language::Italian, .target_advances = 3001, .delay_ms = 21133, .extra_press_at_end = false},
{.language = Language::French, .target_advances = 3001, .delay_ms = 21650, .extra_press_at_end = true},
{.language = Language::German, .target_advances = 3001, .delay_ms = 21600, .extra_press_at_end = true},
{.language = Language::Spanish, .target_advances = 2275, .delay_ms = 15650, .extra_press_at_end = true},
};

class Test_SidTiming : public UnitTest{
public:
explicit Test_SidTiming(const SidTimingRow& row)
: UnitTest(std::format(
"PokemonFRLG::RngSteps - SID timing {} {}",
language_data(row.language).name, row.target_advances
))
, m_row(row)
{}

virtual UnitTestResult run(Logger&, CancellableScope&) const override{
SidTiming timing = sid_timing(m_row.language, m_row.target_advances);
if (timing.delay_ms != m_row.delay_ms || timing.extra_press_at_end != m_row.extra_press_at_end){
return std::format(
"Expected delay {} ms, extra press {}; got {} ms, extra press {}",
m_row.delay_ms, m_row.extra_press_at_end,
timing.delay_ms, timing.extra_press_at_end
);
}
return true;
}

private:
SidTimingRow m_row;
};


struct StarterRow{
StarterSpecies starter;
std::string name;
BaseStats base_stats;
EVs rival_ev_yield;
int steps_left;
};

const std::vector<StarterRow> STARTER_ROWS = {
{
.starter = StarterSpecies::bulbasaur,
.name = "Bulbasaur",
.base_stats = {45, 49, 49, 65, 65, 45},
.rival_ev_yield = {0, 0, 0, 0, 0, 1},
.steps_left = 2,
},
{
.starter = StarterSpecies::squirtle,
.name = "Squirtle",
.base_stats = {44, 48, 65, 50, 64, 43},
.rival_ev_yield = {0, 0, 0, 1, 0, 0},
.steps_left = 3,
},
{
.starter = StarterSpecies::charmander,
.name = "Charmander",
.base_stats = {39, 52, 43, 60, 50, 65},
.rival_ev_yield = {0, 0, 1, 0, 0, 0},
.steps_left = 4,
},
};

std::string to_string(const BaseStats& stats){
return std::format("{}/{}/{}/{}/{}/{}", stats.hp, stats.attack, stats.defense, stats.spatk, stats.spdef, stats.speed);
}
std::string to_string(const EVs& evs){
return std::format("{}/{}/{}/{}/{}/{}", evs.hp, evs.attack, evs.defense, evs.spatk, evs.spdef, evs.speed);
}

class Test_StarterTables : public UnitTest{
public:
explicit Test_StarterTables(const StarterRow& row)
: UnitTest("PokemonFRLG::RngSteps - Starter tables " + row.name)
, m_row(row)
{}

virtual UnitTestResult run(Logger&, CancellableScope&) const override{
std::string expected = std::format(
"base stats {}, rival EVs {}, steps left {}",
to_string(m_row.base_stats), to_string(m_row.rival_ev_yield), m_row.steps_left
);
std::string got = std::format(
"base stats {}, rival EVs {}, steps left {}",
to_string(starter_base_stats(m_row.starter)),
to_string(rival_ev_yield(m_row.starter)),
steps_left_to_lab_door(m_row.starter)
);
if (expected != got){
return "Expected " + expected + "; got " + got;
}
return true;
}

private:
StarterRow m_row;
};



// Target: seed 0x70FE, advance 1000.
struct ShinyRow{
std::string name;
// The (seed, advance) of each state the shiny's stats match.
std::vector<std::pair<uint16_t, uint64_t>> hits;
StarterRngOutcome expected;
};

const std::vector<ShinyRow> SHINY_ROWS = {
{.name = "the target alone", .hits = {{0x70fe, 1000}}, .expected = StarterRngOutcome::shiny_on_target},
{.name = "the target among others", .hits = {{0x70fe, 998}, {0x70fe, 1000}, {0x70ff, 1000}}, .expected = StarterRngOutcome::shiny_on_target},
{.name = "another advance", .hits = {{0x70fe, 1002}}, .expected = StarterRngOutcome::shiny_off_target},
{.name = "another seed", .hits = {{0x70fd, 1000}}, .expected = StarterRngOutcome::shiny_off_target},
{.name = "no match", .hits = {}, .expected = StarterRngOutcome::shiny},
};

const char* outcome_name(StarterRngOutcome outcome){
switch (outcome){
case StarterRngOutcome::shiny: return "shiny";
case StarterRngOutcome::shiny_on_target: return "shiny_on_target";
case StarterRngOutcome::shiny_off_target: return "shiny_off_target";
case StarterRngOutcome::target_hit: return "target_hit";
case StarterRngOutcome::wild_shiny: return "wild_shiny";
case StarterRngOutcome::out_of_resets: return "out_of_resets";
case StarterRngOutcome::no_matches: return "no_matches";
}
return "?";
}

class Test_IdentifyShiny : public UnitTest{
public:
explicit Test_IdentifyShiny(const ShinyRow& row)
: UnitTest("PokemonFRLG::RngSteps - Shiny " + row.name)
, m_row(row)
{}

virtual UnitTestResult run(Logger&, CancellableScope&) const override{
std::vector<AdvRngState> hits;
for (const auto& [seed, advance] : m_row.hits){
AdvRngState hit{};
hit.seed = seed;
hit.advance = advance;
hits.push_back(hit);
}
const StarterRngOutcome got = identify_shiny(0x70fe, 1000, hits);
if (got != m_row.expected){
return std::string("Expected ") + outcome_name(m_row.expected) + ", got " + outcome_name(got);
}
return true;
}

private:
ShinyRow m_row;
};


AdvRngState state(uint16_t seed, uint64_t advance){
AdvRngState hit{};
hit.seed = seed;
hit.advance = advance;
return hit;
}

// A state's (seed, advance).
using SeedAdvance = std::pair<uint16_t, uint64_t>;

std::string state_text(const std::optional<SeedAdvance>& state){
return state ? std::format("{:04x}/{}", state->first, state->second) : "none";
}

struct ResetHitRow{
std::string name;
// The reset's hits, and what update_history() left after it.
std::vector<AdvRngState> hits;
size_t uncertain = 0;
std::vector<AdvRngState> calibrated;
// The (seed, advance) taken as hit, if any.
std::optional<SeedAdvance> expected;
};

const std::vector<ResetHitRow> RESET_HIT_ROWS = {
{.name = "one hit", .hits = {state(0x70fe, 1001)}, .uncertain = 0, .calibrated = {state(0x70fe, 1001)}, .expected = SeedAdvance{0x70fe, 1001}},
{
.name = "settled by the history", .hits = {state(0x70fe, 998), state(0x70ff, 1003)}, .uncertain = 0,
.calibrated = {state(0x70fd, 1004), state(0x70ff, 1003)}, .expected = SeedAdvance{0x70ff, 1003},
},
{.name = "a tie", .hits = {state(0x70fe, 998), state(0x70ff, 1003)}, .uncertain = 2, .calibrated = {state(0x70fd, 1004)}, .expected = std::nullopt},
{.name = "no hits", .hits = {}, .uncertain = 0, .calibrated = {state(0x70fd, 1004)}, .expected = std::nullopt},
};

class Test_ResetHit : public UnitTest{
public:
explicit Test_ResetHit(const ResetHitRow& row)
: UnitTest("PokemonFRLG::RngSteps - Reset hit " + row.name)
, m_row(row)
{}

virtual UnitTestResult run(Logger&, CancellableScope&) const override{
RngUncertainHistory uncertain;
uncertain.results.resize(m_row.uncertain);
RngCalibrationHistory calibration;
calibration.results = m_row.calibrated;
const std::optional<AdvRngState> hit = reset_hit(uncertain, calibration, m_row.hits);
std::optional<SeedAdvance> got;
if (hit){
got = SeedAdvance{hit->seed, hit->advance};
}
if (got != m_row.expected){
return "Expected " + state_text(m_row.expected) + ", got " + state_text(got);
}
return true;
}

private:
ResetHitRow m_row;
};

}


void add_tests_RngSteps(UnitTestDatabase& database){
for (const SidTimingRow& row : SID_TIMING_ROWS){
database.add<Test_SidTiming>(row);
}
for (const StarterRow& row : STARTER_ROWS){
database.add<Test_StarterTables>(row);
}
for (const ShinyRow& row : SHINY_ROWS){
database.add<Test_IdentifyShiny>(row);
}
for (const ResetHitRow& row : RESET_HIT_ROWS){
database.add<Test_ResetHit>(row);
}
}


}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* RNG Steps Tests
*
* From: https://github.com/PokemonAutomation/
*
*/

#ifndef PokemonAutomation_PokemonFRLG_RngSteps_Tests_H
#define PokemonAutomation_PokemonFRLG_RngSteps_Tests_H

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

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


// The pure parts of the SID steps and Starter RNG attempts.
void add_tests_RngSteps(UnitTestDatabase& database);


}
}
}
#endif
Loading
Loading