Skip to content

FRLG overworld navigation MVP (collector 3) - #117

Closed
dayfine wants to merge 34 commits into
nav/story-stepsfrom
frlg/mvp
Closed

dayfine wants to merge 34 commits into
nav/story-stepsfrom
frlg/mvp

Conversation

@dayfine

@dayfine dayfine commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Depends on #122 (the shared story steps, on #114).

FireRed had no map data or route planner to walk the story with. This adds the overworld maps from Pallet Town to Pewter City (plus Cerulean City, Route 24 and Cerulean Cave for the planner tests), generated from pret/pokefirered by devtools/maps/frlg_maps.py in #114's shared Pokemon::MapData schema and built into the program from Overworld/Maps/; FRLG's walking rules on them (collision, elevation, ledges, signs, Surf, warps); the route planner on the shared leg planner, with the wall holds and leg tiles #119 uses to find the player again; and the player facing reader. The leg walker reports, for an open leg stopped at an interrupt, how far the timing puts the player (LegResult::tiles), which #119 narrows its search with. Tested with the OverworldMaps, OverworldRoute and PlayerFacingReader suites (and LegWalker) in fork CI on Mac, Windows Clang and Ubuntu; the route tests plan and replay routes on the built-in maps.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E

dayfine and others added 6 commits October 31, 2026 23:17
The script reads a map's layout, collision and events from a pokefirered checkout and writes the JSON resource file the FRLG overworld maps load, so a map is regenerated instead of edited by hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
An OverworldMap is plain data: a Grid of Tiles with their elevation, named points, warps and the flag sets a route may depend on, with free functions over it. OverworldMaps loads each map from its JSON resource file, generated by devtools/maps/frlg_maps.py, and names the maps from a new game to Brock. The parse errors are written with std::format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
plan_route() runs the shared LegPlanner over an OverworldMap with FRLG's step model (walk, surf, warp, face, ledges) and turns the legs into RouteSteps. Route costs are a map keyed by RouteCost so a program can weigh grass, water and warps. The routes from a new game to Brock are checked by the unit tests. The step text is written with std::format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
walk_legs() holds a leg for the time its tiles take, through a LegMover the game supplies, and watches a set of LegInterrupts (a battle flash, a dialog) while it does. An interrupt ends the hold, is cleared by the game, and the walker works out how many tiles were walked from the elapsed time and the interrupt's detection delay, then holds the rest. The decisions run against injectable calls so the unit tests cover them without a console. The log and error lines are written with std::format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
read_player_facing() matches the player sprite at the screen's centre against the four standing sprites, loaded from the sprite sheet in the resources, and gives the Direction the player faces, or nothing while they move. The unit tests run on screenshots of each facing and skip when their files are missing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
walk_route() runs a planned route's steps through the shared leg walker: a LegMover that holds the D-pad for a leg, and interrupts for the battle flash, a late wild battle dialog and the repel wearing off, each cleared by the FRLG routine that handles it. A face step is checked with the player facing reader. BattleFlashWatcher sees the screen flashes that start a wild battle from the brightness of recent frames. The repel log line is written with std::format.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
… the bdsp/mvp kit builds them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
# Conflicts:
#	SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp
#	SerialPrograms/Source/Pokemon/Navigation/Pokemon_Navigation_Tests.h
#	SerialPrograms/cmake/SourceFiles.cmake
dayfine added a commit that referenced this pull request Nov 1, 2026
Brings in nav/shared (#114) through #117.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
…count

Brings in nav/shared (#114) through #117 and #118, including the planner's wild-step weighting (7008f8c), which moved there from this branch's b4d1976.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
…-mvp

#119 now has nav/shared (#114) through #117 and #118, so this PR's diff shows only its own files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
LegPlanner's wild-step weighting moved from #119 to #114 (7008f8c) and merged down #117 → #118 → #119 → #120; #120's diff is PokemonFRLG only. Adds #119, #120, #121 to the tree and a file-ownership table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
frlg_maps.py --cpp writes the maps' JSON, byte for byte as --out writes the
Packages files, into a C++ file of raw string literals, so the map tests
can run in CI, which has no Packages maps. --check regenerates that file for
the maps already in it and fails if it differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine and others added 2 commits November 1, 2026 09:33
With the START menu still open, a Viridian Forest route of three legs to a
wall reported each one done; the D-pad only moved the menu's cursor
(console B, 2026-11-01). Before each step the route now fails if the START
menu, a selection menu or a message is on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
A trainer who sees the player mid-leg speaks first; the walker closed the
message as an overworld one, then saw the battle and tried to flee it
(console B, 2026-11-01, Viridian Forest). After any message on the
overworld the walker now watches 6s for a battle's dialog, and if one
shows it stops with "trainer battle" in the error, without an error
report, for the caller to fight it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine and others added 2 commits November 1, 2026 09:38
The maps the tests use become plain .json files in the repo, built into the
program by CMake, so frlg_maps.py no longer writes a C++ file (--cpp).
--check now regenerates the maps in --out and fails if any file differs;
without map names, the maps are the ones already in --out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
In 12 of the 25 maps every tile the player can walk onto is at ground level
(3); only walls, ledges, signs and doors differ, at 0. frlg_maps.py now leaves
"elevation" out of such maps (about 15 KB of 104 KB), and the loader reads a
missing grid as ground everywhere. A door is then 3 rather than 0: the
player, put on it by a warp, steps off onto ground either way. A test plans
a room and yard written both ways and gets the same routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 1, 2026
12 of the 25 maps drop their elevation grid (#117): 104 KB to 89 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
…mes from #114

# Conflicts:
#	SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.cpp
#	SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.h
#	SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker_Tests.cpp
dayfine added a commit that referenced this pull request Nov 1, 2026
The leg walker and its tests, moved here from the FRLG PRs (#117 added
it, #119 dropped the tile-at-a-time walk and added the stop hooks), so
that the shared navigation code lives in one PR. Unchanged from #119's
version: it walks one leg with a game's mover, clears interrupts on the
way, and stops an open leg at an interrupt or a leg to a wall that is
interrupted again at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
@dayfine dayfine added autostory AutoStory programs game:frlg FireRed/LeafGreen navigation Maps, planner, walker labels Nov 1, 2026
dayfine and others added 3 commits November 1, 2026 16:25
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
FRLG kept its own map types (Grid, Tile, MapWarp, MapObject,
MapConnection, MapData) and parser. They are gone: FRLG reads
Pokemon::MapData, and PokemonFRLG_OverworldMap keeps only the game's
rules on it.

- frlg_maps.py writes the shared schema: a source, the shared glyphs
  (ledges by the way they are hopped, closed edges as 8 2 4 6), and
  cave entrances, ladders and exit mats as open tiles whose warp says
  how it is entered. A warp leads to point "warp_<n>" of its map; one
  to a map that isn't loaded has no destination.
- warps_on_step(), exit_mat() and walks_out_on_arrival() read the
  tile's warp; object_at() the wander box; hide_objects() hide_flag.
- parse_map() wraps Pokemon::parse_map().

No behaviour change: per tile, every rule reads the same on the old
and new map files, and every warp arrives on the same tile. The one
wander box past a map's edge (Oak's lab) is clipped to it, and the
two cut trees standing off their maps (Route 2, Cerulean City) are
left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
The inline test maps of the map and route tests are written as
Pokemon::MapData maps: a source, no width or height, the shared glyphs
(water 'w', a closed north edge '8', south-facing ledges 'v'), points
and flag sets as objects, and the room's exit mat as an open tile with
a walk_south warp to the yard's point "warp_0". Two parse checks move
to fields the shared schema still requires or checks (tiles, a warp's
x), and the short row is the second one, since the first sets the
width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 2, 2026
…hema) into frlg/walk-wild-legs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 2, 2026
The field and corridor test maps are written as Pokemon::MapData maps
(#117 moved FRLG onto it): a source, no width or height, the field's
ledges as south-facing 'v', and the corridor's people as named npc
objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 2, 2026
#117 moved FRLG onto the shared Pokemon::MapData, whose tiles are
Pokemon::TileKind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
Moved here from #119, which uses them to find the player again after a
stopped leg: OverworldRoute.cpp/.h belong to this PR. hold_to_wall() and
leg_tiles() replay the step rules; OPEN_WILD_STOP makes the planner avoid
open legs across tall grass or water.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 2, 2026
…k-wild-legs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 2, 2026
…lk with recovery in its own file

#118's walk_overworld_route() is left as it is: the route walk that finds
the player again moves out of PokemonFRLG_OverworldWalk.cpp/.h, which
#118 owns, into walk_overworld_route_with_recovery() in the new
PokemonFRLG_OverworldRecoveryWalk.cpp/.h, built on #118's
OverworldWalkSteps. The route wall holds this PR added to
PokemonFRLG_OverworldRoute.cpp/.h now come from #117. The code is the
same; the conflict in OverworldWalk.cpp is resolved to #118's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine and others added 5 commits November 2, 2026 07:00
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
As the other image tests do, the expected result ends the file name:
<anything>_<South|North|West|East|None>, so a test is registered with its
path only. The images are renamed to match (Route24_South.png, ...).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
trainer_may_see() checks a trainer's sight from its tile, or from every
tile of its wander box, each way it looks, short of a wall. The walker uses
it to tell which legs cross a trainer's sight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 3, 2026
dayfine and others added 2 commits November 2, 2026 20:13
press() refuses a warp whose map isn't loaded as if it were a wall, so
stops_at() called the tile before Route 4's Pokemon Center escalator a
wall: "west 5 (to wall)" rode the escalator up to 2F (console B,
2026-11-02). A leg there now ends by counting tiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
…ng puts the player

A leg that stops at its first interrupt (stop_on_interrupt) used to leave
LegResult::tiles at 0, so the caller had to look for the player along the
whole leg. It now carries tiles_before_interrupt()'s estimate, which the
FRLG recovery walk (#119) narrows its search with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 3, 2026
…into frlg/walk-wild-legs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E
dayfine added a commit that referenced this pull request Nov 3, 2026
…s, or stop

After a battle or a message stopped an open leg, recovery looked for the
player along the whole leg, and a trainer's stop was placed only when the
leg crossed one tile of sight off the grass. Now:

- the stop's timing (#117's estimate) narrows the player to a tile either
  side walking, two running (walked_near());
- stop_candidates() adds each tile up to there a trainer may see, with the
  trainer standing next to the player afterwards, since a trainer's message
  comes later than the timing says; off the grass a message is the trainer's;
- plan_reanchor() finds holds to walls that end on one tile from every
  candidate, on the maps as each one stands (a walked-up trainer blocks;
  a person on the way rejects a hold), and a way back to the leg's end that
  walks the same from there for all of them. One candidate needs no holds.
  None: the route fails rather than guess;
- the tap after a leg to a wall is only logged (it can't tell a bump from a
  step).

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

dayfine commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #213 (FRLG stack consolidated into stage PRs; will be resplit for upstream).

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

Labels

autostory AutoStory programs game:frlg FireRed/LeafGreen navigation Maps, planner, walker

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant