Skip to content

FRLG overworld navigation on the shared core (Brock MVP base) - #67

Closed
dayfine wants to merge 27 commits into
nav/corefrom
frlg/brock-mvp
Closed

dayfine wants to merge 27 commits into
nav/corefrom
frlg/brock-mvp

Conversation

@dayfine

@dayfine dayfine commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Depends on #66. Includes #65's commit.

FRLG overworld navigation on the shared core: maps generated from pokefirered (the Cerulean area for now), routes planned with the shared leg planner on FRLG's step rules (doors, ladders, exit mats, map edges, Surf, elevation), the leg walker (moved here from #66 as FRLG's own, with LegMover a small abstract class instead of a MoveOnlyFunction alias, so nothing here needs #72) holding the D-pad with wild-battle, message and Max Repel interrupts, and a reader for which way the player faces. Route tests, walker tests and facing-reader tests pass; the six facing images are local only and still need to go into CommandLineTests. The same walker walked Route 24 to Mewtwo on console B before it moved onto the core, and needs that run again. This is the base for the Brock MVP, a new game to the Boulder Badge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E

dayfine and others added 5 commits October 29, 2026 17:45
The red arrow drawn over the bag's top frame once the pocket list is
scrolled down made the check fail, so open_bag_from_overworld() never
saw the bag open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Per-tile collision, elevation and the behaviours that matter for walking,
plus warps, objects and map connections, generated from the pret/pokefirered
decompilation (Cerulean City, its Pokemon Center, Route 24 and Cerulean Cave
for now). OverworldMap's step checks follow the game's collision rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
FRLG's step rules (doors, cave mouths, ladders, exit mats, map edges,
Surf, elevation) as a step model for Pokemon::plan_legs(). Tests plan
from Cerulean City to Mewtwo and check the steps replay on the maps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Compares the centre of the screen, where the camera keeps the player, with
the standing sprites from pokefirered, on the sprite's own pixels only.
Tested on LeafGreen captures on Route 24 and in Cerulean Cave.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
A D-pad mover and FRLG's interrupts for Pokemon::walk_leg(): a wild
battle seen by its opening flashes (or, late, by its screen), and a
message, optionally followed by another Max Repel. Warps are waited out by
their fade; Surf goes through its dialog. The facing at the start is read
from the screen when not given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine and others added 9 commits October 29, 2026 22:04
Brings in the callable leg mover and the split leg planner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The leg walker now takes its mover as a callable, so DpadMover becomes
hold_dpad().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
OverworldMap had 20 public methods, all reads of its tiles. It is now a
struct of the checked map, and make_overworld_map() and the tile queries
are free functions. The checks in the constructor are split into
known_terrain(), elevation_level() and add_row(). The tile rules are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
all_maps() held every map's data in one 500-line function. Each map is
now a MapText constant, and all_maps() lists them. The map data is
unchanged apart from its indentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
plan_overworld_route() was 59 lines. Converting the costs and each leg
now has a function of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Picks up the MoveOnlyFunction test fix for MSVC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Brings in fork main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
frlg_maps.py reads maps from pret/pokefirered at a pinned commit and
writes one JSON file per map for Packages' Resources/PokemonFRLG/Maps.
For the six Cerulean maps its output matches the map data compiled in so
far, field for field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The map data moves out of the source into Packages, one file per map at
Resources/PokemonFRLG/Maps/<name>.json. MapData is the map as the file
holds it, and the tile queries work on it directly; load_map() reads a
file once and names the map and field when one is wrong. Objects' name
becomes kind, as in the files.

Upstream Packages doesn't have the maps yet, so the route tests on the
Cerulean maps skip when the files are missing. A new route test plans
across two small maps given as JSON in the test, and parser tests cover a
valid map, a missing field, a field of the wrong type, a short row, an
unknown tile and broken JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
// 's' can't be entered moving north or left moving south
// 'e' can't be entered moving west or left moving east
// 'w' can't be entered moving east or left moving west
std::vector<std::string> tiles;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should define a type for this

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a Tile enum and a small Grid<T> (width, height, at(pos)); tiles and elevation are parsed into them once (c8eab23).

// What decides where the player's next step can go.
struct PlayerState{
TilePos position;
uint8_t elevation = 3;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 3

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Named it ELEVATION_GROUND with a comment on what elevation 3 means in pokefirered (3731be9).

// way to Cerulean Cave: its guard, the rival and the Rocket in Cerulean City.
const std::set<std::string>& post_game_flags();

// Where the player stands after entering Cerulean Cave from Cerulean City (1F).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these be part of the map data?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: the Cerulean Cave points and the post-game flag set now live in the map JSON as named points and flag sets, read with map_point() / map_flag_set() (e90d70b).



enum class RouteAction{
WALK, // hold `direction` for `tiles` tiles

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we do biking here?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. Biking needs a movement mode on PlayerState/RouteStep, its own tile time in the walker, and a per-tile cost; it comes with the Brock route if we bike there.

std::optional<Direction> facing;
};

struct RouteCosts{

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a map keyed by an enum? feels like it should not be a struct (type) but just a data strcuture

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now data: std::map<RouteCost, int> keyed by a cost-kind enum, with defaults (2835948).

// west mirrored.

// Standing, facing south.
const char* const SOUTH[] = {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not very readable

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sprite now loads from the decomp PNG as a resource (PlayerSprites/red_normal.png); tests use a small embedded fixture (362699b).

dayfine and others added 2 commits October 29, 2026 23:22
Review: the tile rows were strings of legend characters. The loader now
reads them once into a Grid<Tile> and a Grid<uint8_t> of elevations,
naming the row and tile it can't read, and the tile queries compare
Tile values. The one-way tiles become CLOSED_NORTH and so on, after the
edge they close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Review: PlayerState's elevation defaulted to a bare 3. The game's
elevation constants move into the map header with what each means, and
the default is ELEVATION_GROUND.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
@dayfine
dayfine added this pull request to stack #77 October 30, 2026 06:24
dayfine and others added 10 commits October 29, 2026 23:25
Review: the cave entrance, Mewtwo's tile, the PC tile and the Hall of
Fame flags were constants in code. They are now points and flag sets in
the map files, written by the generator from a short table, and
map_point() and map_flag_set() look them up by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Review: RouteCosts was a struct of one int per kind of cost. It is now a
std::map from a RouteCost enum, with the defaults in
default_route_costs(); a map that leaves a kind out gets its default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Review: the standing frames were hex strings in the source. The reader
now loads red_normal.png from Packages once and compares its opaque
pixels, as before. match_player_facing() takes the sprites, so a new test
checks the matching with made-up sprites when the sheet isn't there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The generator also writes Pallet Town through Pewter City, with side
stairs as exit mats (they warp the same way) and the story's named
points. overworld_maps() loads them, and new route tests plan from the
bedroom to Oak's Lab and from the Viridian Mart counter through Viridian
Forest to Pewter Gym. Like the Cerulean ones, they skip when Packages
doesn't have the maps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
GCC 14 warned that the array read_array() returns for a temporary
"flags" string might dangle, which the Ubuntu build treats as an
error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The screenshots and the sprite sheet aren't in CI's resources yet, so
those tests now skip instead of failing; the made-up sprites test still
checks the matching there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
LegMover was an alias for a MoveOnlyFunction; a class with one virtual
hold() (as the BDSP copy has) needs no callable wrapper, so the walker
can leave the shared core and come along with FRLG without #72.
FRLG's D-pad mover becomes DpadMover.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The core now holds only Grid and the leg planner. The leg walker, with
its tests and build lines, stays here as FRLG's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
@dayfine

dayfine commented Nov 1, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #117 (FRLG overworld navigation MVP) and its children #118, #119, #120.

@dayfine dayfine closed this Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant