Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8b656d1
FRLG story drafts 00.29-00.31: Lavender to the Silph Scope
dayfine Nov 9, 2026
9fb268c
Merge remote-tracking branch 'origin/frlg/stage8-rocktunnel' into frl…
dayfine Nov 10, 2026
5a1c770
FRLG drafts: drop the 00.29 draft for #293's row; say why 00.30-00.31…
dayfine Nov 10, 2026
cf9400f
FRLG overworld: spinner tiles, the lift's floor and the poster switch…
dayfine Nov 10, 2026
5ca3d8f
FRLG AutoStory: lift floor and poster switch steps; the Rocket Hideou…
dayfine Nov 10, 2026
cf123e7
FRLG AutoStory: use items from the bag, renew a Repel, ride the Bicycle
dayfine Nov 10, 2026
a078ce6
FRLG AutoStory: mark the sim bag's use_row override
dayfine Nov 10, 2026
620d6ce
FRLG: code health: field actions in their own file, route tests in a …
dayfine Nov 10, 2026
ce645d1
FRLG: route tests find maps without the route's internals
dayfine Nov 10, 2026
fd9f6b9
FRLG drafts: restore Lavender to Celadon as 00.30, hideout as 00.31-0…
dayfine Nov 10, 2026
8ebc21b
FRLG: script Pokemon Centers and the hideout lift; code spinners and …
dayfine Nov 10, 2026
33e902b
Merge remote-tracking branch 'origin/frlg/draft-story-29-31' into frl…
dayfine Nov 10, 2026
2162103
FRLG AutoStory 00.30: Lavender Town to Celadon City as an authored plan
dayfine Nov 10, 2026
ddb1e72
FRLG AutoStory 00.30: the story-wide tests count 00.30
dayfine Nov 10, 2026
125c19f
FRLG AutoStory 00.30: Gamer Rich in the story's trainers
dayfine Nov 10, 2026
b7c9dfa
Merge frlg/story-json into frlg/story-0030
dayfine Nov 10, 2026
c5ee094
FRLG walker: no turn tap on the Bicycle
dayfine Nov 10, 2026
234d0b5
Merge remote-tracking branch 'origin/frlg/story-0030' into frlg/bike-…
dayfine Nov 10, 2026
e615aaa
FRLG walker: keep the bike change inside the file and function limits
dayfine Nov 10, 2026
2167bbd
Merge frlg/story-json into frlg/story-0030
dayfine Nov 10, 2026
f7273a8
FRLG AutoStory: 00.30's GAME steps, use_item and lift_floor carry args
dayfine Nov 10, 2026
2f99919
FRLG walker: the Bicycle moves a tile per 100ms
dayfine Nov 10, 2026
7aff29c
FRLG AutoStory: sim test for 00.30's Route 8 walk from a start off by 3
dayfine Nov 10, 2026
87f2e85
FRLG AutoStory: 00.30 re-anchors on Route 8's walls before the door
dayfine Nov 10, 2026
83d3be7
FRLG walker: count open Bicycle legs in taps, get on with SELECT
dayfine Nov 10, 2026
0a9bb40
FRLG AutoStory: register the Bicycle in 00.30, ride_bike presses SELECT
dayfine Nov 10, 2026
388bfff
FRLG walker: code health for the Bicycle taps; register_bike's trace …
dayfine Nov 10, 2026
606d428
FRLG walker: the Bicycle tap API in its own header; linter fixes
dayfine Nov 10, 2026
b062392
FRLG AutoStory: red sim test, a capture started on the Bicycle drifts…
dayfine Nov 10, 2026
d2504dd
FRLG AutoStory: pace for a capture or grind on foot, from where the l…
dayfine Nov 10, 2026
cbf3354
FRLG AutoStory: qualify OverworldWalkSteps::BikeSelectCalls in pacing
dayfine Nov 10, 2026
6c658e9
FRLG AutoStory: the pacing search in its own header, AutoStoryPacing.h
dayfine Nov 10, 2026
acc74a7
PokemonFRLG: Split over-limit headers, drop char* for code health
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class ItemNameMatcher : public OCR::SmallDictionaryMatcher{
{"parlyz-heal", "Parlyz Heal"},
{"awakening", "Awakening"},
{"poke-ball", "Poke Ball"},
{"repel", "Repel"},
{"super-repel", "Super Repel"},
{"max-repel", "Max Repel"},
{"escape-rope", "Escape Rope"},
{"bicycle", "Bicycle"},
{"cancel", "Cancel"},
}){
JsonArray names;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ ImageFloatBox item_list_name_box(int row);
ImageFloatBox bag_item_count_box(int row);

// The item named in row `row`: a slug such as "potion", "antidote",
// "parlyz-heal", "awakening", "poke-ball", or "cancel" for the list's last row, in a
// "parlyz-heal", "awakening", "poke-ball", "repel",
// "super-repel", "max-repel", "escape-rope", "bicycle", or "cancel" for the list's last row, in a
// game in `language`; empty if it can't be read, as in a language the
// reader has no names for.
std::string read_item_list_name(Logger& logger, Language language, const ImageViewRGB32& screen, int row);
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=27 (advisory above 25, limit 50)
JsonObject english;
for (const auto& [slug, name] : {
std::pair<const char*, const char*>{"pallet-town", "Pallet Town"},
Expand All @@ -76,6 +76,7 @@
{"route-10", "Route 10"},
{"lavender-town", "Lavender Town"},
{"vermilion-city", "Vermilion City"},
{"celadon-city", "Celadon City"},
}){
JsonArray names;
names.push_back(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* Bicycle and Spinner Mechanics

Check warning on line 1 in SerialPrograms/Source/VideoGames/PokemonFRLG/Overworld/PokemonFRLG_BikeSpinMechanics.h

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

<header>: header_symbols=7 (advisory above 6, limit 8)
*
* From: https://github.com/PokemonAutomation/
*
* How FireRed and LeafGreen move the player on the Bicycle and on spinner
* tiles, as measured on the console (PokemonFRLG_MovementMechanics.h has
* walking and running).
*
*/

#ifndef PokemonAutomation_PokemonFRLG_BikeSpinMechanics_H
#define PokemonAutomation_PokemonFRLG_BikeSpinMechanics_H

#include "Common/Cpp/Time.h"

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


// Riding the Bicycle, measured on console B (2026-11-09, Lavender Town,
// east from the rock west of the Center, from rest, counted back with
// one-tile taps): holds of 450, 500, 650, 700, 900, 950, 1100, 1250,
// 1300, 1450 and 1500ms moved 5, 5, 7, 7, 9, 10, 11, 13, 13, 15 and 15
// tiles, the same each time: a tile per 100ms, so an open leg's hold of
// (n - 1) tiles and a half moves n. 112ms a tile (a wall-to-wall time
// of the motion on screen) held "west 16 (open)" for 17 or 18 tiles on
// Route 8 (00.30 run 2). Not the decompilation's 4 frames a tile (67ms):
// the bike speeds up from rest.
inline constexpr Milliseconds BIKE_TILE_TIME(100);

// On the Bicycle a tap never only turns the player: from rest, a 40ms tap
// moved a tile facing either way, and holds of 112, 150, 200, 250, 300,
// 336 and 400ms moved 1, 2, 2, 3, 3, 4 and 4 tiles (console B,
// 2026-11-09, Route 12's north pier). So a bike leg has no turn tap: its
// hold turns the player too, and loses no tile for it.
inline constexpr bool BIKE_TAP_ONLY_TURNS = false;

// Chains of 40ms taps on the Bicycle (console B, 2026-11-09): one tap per
// 100ms (60ms release) moved exactly a tile each.
inline constexpr Milliseconds BIKE_TAP_PERIOD_WORKED(100);

// At one tap per 60ms, one tap in 12 was dropped. Somewhere between that
// and BIKE_TAP_PERIOD_WORKED the game starts dropping taps; the sim drops
// taps that start sooner than this after the last one.
inline constexpr Milliseconds BIKE_TAP_DROP_PERIOD(80);

// Of the taps that come sooner than BIKE_TAP_DROP_PERIOD, the sim drops
// one in this many.
inline constexpr int BIKE_FAST_TAP_DROP_EVERY = 12;

// With the Bicycle registered (Key Items, BICYCLE, REGISTER), SELECT gets
// on and off in about 1s (console B, 2026-11-09). Going through a door
// takes the player off (not measured); a battle leaves them on.
inline constexpr Milliseconds BIKE_SELECT_TIME(1000);

// A spinner tile (Rocket Hideout, Viridian Gym) slides the player a tile
// per 8 frames (pokefirered PlayerGoSpin(): MOVE_SPEED_FAST_1). Not
// measured on the console.
inline constexpr Milliseconds SPIN_TILE_TIME(133);


} // namespace MovementMechanics
} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* Bicycle and Spinner Presses
*
* From: https://github.com/PokemonAutomation/
*
* The presses the walker sends on the Bicycle and after a slide, from
* PokemonFRLG_BikeSpinMechanics.h.
*
*/

#ifndef PokemonAutomation_PokemonFRLG_BikeSpinPresses_H
#define PokemonAutomation_PokemonFRLG_BikeSpinPresses_H

#include "Common/Cpp/Time.h"
#include "PokemonFRLG_BikeSpinMechanics.h"
#include "PokemonFRLG_MovementMechanics.h"

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


// An open leg on the Bicycle is counted out in taps, each a tile: a 40ms
// press each.
inline constexpr Milliseconds BIKE_TAP(40);

// The taps' period, start to start, slower than the chains that worked,
// so none is dropped.
inline constexpr Milliseconds BIKE_TAP_PERIOD(120);

// After a tap, how long until the screen shows whether it moved: the
// tile's 100ms and video lag. The Command File Runner's tap check judged
// its first tap too early, as unmoved. Not measured: a guess.
inline constexpr Milliseconds BIKE_TAP_SETTLE(250);

// SELECT, pressed to get on the registered Bicycle.
inline constexpr Milliseconds BIKE_SELECT_PRESS(160);

// The wait after SELECT for the player to be up on the Bicycle.
inline constexpr Milliseconds BIKE_SELECT_SETTLE(1200);

// The wait after a slide's last tile before the next press: the spin's
// last frames and the facing it leaves. Not measured.
inline constexpr Milliseconds SPIN_STOP_SETTLE(300);

static_assert(BIKE_TAP_PERIOD > BIKE_TAP_PERIOD_WORKED && BIKE_TAP_PERIOD_WORKED > BIKE_TAP_DROP_PERIOD);
static_assert(BIKE_TAP < BIKE_TILE_TIME && BIKE_TAP <= LONGEST_TAP);
static_assert(BIKE_SELECT_SETTLE > BIKE_SELECT_TIME);


} // namespace MovementMechanics
} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/* Coded Rooms and Tiles
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include <map>
#include "PokemonFRLG_CodedRooms.h"
#include "PokemonFRLG_HideoutLiftRoom.h"
#include "PokemonFRLG_PokemonCenterRoom.h"
#include "PokemonFRLG_OverworldMaps.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


namespace{

// A floor's spinner tiles, by the way each slides the player.
struct SpinnerFloor{
std::string_view map;
std::vector<TilePos> north;
std::vector<TilePos> south;
std::vector<TilePos> west;
std::vector<TilePos> east;
std::vector<TilePos> stop;
};

// Read off pokefirered: each floor's layout (data/layouts/RocketHideout_
// B2F, _B3F map.bin, primary tileset gTileset_Building, secondary
// gTileset_SilphCo) with each metatile's behaviour from the tilesets'
// metatile_attributes.bin: MB_SPIN_UP (0x56), MB_SPIN_DOWN (0x57),
// MB_SPIN_LEFT (0x55), MB_SPIN_RIGHT (0x54), MB_STOP_SPINNING (0x58).
const std::vector<SpinnerFloor>& spinner_floors(){
static const std::vector<SpinnerFloor> FLOORS{
{
"RocketHideout_B2F",
{{9, 5}, {11, 6}, {7, 7}, {11, 8}, {15, 9}, {7, 10}, {9, 10}, {3, 11}, {15, 11}, {13, 12}, {15, 13}, {9, 14}, {5, 17}, {7, 18}, {5, 19}},
{{12, 5}, {8, 9}, {10, 9}, {14, 11}, {10, 13}, {8, 17}},
{{3, 4}, {7, 4}, {9, 4}, {11, 4}, {16, 5}, {16, 6}, {12, 13}, {3, 14}, {7, 14}, {12, 14}, {12, 17}, {12, 18}},
{{3, 6}, {12, 7}, {4, 9}, {3, 10}, {10, 11}, {12, 11}, {9, 12}, {11, 12}, {9, 20}},
{{1, 4}, {7, 6}, {13, 7}, {15, 8}, {13, 10}, {8, 11}, {14, 13}, {1, 14}, {5, 15}, {10, 15}, {8, 19}, {13, 20}},
},
{
"RocketHideout_B3F",
{{8, 11}, {10, 14}, {6, 15}, {6, 17}, {4, 18}},
{{9, 10}, {7, 16}},
{{4, 9}},
{{6, 9}, {2, 11}, {6, 13}, {5, 14}, {7, 14}, {4, 15}, {3, 16}, {2, 17}},
{{2, 9}, {8, 9}, {6, 11}, {10, 13}, {9, 14}, {7, 20}},
},
};
return FLOORS;
}

const SpinnerFloor* spinner_floor(std::string_view map){
for (const SpinnerFloor& floor : spinner_floors()){
if (floor.map == map){
return &floor;
}
}
return nullptr;
}

bool contains(const std::vector<TilePos>& tiles, const TilePos& point){
return std::any_of(tiles.begin(), tiles.end(), [&](const TilePos& tile){
return tile.x == point.x && tile.y == point.y;
});
}

bool has_object(const MapData& map, std::string_view name){
return std::any_of(map.objects.begin(), map.objects.end(), [&](const MapObject& object){
return object.name == name;
});
}

MapObject object(std::string name, Pokemon::ObjectKind kind, const TilePos& tile){
MapObject out;
out.name = std::move(name);
out.kind = kind;
out.x = tile.x;
out.y = tile.y;
return out;
}

// LAYOUT_POKEMON_CENTER_1F's collision and elevation, as the map lock's
// seven Centers (PokemonSimulation 6c2f5c8, data/games/frlg/maps/runtime/
// *_PokemonCenter_1F.json) all have them. The mat's warps: (6..8, 8),
// the middle one MB_SOUTH_ARROW_WARP-like ("walk_south"); the stairs to
// 2F at (1, 6), which isn't loaded.
constexpr std::string_view CENTER_ROOM_JSON = R"({
"name": "PokemonCenter_1F",
"source": {"decomp_file": "data/layouts/PokemonCenter_1F/map.bin", "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", "date": "2026-11-09"},
"allow_running": false,
"tiles": ["#.#############", "###############", "....###..##....", "....#######....", "...............", "...............", "#..........##..", "...........##..", "...............", "###############"],
"elevation": ["030000000000000", "000000000000000", "333300033003333", "333300000003333", "333333333333333", "330333333333333", "044033333330033", "330333333330033", "333333333333333", "000000000000000"],
"points": {"warp_0": [6, 8], "warp_1": [7, 8], "warp_2": [8, 8], "nurse_front": [7, 4]},
"warps": [
{"x": 6, "y": 8, "enter": "walk_into"},
{"x": 7, "y": 8, "enter": "walk_south"},
{"x": 8, "y": 8, "enter": "walk_into"},
{"x": 1, "y": 6, "enter": "step_on"}
],
"objects": [{"name": "nurse", "kind": "npc", "x": 7, "y": 2}]
})";

// LAYOUT_ROCKET_HIDEOUT_ELEVATOR's collision (map.bin: rows 0 to 2 and 6
// walls, 3 to 5 floor), the panel's bg event at (0, 2), and warp 1 at
// (2, 5), the one MB_SOUTH_ARROW_WARP tile (warp 0 at (1, 5) has no
// behaviour, so never fires).
constexpr std::string_view LIFT_ROOM_JSON = R"({
"name": "RocketHideout_Elevator",
"source": {"decomp_file": "data/layouts/RocketHideout_Elevator/map.bin", "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", "date": "2026-11-09"},
"tiles": ["#####", "#####", "S####", ".....", ".....", ".....", "#####"],
"points": {"warp_0": [1, 5], "warp_1": [2, 5]},
"warps": [{"x": 2, "y": 5, "enter": "walk_south"}]
})";

} // namespace


std::optional<Direction> coded_spin_direction(std::string_view map, const TilePos& point){
const SpinnerFloor* floor = spinner_floor(map);
if (floor == nullptr){
return std::nullopt;
}
if (contains(floor->north, point)){
return Direction::NORTH;
}
if (contains(floor->south, point)){
return Direction::SOUTH;
}
if (contains(floor->west, point)){
return Direction::WEST;
}
if (contains(floor->east, point)){
return Direction::EAST;
}
return std::nullopt;
}

bool coded_spin_stop(std::string_view map, const TilePos& point){
const SpinnerFloor* floor = spinner_floor(map);
return floor != nullptr && contains(floor->stop, point);
}


void add_coded_objects(MapData& map){
if (map.name != GAME_CORNER_MAP){
return;
}
if (!has_object(map, "poster")){
map.objects.push_back(object("poster", Pokemon::ObjectKind::SIGN, GAME_CORNER_POSTER));
}
if (!has_object(map, "poster_stairs")){
for (const TilePos& tile : {TilePos{15, 2}, TilePos{16, 2}, TilePos{17, 2}, TilePos{16, 3}, TilePos{17, 3}}){
map.objects.push_back(object("poster_stairs", Pokemon::ObjectKind::NPC, tile));
}
}
}


bool is_pokemon_center(std::string_view map){
return map.ends_with("_PokemonCenter_1F");
}

MapData pokemon_center_room(const std::string& name, const Pokemon::WarpDestination& outside){
MapData room = parse_map(std::string(CENTER_ROOM_JSON), name);
room.name = name;
for (MapWarp& warp : room.warps){
if (warp.y == CENTER_MAT.y){
warp.to = outside;
}
}
return room;
}

std::optional<std::string> add_pokemon_center_room(std::vector<MapData>& maps, size_t town){
for (const MapWarp& warp : maps[town].warps){
if (!warp.to || !is_pokemon_center(warp.to->map)){
continue;
}
const std::string center = warp.to->map;
const std::string door = center + "_door";
maps[town].points[door] = TilePos{warp.x, warp.y};
Pokemon::WarpDestination outside{maps[town].name, door};
maps.push_back(pokemon_center_room(center, outside));
return center;
}
return std::nullopt;
}


MapData rocket_hideout_lift_room(){
return parse_map(std::string(LIFT_ROOM_JSON), std::string(LIFT_MAP));
}

const std::vector<MapPosition>& rocket_hideout_lift_doors(){
static const std::vector<MapPosition> DOORS{
{"RocketHideout_B1F", {24, 25}},
{"RocketHideout_B2F", {28, 16}},
{"RocketHideout_B4F", {20, 23}},
};
return DOORS;
}



} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
Loading
Loading