Skip to content
Draft
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
71bfb90
FRLG AutoStory 00.28-00.29: through Rock Tunnel to Lavender Town's Ce…
dayfine Nov 9, 2026
b7b7ce5
FRLG AutoStory: register the 00.28-00.29 sim tests from the Celadon ones
dayfine Nov 9, 2026
1176498
FRLG AutoStory 00.28: sim tests for wild battles every few steps in R…
dayfine Nov 9, 2026
4febace
FRLG walker: after a stop, find the player the way that walks the few…
dayfine Nov 9, 2026
a8c174d
Merge frlg/checkpoint-replay into frlg/stage8-rocktunnel (hold watche…
dayfine Nov 9, 2026
1484c5e
FRLG AutoStory: watch a warp's fade from the start of the hold onto it
dayfine Nov 9, 2026
ec9cf9f
FRLG walker: walk on after a wild battle when the next holds find the…
dayfine Nov 9, 2026
291f72d
FRLG AutoStory: run from wild battles on steps into a trainer's line;…
dayfine Nov 9, 2026
9229561
Merge remote-tracking branch 'origin/frlg/stage8-rocktunnel' into frl…
dayfine Nov 9, 2026
62aa1d4
FRLG sim: wild battles at the game's own odds; 00.28 Monte Carlo numbers
dayfine Nov 9, 2026
d5910eb
FRLG AutoStory: reach Martha's line up column 16, no one-tile step
dayfine Nov 9, 2026
d9dae01
FRLG walker: check each turn tap by the facing on screen, tap again i…
dayfine Nov 9, 2026
d40ae84
FRLG walker: turn tap tests in the walk tests; no extra public sim me…
dayfine Nov 9, 2026
0e0b2db
Merge frlg/checkpoint-replay into frlg/stage8-rocktunnel
dayfine Nov 9, 2026
75c8c85
FRLG AutoStory: test that every resource it loads is in Resources
dayfine Nov 9, 2026
2134dbb
FRLG: movement mechanics table; sim taps as measured on the console
dayfine Nov 9, 2026
2bd5874
FRLG: space step taps past the lockout
dayfine Nov 9, 2026
91b9ff7
FRLG: code health for the movement table and tap tests
dayfine Nov 9, 2026
853a6b5
FRLG AutoStory sim: sweep scene end poses, wild battles and late fade…
dayfine Nov 9, 2026
f6080f0
FRLG 00.28: honest stops in the dark cave; Martha via the corner (11,…
dayfine Nov 9, 2026
e951ea8
Merge remote-tracking branch 'origin/frlg/stage8-rocktunnel' into fix…
dayfine Nov 9, 2026
79bfa97
FRLG 00.28: dark-cave stop rule shared in the reanchor code; code health
dayfine Nov 9, 2026
30dc576
Merge frlg/checkpoint-replay into frlg/stage8-rocktunnel (wait for a …
dayfine Nov 9, 2026
5edbf42
Merge frlg/checkpoint-replay into frlg/stage8-rocktunnel (scene ends …
dayfine Nov 9, 2026
2b8b70e
FRLG: red tests for a battle won in a dark cave
dayfine Nov 9, 2026
710e5de
FRLG: a dark cave's overworld ends the battle loop's evolution wait
dayfine Nov 9, 2026
db14a25
FRLG: keep the battle loop and the sim walker within the file limit
dayfine Nov 9, 2026
a22824f
Merge frlg/checkpoint-replay into frlg/stage8-rocktunnel: a step on t…
dayfine Nov 9, 2026
413f1cb
FRLG AutoStory: wait out a battle's exit before the next tap in a tra…
dayfine Nov 9, 2026
0a1ab58
FRLG maps: adopt PokemonSimulation e52be04; spin, hole and button glyphs
dayfine Nov 10, 2026
6aa5a9b
FRLG replay: refresh the Parcel fixture for PokemonSimulation bdca47c
dayfine Nov 10, 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
2 changes: 1 addition & 1 deletion .github/workflows/cpp-ci-serial-programs-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:
uses: actions/checkout@v7
with:
repository: dayfine/PokemonSimulation
ref: 9cb12c52f5f055299e32c2a7cf43e7103c101e3e
ref: bdca47c5045797be1d2a1f0931ad6a8f15ab3225
path: StoryReplaySource

- name: Verify replay preview bytes and independently regenerate its route input
Expand Down
7 changes: 7 additions & 0 deletions SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Map Data

Check warning on line 1 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=593 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -36,6 +36,13 @@
{'2', TileKind::CLOSED_SOUTH},
{'4', TileKind::CLOSED_WEST},
{'6', TileKind::CLOSED_EAST},
{'u', TileKind::SPIN_NORTH},
{'d', TileKind::SPIN_SOUTH},
{'l', TileKind::SPIN_WEST},
{'r', TileKind::SPIN_EAST},
{'o', TileKind::STOP_SPIN},
{'H', TileKind::FALL_HOLE},
{'B', TileKind::STRENGTH_BUTTON},
};

constexpr std::pair<const char*, WarpEntry> WARP_ENTRIES[] = {
Expand Down
7 changes: 7 additions & 0 deletions SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ enum class TileKind : uint8_t{
CLOSED_SOUTH, // '2' not entered from it, nor left through it (the
CLOSED_WEST, // '4' number pad's arrows)
CLOSED_EAST, // '6'
SPIN_NORTH, // 'u' open; stepping on it slides the player north
SPIN_SOUTH, // 'd' (Rocket Hideout and Viridian Gym spin tiles)
SPIN_WEST, // 'l'
SPIN_EAST, // 'r'
STOP_SPIN, // 'o' open; where a spin's slide stops
FALL_HOLE, // 'H' open; a hole that drops the player a floor
STRENGTH_BUTTON,// 'B' open; Victory Road's switch for a boulder
};
// The tile a glyph stands for, or nothing if it isn't one.
std::optional<TileKind> tile_from_glyph(char glyph);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Map Data Tests

Check warning on line 1 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_MapData_Tests.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<file>: file_lines=550 (advisory above 500, limit 1000)
*
* From: https://github.com/PokemonAutomation/
*
Expand Down Expand Up @@ -181,7 +181,7 @@
}

UnitTestResult test_glyphs_round_trip(){
for (char glyph : std::string("#.~wv^<>DS 8246")){
for (char glyph : std::string("#.~wv^<>DS 8246udlroHB")){
const std::optional<TileKind> tile = tile_from_glyph(glyph);
if (!tile || glyph_of(*tile) != glyph){
return std::string("Glyph '") + glyph + "' does not round-trip.";
Expand All @@ -193,6 +193,27 @@
return true;
}

// PokemonSimulation's tile-legend.json glyphs for forced movement, holes and
// boulder switches, in a map's rows.
UnitTestResult test_spin_hole_and_button_glyphs_parse(){
const std::string json = R"({
"name": "SpinRoom",
"source": {"decomp_file": "f", "revision": "r", "date": "d"},
"tiles": ["udlroHB"]
})";
const MapData map = parse_map(parse_json(json), "SpinRoom.json");
constexpr TileKind EXPECTED[] = {
TileKind::SPIN_NORTH, TileKind::SPIN_SOUTH, TileKind::SPIN_WEST, TileKind::SPIN_EAST,
TileKind::STOP_SPIN, TileKind::FALL_HOLE, TileKind::STRENGTH_BUTTON,
};
for (int x = 0; x < (int)std::size(EXPECTED); x++){
if (map.tiles.at({x, 0}) != EXPECTED[x]){
return std::format("Tile ({}, 0) read '{}'.", x, glyph_of(map.tiles.at({x, 0})));
}
}
return true;
}

struct BadMap{
const char* name;
std::string json;
Expand Down Expand Up @@ -484,6 +505,10 @@
.input = {.name = "glyphs round-trip", .test = test_glyphs_round_trip},
.expectation = {.description = "converts between tile characters and enum kinds"},
},
{
.input = {.name = "spin, hole and button glyphs", .test = test_spin_hole_and_button_glyphs_parse},
.expectation = {.description = "parses u d l r o H B as the tile legend's spin, stop, hole and switch tiles"},
},
{
.input = {.name = "each error class", .test = test_each_error_class},
.expectation = {.description = "rejects invalid JSON syntax and malformed map schemas"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
: SmallDictionaryMatcher(dictionary())
{}

static JsonObject dictionary(){

Check warning on line 62 in SerialPrograms/Source/VideoGames/PokemonFRLG/Inference/Menus/PokemonFRLG_SaveCardReader.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::NintendoSwitch::PokemonFRLG::PlaceNameMatcher::dictionary(): function_lines=26 (advisory above 25, limit 50)
JsonObject english;
for (const auto& [slug, name] : {
std::pair<const char*, const char*>{"pallet-town", "Pallet Town"},
Expand All @@ -74,6 +74,7 @@
{"route-24", "Route 24"},
{"route-25", "Route 25"},
{"route-10", "Route 10"},
{"lavender-town", "Lavender Town"},
{"vermilion-city", "Vermilion City"},
}){
JsonArray names;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ ImageFloatBox save_card_title_box();

// The place on the save card: "pallet-town", "viridian-city",
// "viridian-forest", "pewter-city", "route-1" to "route-4", "cerulean-city",
// "route-10", "route-24", "route-25", "vermilion-city"; empty if it can't be read, as
// "route-10", "route-24", "route-25", "vermilion-city",
// "lavender-town"; empty if it can't be read, as
// in a language the reader has no names for.
std::string read_save_card_location(Logger& logger, Language language, const ImageViewRGB32& screen);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* Movement Mechanics
*
* From: https://github.com/PokemonAutomation/
*
* How FireRed and LeafGreen move the player for a press of the D-pad, as
* measured on the console, and the presses the walker sends because of
* it. The overworld walker (OverworldWalkSteps, AutoStory's step_once())
* presses by this table, and OverworldSim moves its player by the same
* rules.
*
* Taps measured on console B (2026-11-09, Route 10's Pokemon Center 1F,
* D-pad presses with a 700ms release, tiles counted by exact frame match
* against reference shots), 140 of 140 alike:
* - From a standstill, a tap of 16 to 133ms while the player faces
* another way only turns them, never steps (pokefirered turns in 8
* frames).
* - The same tap while they face its way takes exactly one step.
* - No tap was dropped.
* Repeated step taps (66ms each, after a turn tap and a 1000ms wait):
* - A step tap that starts within STEP_TAP_LOCKOUT of the previous step
* tap's start is ignored.
* - Taps with no release between them are one hold: after a turn, 3 of
* them took 0 or 1 steps.
* After a battle (console B, 2026-11-09 15:50, 00.28 winston_line): a
* step tap sent as flee_battle() returned, on the black's end, was lost;
* one more tap by hand stepped.
*
*/

#ifndef PokemonAutomation_PokemonFRLG_MovementMechanics_H
#define PokemonAutomation_PokemonFRLG_MovementMechanics_H

#include <cstddef>
#include "Common/Cpp/Time.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{
namespace MovementMechanics{


// The game.

// Walking: a tile per 16 frames at 60 fps (console B, 2026-11-09).
inline constexpr Milliseconds TILE_TIME(267);
// Running with B held: a tile per 8 frames (console B, 2026-11-02, video
// of run 00.11).
inline constexpr Milliseconds RUN_TILE_TIME(133);
// Surfing: a tile per 8 frames.
inline constexpr Milliseconds SURF_TILE_TIME(133);
// The longest tap measured to only turn a player facing another way, and
// to take exactly one step for one facing its way.
inline constexpr Milliseconds LONGEST_TAP(133);
// A step tap that starts sooner than this after the previous step tap
// started is ignored. Measured between 566ms (66ms taps 500ms apart: the
// 2nd of 3 ignored, 3 of 3 times) and 666ms (600 to 900ms apart: 8 of 8
// worked).
inline constexpr Milliseconds STEP_TAP_LOCKOUT(620);
// The shortest step tap spacing, start to start, measured to work.
inline constexpr Milliseconds STEP_TAP_SPACING_WORKED(666);
// After a battle's black ends, the overworld fades in and the game ignores
// the D-pad this long. Not measured: pokefirered's fade from black is 16
// frames, then the field takes input; a tap sent at once was lost.
inline constexpr Milliseconds BATTLE_EXIT_LOCKOUT(500);


// The walker's presses.

// Turns a player facing another way, without a step.
inline constexpr Milliseconds TURN_TAP(40);
// How long to wait after a turn tap for the turn to show.
inline constexpr Milliseconds TURN_SETTLE(300);
// How many taps turn_to() tries before it takes the player as turned.
inline constexpr size_t MAX_TURN_TAPS = 3;
// Turns the player to face something, short enough not to step.
inline constexpr Milliseconds FACE_HOLD(40);
// The turn animation, before an A press reaches what is ahead.
inline constexpr Milliseconds FACE_SETTLE(300);
// The release between a leg's hold and what comes next.
inline constexpr Milliseconds STEP_RELEASE(100);
// A one-step tap (step_once()).
inline constexpr Milliseconds STEP_TAP(66);
// The release after a step tap: the next one starts STEP_TAP +
// STEP_TAP_RELEASE after it, past the lockout.
inline constexpr Milliseconds STEP_TAP_RELEASE(700);

// How long the overworld must show nothing (no box, fade or battle) after
// a battle before the next tap: twice the lockout.
inline constexpr Milliseconds AFTER_BATTLE_QUIET(1000);

static_assert(TURN_TAP <= LONGEST_TAP && FACE_HOLD <= LONGEST_TAP && STEP_TAP <= LONGEST_TAP);
static_assert(STEP_TAP + STEP_TAP_RELEASE > STEP_TAP_SPACING_WORKED);
static_assert(STEP_TAP_SPACING_WORKED > STEP_TAP_LOCKOUT);
static_assert(AFTER_BATTLE_QUIET > BATTLE_EXIT_LOCKOUT);


} // namespace MovementMechanics
} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ const char* const MAP_NAMES[] = {
"Route9",
"Route10",
"Route10_PokemonCenter_1F",
// Rock Tunnel to Lavender Town's Pokemon Center.
"RockTunnel_1F",
"RockTunnel_B1F",
"LavenderTown",
"LavenderTown_PokemonCenter_1F",
// West from Lavender: Route 8, the Underground Path under Saffron and
// Route 7 to Celadon City.
"Route8",
"UndergroundPath_EastEntrance",
"UndergroundPath_EastWestTunnel",
"UndergroundPath_WestEntrance",
"Route7",
"CeladonCity",
"CeruleanCave_1F",
"CeruleanCave_2F",
"CeruleanCave_B1F",
Expand Down
Loading
Loading