diff --git a/docs/design/AutoStory-1717-review-drafts.md b/docs/design/AutoStory-1717-review-drafts.md new file mode 100644 index 0000000000..fb821023af --- /dev/null +++ b/docs/design/AutoStory-1717-review-drafts.md @@ -0,0 +1,70 @@ +# Review comment drafts for PokemonAutomation/Arduino-Source#1717 (BDSP AutoStory) + +**Not for merge, and not posted.** These are ready-to-paste drafts for the +user, who decides whether to post them, and when. Each one stands on its own +and makes no request beyond the fix. None of them proposes shared code or +asks the BDSP team to restructure anything. + +**Stale:** checked against PR head `65094d4cd` (2026-10-25); the head is now `45a206f0d` +(2026-10-29). Recheck every draft before posting. Lessons Part 1 (§1 to §7) holds the newer +findings for Butters. + +Checked against PR head `65094d4cd` (2026-10-25). Before posting, check that +the head has not moved (`gh pr view 1717 -R PokemonAutomation/Arduino-Source +--json headRefOid`), and recheck the line numbers if it has. Paths are under +`SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/`. + +Each draft is meant as an inline comment on the file and line given in its +heading. If posted as a plain PR comment instead, add the path. + +--- + +### 1. `PokemonBDSP_AutoStoryTools.cpp:245-246` (the retry with no save) + +> When `save` is false and the retry scope isn't `SELECTED_RANGE`, a failed +> attempt logs "Reloading from backup save before retry..." and calls +> `reload_backup_save`, which is still an empty TODO (lines 188-197). The +> next attempt then starts from wherever the failure left the player. Until +> the backup reload exists, resetting from HOME here as well (the `save` +> branch just above) would at least retry from a known state. + +### 2. `StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp:116` (segment end) + +> `AutoStory_Segment_08::run_segment` stops after 0821, but the registry +> (`PokemonBDSP_AutoStory.cpp:181-185`) continues to 0826. In segment mode, +> 0822-0826 (the house west of Sunyshore through the eighth gym) never run. + +### 3. `PokemonBDSP_AutoStory.cpp:155-156` (checkpoint list) + +> The checkpoint list goes from 0716 to 0718, so 0717 can't be selected as a +> start or end checkpoint in advanced mode. + +### 4. `StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp:214` (trainer id) + +> This branch matches `"route_215_skier_edward"`, but the only caller +> (`:773`) passes `"route_216_skier_edward"`, the id that `handle_battle` +> (`:242`) also uses. The 8.1 s push back after that battle never runs. + +### 5. `StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp:31` and `Segment_05.cpp:30` (badge texts) + +> In BDSP the third badge is Veilstone (Maylene) and the fifth is Hearthome +> (Fantina). Segment 04's start text says "Third Gym Badge acquired +> (Hearthome City)" and Segment 05's end text says "Fifth Gym Badge acquired +> (Veilstone City)", so the two city names are swapped. + +### 6. `Utils/PokemonBDSP_AutoStory_Battle.cpp:208, 698` (unused handlers), optional + +> `handle_battle_with_outside_help` and `handle_expert_battle` have no +> callers in the PR. Each of segments 04-09 uses its own `handle_battle`, +> keyed by trainer-id strings. Is the plan to switch the segments over to +> these? + +Draft 6 is a question rather than a bug. Leave it out if the thread is +already long. + +--- + +Left out on purpose (design opinions, not defects): the shared run-flow +core, the tile-path executor, moving the landmark alignment loop into shared +code, and the retry-limit differences from SV. Raise them only after the FRLG +pilot is running and only if they are asked about. See AutoStory.md §6. diff --git a/docs/design/AutoStory-Design.md b/docs/design/AutoStory-Design.md new file mode 100644 index 0000000000..7989acfd9a --- /dev/null +++ b/docs/design/AutoStory-Design.md @@ -0,0 +1,235 @@ +# AutoStory: design of the closed-loop pieces + +Companion to [AutoStory-Lessons.md](AutoStory-Lessons.md): that doc lists the failures, this one +describes the pieces built in response and how they fit together. Status as of 2026-10-28 +(evening): 00.04, from the catch to Dawn in Jubilife, runs unattended on console B (r79); +00.05 was stepped through by hand and rewritten on a Jubilife grid (not yet run). Each section +says what is built, where, and what is still planned. Next work: +[AutoStory-Segment01-Plan.md](AutoStory-Segment01-Plan.md), then +[AutoStory-Segment00-Finish.md](AutoStory-Segment00-Finish.md). + +The common idea: **never act on an assumption the screen could have checked.** Each piece +reads the screen, takes one step, and reads again. Durations are caps, not plans. + +``` + story segment (00.04 after-catch, 01 Roark, 02 Floaroma ...) + | | | + scripted screens overworld walking battles + (intro, menus) map -> plan -> execute fight / escape / heal + | | | + ScreenMachine TileGrid + MapSet battle helpers + + StepTrace planner, executor (starter-agnostic) + \_______________________|________________________/ + | + walk_until / screen reads + (dialog, black screen, battle menu, screen change) +``` + +## 1. Screen machine (built) + +`CommonTools/ScreenMachine/{ScreenMachine,StepTrace}` (branch `lab/console`, merged into +`bdsp/new-game-intro-v2`). Used by the BDSP new-game intro. + +- A routine is a list of `ScreenState`s: a matcher, one step to take on that screen, the + screens the step may lead to, and visit limits. States are tried in order; the last one + matches anything ("other"). +- Loop: wait for inputs to finish, read the screen (a state counts only when two frames + 150 ms apart agree), check, act. Stop after a terminal state acts. +- Checks between reads turn silent drift into an early, explained failure: + - the new screen must be one the previous state lists in `next`; + - no state may be seen more than `max_visits` times, or `max_in_a_row` in a row; + - eight steps in a row that change nothing on screen is a dead end; + - an overall timeout. +- `StepTrace` logs every recognised screen change with a numbered screenshot and every input, + and a failure message ends with the last few screens ("dialog -> keyboard -> dialog"). + This is what makes a failed run explainable from one folder. + +Why not a fixed script: a dropped A press or an extra dialog page shifts every later press of +a script. The machine does the right thing for whatever screen is up, so an extra page is just +one more "dialog" visit. + +Use it for menu-like stretches with a small set of recognisable screens. Overworld walking is +not a good fit (the screen is always "overworld"); it uses the walk loop below. + +## 2. Walking by the screen (built) + +`PokemonBDSP_AutoStory_Walk.{h,cpp}`. + +`walk_until(x, y, limit)` holds the stick in 400 ms chunks and returns why it stopped: + +| Stop | Read from | Meaning | +|---|---|---| +| ARRIVED | caller's matcher | a landmark is on screen | +| DIALOG | short dialog box | an NPC or a trainer stopped us | +| TRANSITION | black screen with no battle after it | door, building, new area | +| BLOCKED | screen change < 10 (RMSD at 192x108) for two chunks | walking into a wall | +| LIMIT | time | none of the above | + +Wild battles on the way (white flash, black screen then battle UI, battle menu) are escaped +and the walk resumes; they are counted, not returned. +`walk_to_talk` walks in 400 ms steps and presses A after each until a dialog opens, for NPCs +in tall grass, where the grass sway hides BLOCKED. `walk_expecting` fails with a screenshot if +the stop is not the expected one. + +The caller decides what a stop means (lesson 4): a wall leg accepts BLOCKED or LIMIT; a trainer +leg accepts anything but TRANSITION and then waits for the dialog. + +Known limits: 400 ms granularity is about 1.5 tiles; BLOCKED does not register in tall grass. +The probe (section 6) samples every ~100 ms instead. + +## 3. Map: tile grid and map set (built, being extended) + +`Pokemon/Navigation/Pokemon_TileGrid.{h,cpp}`, `Pokemon_MapSet.{h,cpp}`; data in +`PokemonBDSP/Programs/AutoStory/Data/` (Route 201, Sandgem Town, Route 202, `story_maps()`). + +**Source.** BDSP keeps DP's overworld layouts, so each grid is traced from Bulbapedia's DP map +(16 px tiles). Fetched maps and the tracing scripts stay out of the repo; only the ASCII grid is +committed, with a comment naming the source image. + +**Encoding.** One ASCII row per tile row, so a grid reads like the map and diffs like text: + +``` +# blocked (trees, walls, ground items) . walkable " tall grass (encounters) +v ledge, south only, hop over N NPC/trainer S sign +D door ? off the map / exit +``` + +Named tiles (catch spot, Potion giver's talk tile, exits) and trainers with facing and sight +range sit next to the grid. + +**Joining maps.** `MapSet` places each grid at an offset on one world grid, so a plan can cross +from Route 201 into Sandgem and on to Route 202. Overlaps are allowed only where both maps are +blocked; a conflict throws at start-up (and in the unit tests). `describe()` turns a world tile +back into "Route 201 (45,3)" for logs. + +## 4. Planner (built) + +`plan_tile_route(grid, from, to, costs)` returns straight legs (direction + tiles). It is a +cheapest-path search over (tile, direction) with costs per tile, per grass tile, per leg, and +a large extra for a leg that **stops in the open** rather than against a wall. + +Why the wall preference: a leg that ends at a wall lands on the same tile however long the stick +is held past it (lesson 1). A leg that ends in the open depends on timing; the planner uses one +only when nothing else reaches the goal, and the executor treats it as the leg to localise +after. + +Also built: `walk_to_wall` (where a straight walk stops, and whether it is held there), +`sight_tiles` / `legs_crossing_sight` (which legs walk into a trainer's view; reported, used to +expect a battle, not avoided, since we want the experience before Roark), `trace_legs` (the +tiles a plan visits, for tests and logs). + +## 5. Executor, localisation and calibration (built for 00.04) + +Built in `Utils/PokemonBDSP_AutoStory_RouteWalk` (`walk_route`), with `plan_robust_route` in +the planner. What it does differs from the plan below in these ways: + +- **Plans for uncertainty.** The robust planner tracks every tile we may be on (at most 5 + after the start). An open leg of n tiles may end 1 + n/5 tiles off; wall legs bring the + possible tiles back together. Offline tests check each plan the segment uses. +- **Turning is its own action.** A tap facing another way only turns, so an open leg turns + first (`route_turn`). Measured speeds: 160 ms per tile across, 240 ms up or down; an open + leg holds (n - 1/2) tiles' worth. +- **Things the map lacks.** A walk that stops early waits twice for the way to clear, then + presses A (a trainer who saw us from the side, or someone standing in the way), fights + if a battle follows, marks the tile blocked, and plans again. +- **Map changes** are checked against the location banner (OCR). +- **Every leg** saves a screenshot under `Screenshots/AutoStory/route/`, and the overlay and + log show the checkpoint's steps, the current one and the next. + +The original plan: + +The piece that turns a plan into stick input: + +- **Leg to walk.** Each leg becomes one `walk_until` with an *expected* stop: + - ends at a wall: hold for `tiles x ms_per_tile` plus a margin; BLOCKED or LIMIT both mean + "at the wall"; + - ends in the open: hold `tiles x ms_per_tile`, then localise; + - enters a trainer's sight: expect DIALOG, run the trainer battle, then re-plan from where + the trainer stopped us; + - ends at a door or an exit: expect TRANSITION. +- **Localisation.** We know where we are when: + - a wall stopped us (the stop tile is known from the grid); + - the route banner appeared (OCR of "Route 201" etc. on entering a map); + - a scripted scene started (it fixes a tile). + After any surprise (wrong stop, extra battle), walk to the nearest walls on two axes to + re-anchor, then re-plan from there. Re-planning beats replaying. +- **Calibration.** `ms_per_tile` (walking, ~267 ms in DP) and the start-up delay are measured + on the console by probing known wall distances, not guessed. Running shoes would be a second + speed; we walk. + +**Routes first, towns only when trusted.** Routes are open ground between tree walls, where the +DP layout carries over and walls make good anchors. Towns changed more in the remake (buildings, +signs, roofs), so a town grid is used for planning only once the console check (section 6) +confirms it. Until then a town is crossed with screen-driven legs: walk to a wall or fence, +then to the exit, ending on the route banner (OCR) or the transition. Each map carries a +confidence flag that the executor checks before planning across it. + +This replaces Butters' recorded legs in 00.04 (catch -> Potion giver -> Jubilife) first, then +segments 01 and 02. + +## 6. Verifying the map on the console (next) + +Before the executor trusts a grid, check it against the game: + +1. `bdsp_probe ` Lab Console command: hold the stick, sample the screen every + ~100 ms, report the time moved before the stop. +2. From the post-catch save, probe the walls in all four directions; compare with the grid's + predicted tile counts to pin the start tile and fit `ms_per_tile`. +3. Repeat at each corner of the planned route (Route 202, Sandgem, Route 201 to the Potion + giver), with a screenshot at each stop and the route banner at map joins. +4. Output: predicted vs measured tiles per probe; grid fixes where they disagree. + +Probes run on open ground, since grass sway hides the stop. + +Doubts to settle first (from tracing the DP images): Route 202's row offset against Sandgem +(the images overlap by only 8 px, so it may be off by one); Sandgem's lab sign at row 6 or 7 +and which roof rows block; Route 201's tree line sits about 3 px off the grass grid; trainer +sight range (4 tiles is a guess). + +## 7. Battles and healing (built) + +- **Move choice:** each turn the Fight menu's move names are read by OCR, with the threshold + set per button (the one under the cursor is light blue). The strongest known attack is + used, so Water Gun once Piplup learns it, whatever the starter. If no attack can be read, + the last move that is not a known status move is used: the starter's own-type attack. +- **Training:** before the Route 202 trainers, three wild battles in the catch grass. The + run walks up and down between the tree walls north and south of the grass, never in short + sideways steps (lesson 7). +- **Healing:** 00.04 first gets 10 Potions from the Poké Mart lady on Route 201, saves on + Route 202, then heals from the Bag. That means full after training, and a Potion after any + trainer that leaves the lead under half (`use_potion_first_pokemon(…, heal_below)`). The + Pokémon Center routine (lining up with the lit doorway, the nurse) is kept for later. +- Items are found by OCR of their names (Potion), not by pixel templates. +- Wild battles while walking are run from. +- Planned: the opponent's type in the move choice (Roark), and a lead that faints (the + "can't battle" party screen). + +## 8. Stepping through new ground (Lab Console) + +A Lab Console program on the console takes command files: walk until stopped +(`bdsp_walk x y ms`), presses, stick holds, detector reads and screenshots. New ground is +stepped through in batches of 5 to 10 commands (15 to 30 s), each ending with a screenshot, +and each stop and dialogue is written down before the automated version is written. This is +how the Poké Mart lady's tile, her dialogue timing, and Barry's position in the Trainers' +School were found, after whole-run attempts had failed on each. + +## 9. Evidence and repeatability (built) + +- The clock is set (2026-01-01 00:00:50) at the start of each attempt, because some landmark + matchers depend on lighting. +- A failure saves its screenshot under `Screenshots/AutoStory/failures/` before the retry + resets the game; each save prompt is saved under `Screenshots/AutoStory/saves/` (play + time, badges). +- Runs stop at the first failure and close the game (lesson 3); retries are for real + randomness, not for deterministic failures. + +## Open questions + +- Is BDSP really 1:1 with DP everywhere along the route? Section 6 answers this for the + first three maps; buildings and towns changed most in the remake. +- Can tall grass get a stop signal (e.g. the player sprite's position instead of the whole + view)? That would remove the special case in section 2. +- How much of the planner and map set is shared with FRLG (same 16 px tiles, same ledges)? + Answered 2026-10-29 in [AutoStory-Navigation-Stack.md](AutoStory-Navigation-Stack.md): the + planner and walker are shared through a per-game step model and mover; the tile legend and + `TileGrid` stay BDSP's. The BDSP MVP branch is `bdsp/floaroma-mvp`. diff --git a/docs/design/AutoStory-Lessons.md b/docs/design/AutoStory-Lessons.md new file mode 100644 index 0000000000..4ae623d20c --- /dev/null +++ b/docs/design/AutoStory-Lessons.md @@ -0,0 +1,266 @@ +# AutoStory: what broke on a real console, and the rule each break taught + +Each entry is a failure seen on a Switch while running BDSP AutoStory (Developer-Butters' +U#1717 plus our intro work, October 2026), the pattern behind it, and the rule we now follow. +Runs are the lab builds r14 to r109 on console B. The patterns repeat, so the rules matter +more than the individual fixes. + +The lessons fall into two parts. **Part 1 is for Butters**: what his recorded segments in +#1717 (PokemonAutomation/Arduino-Source#1717) need to survive another console, another +starter or another day, with the general rules behind each fix. **Part 2 is for us**: our +own code, our lab and our process. The rules in Part 1 apply to our code too. + + +# Part 1. For Butters: his segments on another console + +## 1. Timed movement drifts; stop on what the screen shows + +| Run | What happened | +|---|---| +| r27 | "Route 201 align east 2300ms" walked into the trees; the next dialogue never came. | +| r31 | Two timed grass walks ended a tile short; Tristan never saw us. | +| r33, r39 | Five timed legs to the Potion giver ended one grass row off; A faced nobody. | +| r35 | Walking 5 s into Jubilife ran past the spot where Dawn's scene starts. | + +Error from each timed leg adds up, and the same leg lands in different places from run to run. + +**Rule:** a duration is only a cap. A walk ends on a screen event: a wall (the view stops +scrolling), a dialogue, a black transition, a battle, or a landmark. Between two anchors, use +as few timed legs as possible. Walking to a wall is the cheapest anchor: hold long enough to +reach it from anywhere the previous legs could have left us, and the wall puts us in the same +place every time. (`walk_until`, `walk_to_talk` in `PokemonBDSP_AutoStory_Walk`.) + +## 2. Checks tied to one capture, one starter or one time of day + +| Run | What happened | +|---|---| +| r27 | The Potion bag search matched pixels against the author's capture. Console B is warmer (blank list 235 blue vs 253) and softer, so the Potion in row 1 failed the match. | +| r28 | The Tristan fight required "Ember" in move slot 3: Chimchar only. We had Piplup. | +| r28 | Grass-exit landmarks scored 0.41 and 0.60 after the console clock had drifted 3.5 h from the time the references were taken at. | +| r34, r36 | "Blocked" was read from screen change; in tall grass the sway keeps the screen changing. | + +**Rule:** if the screenshot plainly shows the thing a check looks for, the check is too +narrow. Fix the check, don't gather more runs. Prefer signals that survive other capture +cards and other playthroughs: OCR of names (items, moves, route banners), layout facts, +colour-normalised comparison. Never assume the starter: pick moves from what the Fight menu +shows. When an image check depends on lighting, set the clock right before it, including on +retries. + +## 3. A retry is not a fix + +Butters' checkpoint retry reloads the save and runs the same steps again. A deterministic +failure (wrong starter, wrong row, a narrow check) fails all 20 attempts. r27 spent three +attempts on the Potion check, and every r28 to r39 failure retried until we stopped it. + +**Rule:** stop at the first failure, go HOME and close the game, find the cause. Save the +screen before the retry resets it (`Screenshots/AutoStory/failures/`), and save the save prompt +at each checkpoint (`Screenshots/AutoStory/saves/`: play time, badges, party). + +## 4. Know what a stop means before reacting to it + +| Run | What happened | +|---|---| +| r30 | Walking toward Tristan read as "blocked": he had spotted us and frozen the player while he walked over. | +| r32 | Natalie spotted us, but her walk kept the screen changing, so the walk ran out its time instead. | + +**Rule:** for a trainer or a scene, the walk can end in any way short of a transition; the +dialogue decides. Walk, then wait for the dialogue with its own timeout. + +## 5. Butters' 00.04 and 00.05, checked against the game + +What his recorded version does, what went wrong with it on console B, and what replaced it. Our +changes are on the fork branch +[`bdsp/new-game-intro-v2`](https://github.com/dayfine/Arduino-Source/commits/bdsp/new-game-intro-v2), +on top of his segment 00 ([`bdsp/1717-seg00`](https://github.com/dayfine/Arduino-Source/compare/503a28fc6...bdsp/1717-seg00)). + +| His version | What happened | Now | Our commits | +|---|---|---|---| +| Every leg is a stick hold of a recorded length (00.05 alone is ~150 lines of them). | Drift, as in section 1. In r79 the 00.05 legs reached the Trainers' School, then "west 200 ms, A" faced an empty tile: Barry stands up and to the right, at the blackboard. | 00.04 walks map plans whose legs end at walls (design doc, sections 3 to 5). 00.05 is being stepped through (section 10 below). | [`1598fc2c7`](https://github.com/dayfine/Arduino-Source/commit/1598fc2c74e7cccf6356251bf35e21fdbcac5006), [`0a5311175`](https://github.com/dayfine/Arduino-Source/commit/0a5311175ec72feb0fcb326a38d7e2dc7b1ec673) | +| Tristan is fought with move slot 3 (Ember). | Chimchar only; we had Piplup (r28). | The Fight menu is read by OCR and the strongest move picked. | [`c16ee0262`](https://github.com/dayfine/Arduino-Source/commit/c16ee0262772a5519e489fb9ad360d263d07852c), [`97df88c43`](https://github.com/dayfine/Arduino-Source/commit/97df88c439845600a02d0114478bb66d4b158ad5) | +| No training, no healing before the Route 202 trainers. | A Lv5 Piplup with Pound fainted to Logan's Shinx (r72). | Three wild battles first (Piplup learns Water Gun), then Potions. | [`08a9fb7e0`](https://github.com/dayfine/Arduino-Source/commit/08a9fb7e01b6be5ee042708b89e32ca2149dca69), [`a95359311`](https://github.com/dayfine/Arduino-Source/commit/a953593111dba3d56b10cf995eb7e9c86455018d) | +| The Potion is found in the Bag by a pixel match. | Console B's colours differ; the match failed (r27). | OCR of the item name. | [`50c9d955d`](https://github.com/dayfine/Arduino-Source/commit/50c9d955d44d767d58d45834dc7e8d3a1b0e6c65) | +| The Route 201 Poké Mart lady is reached by timed legs. | Missed her by a tile (r33, r39); so did our first planned route, which had her above the row (r77). | Walk west along her grass's top row until she stops us: she stands at (43,3), and we stop at (44,3) facing her. One A per line, 1.5 s apart. | [`99715d5d6`](https://github.com/dayfine/Arduino-Source/commit/99715d5d6d3e2c373980b68123363a2b64ae1022), [`f823453e9`](https://github.com/dayfine/Arduino-Source/commit/f823453e9cea8966d20752c4da5f48f54e634d76) | +| Barry in the Trainers' School: "west 200 ms, A", then mash B. | Faced nobody (r79). | From the school door: one tile west, north until Barry stops us, then A per line. He gives the Town Map. | (not yet coded) | +| Leaving 00.04: walk north 5 s into Jubilife. | Ran past Dawn's spot (r35); with our route, walked into the wall beside the stairs (r78). | Walk north; when the wall stops us and no dialogue follows, step a tile west and try again. Next: put the stairs in the map. | [`3cef78b77`](https://github.com/dayfine/Arduino-Source/commit/3cef78b771877675c83356133d8917eb0de681c9) | +| The Pokétch president (Phase 23): timed legs, then wait for his dialogue. | He stands in the road and talks when we face him; pressing A to advance re-opened his talk each time it ended, so his first line came back again and again. | Talk once with A, then advance with B, which never starts a talk. | (stepped through; not yet coded) | +| Three coupon clowns: recorded legs, then press toward each and A until a dialogue opens. | The recorded legs ended a row off each clown; walking past a clown on the row above or below never talks. | Each clown stands still: below the Poké Mart's planters (from the south, facing north; Coupon 1), at the TV station door (from the east, facing west; Coupon 3), beside the Pokétch Company sign (from the south, facing north; Coupon 2). The default "Yes" answers both quiz questions; close "obtained" with A, the rest with B. | (stepped through; not yet coded) | +| Pokétch from the president: a landmark match, then align and talk. | — (not reached) | He is back at his first spot. Stand level with him on his east side, face west, A, then B. "Please press the R Button" means the Pokétch is ours. | (stepped through; not yet coded) | +| Into the Jubilife Pokémon Center, to the PC. | — (not reached) | The door opening is half a tile east of where the frame stops a walk north. Inside: north to the counter, about 4 tiles east, then the tile below the PC, facing north. | (stepped through; not yet coded) | +| Checkpoint retry reloads and repeats the same steps. | Deterministic failures retried up to 20 times. | Lab runs stop at the first failure (section 3). | lab scripts | + +His route did one thing right that we dropped for a while: the Poké Mart lady on Route 201 +gives **10 Potions**. With them, healing needs no trip to the Pokémon Center: heal to full +after training, and after each trainer read the HP bar in the Bag and use a Potion under half. +The run now gets the Potions first and saves on Route 202, (30,19), so later attempts skip her. + +## 6. Butters' segment 01, checked against what we found + +| His version | What we found | Rule | +|---|---|---| +| Oreburgh city, mine and gym are timed stick holds and d-pad tap counts. | The same drift as section 5; d-pad taps are one tile each only when the player already faces that way. | Walls and the map, not counts. | +| Roark in the mine: A once, then up to 30 s for his first box, B to advance. | Right to wait long and to advance with B. But A does nothing until Barry has been talked to at the gym door, which his route gets from the tour scene's mashed B. | Make the Barry talk an explicit step. | +| Gym: up 3, left 4, up 5, right 8, up 11, left 5, up 4, then A at Roark. | That path goes round both gym trainers; straight up meets both. | Worth keeping as a plan for the gym grid; the trainers' experience helps an under-levelled team, though. | +| "Barry 1 battle logic can be used here since the moves don't matter this early." | Moves matter against Roark: Pound does almost nothing to rock types, and the first two tries were lost. | Pick Water Gun by name; heal or switch before Cranidos. | + +## 7. Butters' route on console B, 2026-10-29 (r107 to r109) + +Two failures in today's runs came from #1717's own code rather than ours: + +| Run | What happened | Rule / fix | Commit | +|---|---|---|---| +| r108 | 00.01 "Failed to enter Date Change screen". | The clock pin walks the System Settings menu by position; with "Synchronize Clock via Internet" on, the date row is disabled and the walk lands on Time Zone. Turned the sync off on console B; dropping the pin is queued. | — | +| r108b | 00.04: A beside Mom started nothing. Two d-pad taps north from the door should have put us level with her; one did not move. | Same as section 1, now in the house: counted taps lose tiles. The door fixes the column, so face east and press A one row at a time going north until she answers. | `985de47c1` | + +# Part 2. For us: our code, our lab, our process + +## 8. Our own process + +- Look at the screen, then the code, before rerunning. Every run since r28 that failed in a new + way was explained by one screenshot. +- Put new code in its final module first; no copy-then-move PRs of our own code. +- Keep line endings as they are (CRLF files stay CRLF); compare `git diff --stat` with + `--ignore-cr-at-eol`. +- When a program is stuck or failing, stop it and close the game at once; don't let retries run. + +## 9. Our own mistakes in this round + +- **An optional step kept for two hours.** We spent ~15 runs making the Route 201 detour + work before asking whether the story needs it. Rule: before automating an inherited + step, write down why the story needs it; after three different failures at one stage, + question the stage, not the symptom. +- **Claims not checked against the screen.** We concluded "the save already has Potions" + from log lines of runs that had, in fact, reached the lady; r76's Bag screenshot showed an + empty Medicine pocket. Rule: an inventory or position claim needs a screenshot of it. +- **Map errors, each found from one screenshot.** Route 202's east tree wall is at column 31 + in rows 15 to 17, not 33 (r75 lost its place by three rows); the lady is in the grass row, + not above it; Jubilife's south edge is a wall with stairs, not open ground. Rule: when a + wall leg stops somewhere unexpected, compare its screenshot with the grid before changing + any timing, and add the measured stop as a wall-hit test. ([`a95359311`](https://github.com/dayfine/Arduino-Source/commit/a953593111dba3d56b10cf995eb7e9c86455018d), [`f823453e9`](https://github.com/dayfine/Arduino-Source/commit/f823453e9cea8966d20752c4da5f48f54e634d76), [`3cef78b77`](https://github.com/dayfine/Arduino-Source/commit/3cef78b771877675c83356133d8917eb0de681c9)) +- **Movement is not tile-locked.** A short tap moves about 0.77 of a tile. A left/right + wiggle for encounters rocked across one tile edge and found no battle in three minutes + (r74). Walk wall to wall instead: the catch grass's rows sit between tree walls. ([`68279ca9b`](https://github.com/dayfine/Arduino-Source/commit/68279ca9ba631a5012207ad664892a2bdf88e760)) +- **One fixed OCR cut for every button.** Move names under the cursor are dark text on a + light blue button, and status moves sit lower (no "Effective" line); a fixed cut and box + read "" for Water Gun and Growl (r73). Threshold halfway between each button's background + and its darkest pixel, and keep the tallest text band. ([`97df88c43`](https://github.com/dayfine/Arduino-Source/commit/97df88c439845600a02d0114478bb66d4b158ad5)) +- **Reading positions from roofs and heads.** In the tilted 3D view, a roof shifts against the + ground as the camera follows us, and a sprite's head rises into the row above its feet. Fixed + clowns looked like they wandered, and we pressed A diagonally next to the president, which + talks to nobody. Rule: judge rows and columns by feet and ground features (planters, curbs, + plaza edges), and stand level with an NPC before talking. +- **Dead reckoning in a town without a map.** In Jubilife, open-street "walk until stopped" legs + ran far past what was meant, and buildings hid the player; we lost our place twice. Bulbapedia's + DP map of Jubilife matches the BDSP layout (school, Center, Mart, TV station, Pokétch Company, + fountain park all where DP has them), so towns get a tile grid too, checked against the + console, with the story NPCs on it. *Later (r82):* the grid's walls in the open plaza are not + where the game has them, so it is not good enough to plan on there; see section 11. +- **Not saying what we are doing.** The overlay and the log now print each checkpoint's steps, + the current one and the next, and every status report starts with the current problem. + ([`3cd874125`](https://github.com/dayfine/Arduino-Source/commit/3cd8741250dc03f1c166e15cfc9563f5ac36add9)) + +## 10. Step through new ground before automating it + +After hours of whole-run attempts at the lady, five minutes of stepping found her: the Lab +Console takes 5 to 10 commands at a time (walk until stopped, a few presses, a screenshot), +then waits while we look. Where the map or the scene is unknown (a new town, a building, +an NPC to talk to), step through it first, write down each stop and dialogue, and only then +write the automated version from what was seen. 00.05 was done this way on 2026-10-28: Barry and the Town Map, the president, all three +coupon clowns, the Pokétch and the Center PC, about 60 batches, ending in a save at the PC +(segment 0 complete). + +## 11. Segment 01 to Oreburgh City (2026-10-28, r81 to r90) + +01.01 (Jubilife Center to Barry) and 01.02 (Route 203, Oreburgh Gate, Oreburgh City) were +stepped through by hand once, written from that ([`8dda984d5`](https://github.com/dayfine/Arduino-Source/commit/8dda984d5ca88a1d59b605a3312711162777bba7)), and then run on console B. Ten +runs, each stopped at its first failure, and one fix per failure: + +| Run | What happened | Rule / fix | Commit | +|---|---|---|---| +| r81 | West from the PC hit the end of the counter; the walk south then stopped at the stools. | The hand run had gone south first without our noticing. Write down every move of a hand run, including the ones that "did nothing". | [`da01cc799`](https://github.com/dayfine/Arduino-Source/commit/da01cc799ba92eb5723493d943414c881a4a36dc) | +| r82 | A grid leg to a wall the Jubilife map shows walked on ~19 tiles and ended at the Trainers' School. | Jubilife's plaza grid is wrong in its open areas. There, use the holds measured on the console, and end each leg with a search (walk east; at a wall, try the next row). | [`c4883f3cb`](https://github.com/dayfine/Arduino-Source/commit/c4883f3cb2e7c1ea18f1a34a1dd0f82b89d1b2f4) | +| r83 | Barry stopped us, but "only talked" was decided 0.6 s into the pause before his battle. | A trainer's talk and battle have a quiet gap with no text box: wait 4 s of overworld before calling it a talk, and for a story battle never call it one. | [`85ef91790`](https://github.com/dayfine/Arduino-Source/commit/85ef91790d74c0b6b2a9caa88c9890fa07886dfe) | +| r84 | Stuck for 4 min choosing Water Gun at 0 PP; the game refused it each turn. The user spotted the PP risk at the same time. | Read each move's PP by OCR (text = whatever differs from the button's own color, since PP text turns yellow to red); skip 0-PP moves; fail a battle still at its menu after 25 turns. | [`a5f91acde`](https://github.com/dayfine/Arduino-Source/commit/a5f91acdeff362da2e502e33b56e4c5aaf5ddd6a), [`9721801e3`](https://github.com/dayfine/Arduino-Source/commit/9721801e3f258cd49bae506434253df55528648e) | +| r85 | Barry started at 66 % with Water Gun 12/25: the nurse had not healed. Her Yes/No is on her third line, and the B after two A presses answered No. The in-battle Potion (15 % → 93 %) worked. | Count an NPC's lines before choosing how many A presses; an extra A on a later line is harmless, a B on a question is not. The run now also saves after Barry (01.01 split at him). | [`b2236ba72`](https://github.com/dayfine/Arduino-Source/commit/b2236ba72f48e09f237a2ea07b82da802c2efba9) | +| r85 | After the Hiker, a "Hidden Moves" tutorial card held the screen; it is no dialogue box, so the walk read it as a wall. | Tutorial cards close with B; press B after a scene that teaches something. | [`a9863ad8b`](https://github.com/dayfine/Arduino-Source/commit/a9863ad8b73b7941d2b0070025b2415b9913f3a3) | +| r86 | The nurse healed, but `finish_dialogue` ended during the healing jingle; her "Thank you for waiting" then stopped the walk out. | A scene with an animation in the middle needs a second wait for its last lines. | [`00c8fcbf7`](https://github.com/dayfine/Arduino-Source/commit/00c8fcbf7c1f88680ebf54a98e42001918a122a2) | +| r87 | Route 203: only one trainer stopped us, so "after two fights the next wall is the cliff" fired at the stairs between stretches. | Who stops us depends on our row, so trainer counts say nothing about where we are. | [`5345260db`](https://github.com/dayfine/Arduino-Source/commit/5345260dbb113e90cb0174a594561b3d3281bef7) | +| r88 | Reached the north-east corner by the second sign, but the row search counted blocked steps north as moves and never tried the cave's row. | The user's rule: at a wall, go north to the top wall (that pins the row), then try rows south of it one at a time. The cave is two rows below the corner. | [`22998e853`](https://github.com/dayfine/Arduino-Source/commit/22998e853caa8b8529165a33b0c5135511a869a5) | +| r89 | Got into the Gate on the first row, but the cave's fade did not read as a transition; the Hiker's talk was then taken for a trainer's, and his card stayed up. | On Route 203 only trainers start a talk, so a talk with no battle means the Hiker inside the Gate. | [`3cb2e2f78`](https://github.com/dayfine/Arduino-Source/commit/3cb2e2f78631648b132824997e9f8c757cefd657) | +| r90 | **01.02 passed**: trainer, Hiker, Curtis, Diana, the man at Oreburgh's entrance. | | | + +Also from these runs: + +- **01.02 ends too early.** The man at Oreburgh's entrance walks us to the Pokémon Center, + where Barry shows up ("Huh? There's someone there."). `finish_dialogue` returned while the + scene was walking, between lines. The next checkpoint must finish that scene first. The user + saved by hand there after r90. +- **The lab script's Stop click can miss.** r87's load step left the Lab Console running, so + AutoStory was refused as "console busy". `run_from_save.sh` now retries Stop until the + console reads free. +- **Overlay text was cut off** at the log box's width; AutoStory now wraps its overlay lines. + ([`344a050ce`](https://github.com/dayfine/Arduino-Source/commit/344a050cecc08a61a0f5b528c2c301493c2095e9)) +- **What held up:** the in-battle HP read and Potion, the PP read (29, 40, 12 → 11 in Barry's + battle), the walk out of the Center to the girls below the door, and Barry from the measured + holds plus row search. + +## 12. Oreburgh Mine, Roark's gym and on to Floaroma (2026-10-28/29, r91 to r106, then by hand) + +01.03 (city → mine → Roark) was written from the parsed mine map (`devtools/maps/levels.py`) +and run r91 to r106. After that the user asked to fight the gym as we were, gaining +experience by losing, and to reach Floaroma by hand; the console now holds a save in the +Floaroma Pokémon Center (Badge 1, play time 4:23, Piplup Lv 15, Starly Lv 13). + +| Run | What happened | Rule / fix | Commit | +|---|---|---|---| +| r91–r93 | Timed holds put us a column off the mine's stairs, sometimes left, sometimes right. | Anchor on walls the map shows (the conveyor pillars, the fence below the Mart); never count on a hold's length for a column. | `1a77905e4` | +| r95, r96 | The walk south stopped early: the hard-hat worker by the fence wanders onto the path. | A stop that a wall cannot explain may be a wandering NPC: wait and walk again before trusting it. | `1a77905e4` | +| r96 | One column off, the walk ends on the sign above the mine's stairs. | The user: being stuck is fine if we can rescue ourselves. Step one tile east off the sign's column; the stairs are open on both sides. | `b3928e6fc` | +| r97 | Blocked legs left us a row high, probing Roark's own row with his rock in between. | End a search on a wall that pins the row (the bottom wall) before probing. | `f84c3ce3c`, `2f8be46f9` | +| r98 | Right below Roark; A started his camera zoom but the first line came after 2 s. | Story NPCs zoom the camera before the first line: wait up to 8 s. | `f59923b67` | +| r99, r103 | The route back walked through the corridor's top into the hall ("unexpected transition"); later the hall and the corridor turned out to be one walk. | End the planned route on a tile a wall pins (the corner at (14,11)) and let one walk do the transitions. | `a414e333a`, `f59a16e51` | +| r101, r104 | Below Roark, A did nothing, by hand too. | **Roark only answers after Barry has sent us** ("go down to the mine and ask him"), so talk to Barry at the gym door first. Confirmed by hand: after that talk, Roark answered the first A. | `a65a02b84` | +| r105, r106 | "Passed", but the nudge before A had stepped onto the rock beside Roark and the rock's one line ("It's a rugged rock…") was taken for his. | A dialogue opening is not proof: check the story effect. Movement is by whole tiles (30, 40 and 60 ms holds all move one tile; 20 ms only turns), so there are no half-tile nudges. 01.03 now steps onto Roark's tile after the talk and fails if he is still there. | `a65a02b84` | + +By hand, after 01.03: + +- **The gym.** Walking straight up the middle meets both gym trainers (one at the entrance, + one on the second tier). Roark (Geodude 12, Onix 12, Cranidos 14) beat Piplup Lv 10 and 14 + twice; the third try at Lv 14 with a fixed move choice won and Piplup reached 15. A whiteout + costs some money and keeps experience, so losing and retrying is a cheap way to level. +- **Battle loop on the Lab Console** (`~/BDSPLab/fight_lc.sh`, with a colour check for the + four battle buttons in `~/BDSPLab/battlemenu`): Battle, A, then the move; the move list + needs ~2 s to open, or the Down presses are lost and Pound is used. A double battle needs a + third A to pick the target. Move-learn prompts need "Keep old moves" (Down, A); B alone + loops on them. +- **Screenshots lag.** After long walks a `shot` can show a frame several seconds old, which + made us think the player was stuck. Wait ~3 s before a `shot` that decides the next move. +- **Route 204** (Jubilife → Floaroma): Jubilife's north road starts ~10 tiles west of where + Route 203 enters; Rowan, Dawn and two Galactic grunts stop us there for a tag battle with + Dawn. The Ravaged Path needs Rock Smash on its rocks (A, Yes, Yes); its north exit is the + lit opening a tile west of the corridor's east end, *south* of the entrance chamber, not + across the water. North of it: up the stairs, then a row down and east along the sand, + north through the grass to the flower beds, round the twins (a double battle), and north + between the beds into Floaroma. The Center is a few tiles east of where we arrive. + +## 13. Floaroma MVP from a new game, and the lab around it (2026-10-29, r107 to r109) + +The MVP branch (`bdsp/floaroma-mvp`, fork #68) runs segments 00 and 01 from a new game on +the shared navigation core. Half of today's failures were in the lab, not in the game. + +| Run | What happened | Rule / fix | Commit | +|---|---|---|---| +| r107 | Every program refused to start: "requires video feedback". The camera opened without error but delivered no frames. | Launch lab apps through LaunchServices (`open -n`), never by running the binary from a shell: the app then inherits the shell's (the Claude app's) camera permission, which has none. `stage_launch.sh` now checks for video before anything else. | lab scripts | +| r107 | The MVP build stopped at run time on a missing template ("Unable to open file"). | The worktree's `Packages` linked to upstream's resource set (686 files); BDSP AutoStory needs `~/BDSPLab/packages` (1,467). The build copies `Resources` only when it is missing, so delete it after relinking. | r108 deployment | +| r109 | Mom answered after 1 step; then entering Sandgem the check read the banner as "andgemltown" and failed. | A check tied to an exact OCR string is too narrow (section 2): the place was right. Match banner names loosely, with a test that a different town still fails. | pending | +| r109a | The run never started: the Start click landed where the button was before the window grew to 1554×1488. `stage_run.sh` would then have clicked Start/Stop "to stop the wrong program" and started an unwatched run. | Never toggle Start/Stop blindly: stop only when the log says RUNNING. Better, don't click at all: the lab app gets a control folder (`LabControl/start`, `stop`, `state`). Clicks also take focus from the user's work. | `lab/control-folder` | + +What this round adds to the design: + +- **Confirm where we are, not just where we planned to be.** The planner plans in tiles and + the walker counts them, but nothing reads the position back; every drift so far (sections + 1, 5, 11, 12 and the Mom row in section 7) was found only when the next action failed. After each + leg, confirm with something the game shows (a wall reached, a landmark, a dialogue + answered, the facing) and replan from the confirmed tile when it differs. +- **Overworld screens belong in the screen machine.** The new-game intro runs on + `ScreenMachine`; walking, dialogue and battle interrupts should be states of the same loop, + so a dropped input shows up as an unexpected screen instead of a later timeout. diff --git a/docs/design/AutoStory-Navigation-Stack.md b/docs/design/AutoStory-Navigation-Stack.md new file mode 100644 index 0000000000..2493bbc126 --- /dev/null +++ b/docs/design/AutoStory-Navigation-Stack.md @@ -0,0 +1,159 @@ +# AutoStory: shared navigation core and the two MVP branches + +Status: plan, 2026-10-29. Replaces the T1/T2 sketch in [AutoStory.md](AutoStory.md) §5.1 and §7.7 +and answers the open question in [AutoStory-Design.md](AutoStory-Design.md) ("how much of the +planner and map set is shared with FRLG"). Evidence: the BDSP lab branch +`bdsp/new-game-intro-v2` (00.04 runs unattended; 01.05 to Floaroma written) and the FRLG lab +branch `frlg/overworld-nav` (walked Nugget Bridge to Mewtwo on console B, 2026-10-29). + +## 1. What the two games built, side by side + +| Concern | BDSP (`Pokemon/Navigation`, AutoStory utils) | FRLG (`PokemonFRLG/Overworld`) | +|---|---|---| +| Direction, tile | `GridDirection`, `TilePos` | `MapDirection`, `MapPoint` (same thing again) | +| Map | `TileGrid`, hand-traced from DP maps: blocked, walk, grass, ledge (south), NPC, sign, door, unknown | `OverworldMap`, generated from pokefirered: walls, water, one-way tiles, ledges, doors / cave mouths / ladders / exit mats, elevation, objects with flags and wander boxes | +| Several maps | `MapSet`: grids placed on one world grid; no warps | maps joined by warps and edge connections with offsets | +| Planner | `plan_tile_route` (legs, extra for stopping in the open); `plan_robust_route` (a set of possible tiles, open legs a tile off) | search over (map, tile, elevation, surfing, facing, mid-leg), extra for stopping in the open, warps, Surf | +| Walking | `walk_route`: stick holds, 160 ms per tile across, 240 ms up/down; tracks possible tiles; re-plans after a trainer | `walk_overworld_route`: D-pad holds, 267 ms per tile (133 surfing); warps; Surf | +| Interruptions | hard-wired: trainer fight callback, fail on a wild battle in an open leg, dialog, transition | `OverworldInterrupt` list: battle flash (about 300 ms after the step), battle screen, message / Repel wore off (uses a Max Repel) | +| Facing | tracked; a tap only turns a player facing another way | tracked, and read from the player sprite | +| Trainers | sight lines on the grid | none yet (the decomp has them) | + +Written twice: the direction and tile types, legs that end at a wall, the extra cost for +stopping in the open, open leg timing ((n - 1/2) tiles of hold), the turn tap, and what to do +when something stops a leg. Neither planner can serve the other game, because each is bound to +its own map class. That is the layering problem to fix: the shared code should call into the +game once per step, not own the tile legend. + +## 2. Layers + +**Shared, `Pokemon/Navigation/` (console-free):** + +- `Pokemon_Grid.h`: `Direction`, `TilePos`. +- `Pokemon_LegPlanner.h`: the cheapest legs from a start to a goal, templated on a step model. + The game supplies `press(state, direction)`: the next state, whether the step ends the leg + (warp, starting or leaving Surf), and the facing after it; and `held_at(state, direction)`: + whether holding further keeps the player on this tile (a wall, not a person or a map edge). + The planner searches, merges steps into legs, marks legs that end at a wall, and charges for + turns, leg ends in the open, grass, warps. FRLG's `press()` and BDSP's `TileGrid::step()` both + fit as they are. +- `Pokemon_RobustPlanner.h`: planning over a set of possible tiles (BDSP's + `plan_robust_route`), on the same step model. Only BDSP needs it (stick walking drifts); FRLG + walks exactly. +- `MapSet` stays: one way for a game to build a step model (maps placed side by side). FRLG's + warp graph is the other. + +**Shared, `Pokemon/Navigation/Pokemon_LegWalker.h` (templated on the controller context, like +`run_until`, so `Pokemon/` stays free of `NintendoSwitch/`):** + +- `LegMover`, per game: how to turn (tap), how to hold a direction (D-pad or stick), the time + per tile for a leg (direction, surfing, biking). +- `LegInterrupt`, per game: a watcher, how long after the step it fires, how to clear it. +- `walk_leg()`, shared: hold the leg while watching every interrupt; on one, clear it, then + hold a wall leg again, or work out how far an open leg went from when it was seen. Reports + tiles walked and whether that is exact. + +**Per game:** the map data and its generator, the step rules, timings, detectors, interrupt +handlers (trainer, wild battle, message, Repel), the facing reader, landmarks. + +## 3. State: what is on screen, and where the player is + +Two different things, kept apart: + +- **Screen state**: which screen is up (overworld, dialogue, battle, menu, bag, black). The + shared mechanism is the screen machine and step trace (`CommonTools/ScreenMachine`, already + game-neutral); the per-game part is the detectors. Menus and scripted scenes run on the + screen machine. While walking, the same detectors are the leg walker's interrupts, so the + walker does not need a screen machine of its own. Missing: an FRLG overworld detector + (overworld vs anything else without opening a menu), planned as D1. +- **Overworld state**: where the player is and what the world looks like. The pose is shared + vocabulary: map, tile, facing, movement mode (walking, surfing, biking), plus how sure we are + (one tile, or a set of possible tiles). The world state is per game but follows one pattern + in both: maps are built from data with flags applied (FRLG event flags hiding objects; BDSP + defeated trainers and smashed rocks), and the planner reads the result. The walker updates + the pose after each leg and after an interrupt; the facing reader and landmarks (BDSP) or + wall anchors (both) correct it. + +Rule: the screen is read, the pose is tracked. A pose is never inferred from elapsed time +alone except for an open leg, and then it is marked inexact until a wall or a landmark fixes +it. + +**Status 2026-10-29: the pose is planned but not yet corrected.** The planner plans in tiles and +the walker counts them; nothing reads the pose back after a leg, so a dropped tap is found +only when the next action fails (00.04 r108b: two taps to Mom's row moved one tile; see +Lessons §7). Next, in the core: after each leg, confirm the pose with an anchor the game shows +(wall, landmark, dialogue answered, facing) and replan from the confirmed tile. FRLG's +overworld screens (overworld, dialogue, battle interrupt) should also become screen-machine +states, as BDSP's intro already is. + +Review follow-ups on the core (2026-10-29, fork #66/#67): the single-method `LegMover` becomes a +`MoveOnlyFunction` (fork `common/move-only-function`); `ScreenState`'s `matches`/`act` move +from `std::function` to `MoveOnlyFunction` too; `plan_legs` is split under 50 lines; FRLG's +`OverworldMap` (20 public methods) becomes plain data plus free functions. + +## 4. Branches and PRs + +``` +upstream main + └─ nav/core shared: grid types, leg planner, leg walker, screen machine + ├─ frlg/brock-mvp FRLG: maps + step model, D-pad mover, interrupts, facing, + │ Brock route (new game -> Boulder Badge) + └─ bdsp/floaroma-mvp BDSP: TileGrid/MapSet on the planner, stick mover, interrupts, + AutoStory 00 and 01 to Floaroma, nothing else +``` + +`nav/core` (one fork PR per item, stacked): + +| ID | Contents | Tests | +|---|---|---| +| N1 | `Pokemon_Grid.h`, `Pokemon_LegPlanner.h` | a small made-up game: walls, a warp, a one-way tile, grass, open vs wall legs | +| N2 | `Pokemon_LegWalker.h` | scripted mover and interrupts: wall leg re-walked, open leg estimate, too many interrupts | +| N3 | `CommonTools/ScreenMachine` + `StepTrace` (from the BDSP lab) | none yet: the checks between reads need tests with small fake frames | + +`frlg/brock-mvp` (on N1–N3): the FRLG overworld code moves onto the core types in its final +place (no move PRs of our own code later), then the Brock route. Includes the bag fix +(fork #65) and the facing reader. The Mewtwo route stays in the lab branch as the live test of +the walker. Brock route work follows §7.4 segments 00 and 01 of [AutoStory.md](AutoStory.md). + +`bdsp/floaroma-mvp` (on N1–N3): only what AutoStory 00 and 01 need to reach Floaroma. + +- In: the BDSP maps (Route 201 to Route 204 north, Jubilife, Oreburgh Mine, Ravaged Path) as a + step model, `walk_route` on the leg walker, the trainer and wild battle interrupts, the + AutoStory program with segments 00 and 01, the detectors and readers they use, the battle, + dialogue, Pokémon Center and new-game utilities, the system keyboard detector (in + `NintendoSwitch/Inference/`, its final home), their tests. +- Out: the old Lab Console and BDSP Lab Console commands (the Command File Runner, fork #58, + replaces them in the lab), the lighting test program, the unused `walk_steps` / + `walk_until_*` stubs, anything segment 02+. +- Risk: moving `walk_route` onto the shared walker changes code that ran unattended. The + branch is not done until 00.04 runs on a console again and 01.05 reaches Floaroma. + +Fork drafts (2026-10-29): `nav/core` #66, `frlg/brock-mvp` #67, `bdsp/floaroma-mvp` #68 (on +#1717 at `45a206f0d`, branch `base/bdsp-1717`; their segments 02 to 09 kept but not in the +checkpoint list, since they start where #1717's own 01 ends). + +Follow-ups queued for `bdsp/floaroma-mvp` (2026-10-29), in order: + +1. **Drop the clock pin.** Segment 00 sets the Switch clock (2026-01-01 00:00:50) because #1717's + landmark matchers were captured at that lighting. It is used by 00.01's timed walk from the + bedroom, the starter-grass walk, the Sandgem junction alignment and finding the Poketch man in + Jubilife; segment 01 and the tile-grid walks don't need it. Replace the two landmark + alignments with tile-grid walks anchored on walls (as 00.04 does) and the 00.01 timed moves + with screen-driven walks, then remove `set_time_fast`. It also breaks runs on its own: with + "Synchronize Clock via Internet" on, the date row is disabled and the menu walk lands on Time + Zone (r108, 00.01, six failed attempts). +2. **Move `walk_route` onto the shared leg walker** (N2), then run 00.04 and 01 on the console. +3. **Trim the BDSP resources** to what segments 00 and 01 load (the AutoStory folder is 789 files, + 101 MB, mostly #1717's item icons and later-segment landmarks). + +Order: N1, then the FRLG child up to its planner tests (it already passes on the core idea), +then N2 with the FRLG walker on it (re-run the Mewtwo route), then the BDSP child. N3 can go +any time. The lab branches keep everything else and merge these branches in. + +## 5. Checks before each branch is called done + +- N1–N3: unit tests; no game code depends on anything not in the core. +- FRLG child: route tests; the Mewtwo route walked again on the console with the shared walker. +- BDSP child: offline plan tests for every segment plan; 00.04 and 01 run on the console. +- For both: `git diff --stat` against the lab branch shows only dropped lab code, no lost + game logic. diff --git a/docs/design/AutoStory-Proposal.md b/docs/design/AutoStory-Proposal.md new file mode 100644 index 0000000000..d823ad849e --- /dev/null +++ b/docs/design/AutoStory-Proposal.md @@ -0,0 +1,72 @@ +# FireRed AutoStory: proposal + +Status: draft, 2026-10-25. The full internal plan, with the evidence and line +references, is [AutoStory.md](AutoStory.md). + +## Problem + +FRLG on `main` has most of the low-level pieces an AutoStory needs: dialog, +battle and menu detectors, fly, heal, a tested wild-battle exit, and a +frame-exact tile walker inside the Elite Four farmer. Nothing plays the story, +and users have asked for it +([ComputerControl#259](https://github.com/PokemonAutomation/ComputerControl/issues/259)). +A Poke-Controller script outside this project already plays FireRed to the +Hall of Fame with the starter alone, so the route is known to be automatable. + +## What I'm building first: a pilot to Brock + +A FireRed AutoStory from a new game to the Boulder Badge (about 12 +checkpoints), solo with no trades. It changes no existing program: the +game-neutral parts (the run flow with checkpoints, retries from the last +save, start/end selection and stats; the tile-path walker; a closed-loop +screen driver) go into new shared folders from the start, with FRLG as +their only user, so nothing has to be moved later. + +About 10 PRs, each 150–500 lines, each doing one thing: + +1. Tile-path executor with unit tests, in a new `Pokemon/Navigation/`. +2. Elite Four farmer moved onto it, with no behaviour change. +3. A closed-loop screen driver (each input follows a screen read), then the new-game screens and naming keyboard on it. +4. Program shell: options, run flow, checkpoint list. +5. **New game → starter → rival battle. This is the first version users can run.** +6. Overworld detector. +7. Trainer-spotted and item-pickup watchers. +8. Resuming a walk after a battle or dialog interrupts it. +9. Trainer battle handler with unit tests. +10. **Parcel → Pokédex → Viridian Forest → Brock. This completes the pilot.** + +Detectors come one per PR, with screenshot tests in CommandLineTests. Paths +are planned offline from the pret/pokefirered decompilation (trainer +positions and sight lines) and checked in as reviewed data. + +## What I'd like reviewed first + +PRs 1–2: the tile executor's result and resume types, and moving `walk_path` +out of `PokemonFRLG_EliteFourFarmer.cpp`. They touch existing FRLG code, and +the rest of the pilot builds on them. + +## The cost, honestly + +Mysticial has said that SV AutoStory took over a year. Most of that time +went into hardware hours per checkpoint: writing a checkpoint, then tuning +it and running it again on a console until it is reliable. Shared code and +decomp-planned paths do not shorten that. So the pilot is there to measure +this cost before committing to the rest of the game (about 100 checkpoints +to the Champion). + +## Go/no-go gate + +The pilot passes if **every pilot checkpoint (new game → Boulder Badge, +about 12) succeeds 3 times in a row starting from its own save**, and this +takes **at most 3 Switch hours per checkpoint on average**, counting both +development and validation. If the pilot misses either target, I stop at +Brock. The pilot stays a working program, and I write up what it cost. If +it passes, the projection is roughly 300 hardware hours to the Champion, and +I'll post the measured numbers before continuing. + +## Later, only if the pilot is used + +- The remaining segments to the Champion, one PR per segment. +- With FRLG running on the shared run flow, I'd offer to migrate SV + AutoStory onto it (pure moves with unit tests first) and offer the same to + BDSP. None of this is a precondition for the FRLG work. diff --git a/docs/design/AutoStory-Segment00-Finish.md b/docs/design/AutoStory-Segment00-Finish.md new file mode 100644 index 0000000000..7c10810e9d --- /dev/null +++ b/docs/design/AutoStory-Segment00-Finish.md @@ -0,0 +1,42 @@ +# AutoStory segment 00: what is left (scheduled after segment 01's first pass) + +**Superseded 2026-10-29:** segment 00 now runs on the MVP branch `bdsp/floaroma-mvp` +(fork #68, on #1717 and the shared core); 00.00 to 00.03 passed unattended from a new game +(r108b) and 00.04 got past Mom (r109). Current results are in +[AutoStory-Lessons.md](AutoStory-Lessons.md) §7 and §13. Kept for the history below. + +Status 2026-10-28. Code: fork branch `bdsp/new-game-intro-v2`, worktree +`~/Projects/Arduino-Source-bdsp`, latest commit 6dbcc8e10 (00.05 on a Jubilife grid). +Testing: the `bdsp-autostory-test` skill. + +## Where it stands + +| Checkpoint | State | +|---|---| +| 00.00 to 00.03 (new game, intro, starter, Pokédex) | run unattended earlier (new-game intro on the screen machine) | +| 00.04 (Potions, training, Route 202, Dawn) | ran unattended from the Route 202 Potion save (r79) | +| 00.05 (Jubilife: Barry, president, clowns, Pokétch, Center PC) | done by hand on the console, then rewritten on a tile grid (6dbcc8e10); **not yet run** | + +The only save on console B is the end of segment 00 (at the Jubilife Center PC). There is no +00.05-start save any more. + +## Why it is deferred + +Testing 00.05 for real needs a new game through 00.00 to 00.04, which overwrites the +end-of-00 save that segment 01 starts from. Segment 01 is stepped through first from that save; +segment 00 is finished afterwards. + +## Plan + +1. **Dry run in town (no new game).** From the current save the NPCs are still there and still + talk. Walk the 00.05 legs with the Lab Console or a diagnostic entry: school door, president + (44,26), the three clowns (Mart clown at (54,23) is unverified), the Center door (51,30). + Fix the grid where the console disagrees. Story-specific dialogue lengths differ after the + story, so only walking and talk-starting are checked here. +2. **Shorten the zig-zag plans.** The school-to-president plan is about 30 legs because the + plazas have few walls to end legs on. Either accept open legs with localisation after them, + or add known stopping features (planters, curbs) to the grid as walls. +3. **New game, full segment 00, twice** (needs the user's OK at that time: it overwrites the + save). Each run must end with "Completed: 00.05" and a save at the PC; keep both runs' logs + and the save screenshots. +4. Push the branch and update the fork PR's description (2 to 5 sentences). diff --git a/docs/design/AutoStory-Segment01-Plan.md b/docs/design/AutoStory-Segment01-Plan.md new file mode 100644 index 0000000000..a74407d901 --- /dev/null +++ b/docs/design/AutoStory-Segment01-Plan.md @@ -0,0 +1,109 @@ +# AutoStory segment 01 plan: Jubilife to the Coal Badge + +**Done 2026-10-29:** 01.01 to 01.03 were built from this plan and run on the console +(r81 to r106; Lessons §11 and §12), and Floaroma was reached by hand. The MVP branch +`bdsp/floaroma-mvp` (fork #68) carries the code. Kept as the plan of record. + +Status 2026-10-28: research done, nothing built yet. Companion to +[AutoStory-Design.md](AutoStory-Design.md) (the pieces) and +[AutoStory-Lessons.md](AutoStory-Lessons.md) (what went wrong in segment 00). +Testing is done by an agent with the `bdsp-autostory-test` skill +(`~/.claude/skills/bdsp-autostory-test/SKILL.md`). + +## 1. Starting point + +- **Save on console B:** end of segment 00, at the Jubilife Pokémon Center PC (play time 1:53, + "Last saved 01/01/2026 01:00"). This is 01.01's start, so segment 01 can be stepped through + and tested **without a new game**. +- **Party:** Piplup alone, about Lv 10 to 11 after the 00.04 training and three Route 202 + trainers, knowing Water Gun. Bag: the rest of the 10 Potions, Poké Balls, no money worth + speaking of beyond the trainer prize money. +- **Help mode:** `HELP_MODE = no_outside_help`. Butters' segment 01 assumes the starter was + swapped for a **level-100 Pokémon from HOME** at that PC (his 01.01 start text: "Received + Starter back from Pokemon Home. Max leveled pokemon"). With that, every battle is one + attack. Without it we must win with a Lv 10 to 15 Piplup, so training, healing and move + choice matter here, unlike in his version. + +## 2. The story, step by step + +| # | Where | What happens | Ends when | +|---|---|---|---| +| 1 | Jubilife Center | (optional) heal at the nurse; buy Potions at the Mart next door if money allows | back on the Center's front step | +| 2 | Jubilife east exit to Route 203 | Barry stops us at the route entrance: **rival battle**, Starly Lv 7 then Turtwig Lv 9 (we chose Piplup) | Barry's dialogue ends | +| 3 | Route 203 | trainers in sight along the way: Youngster **Dallas** (Kricketot 7), Youngster **Sebastian** (Machop 7). Off-path: Youngster Michael (Bidoof 5, Zubat 5), Lass Kaitlin (Bidoof, Budew, Starly 4), Lass Madeline (Starly 5, Bidoof 5) | the Oreburgh Gate door (cave entrance, east end) | +| 4 | Oreburgh Gate 1F | a Hiker gives **HM06 Rock Smash** (dialogue only). Camper **Curtis** (Shinx 7), Picnicker **Diana** (Budew 5, Psyduck 5). Wild: Geodude 4 to 8, Zubat 6 | white-screen exit east into Oreburgh City (end of 01.01) | +| 5 | Oreburgh City | a man stops us at the entrance and takes us on a short "tour" (dialogue); Roark is not in the gym yet | mine entrance (south of the city) | +| 6 | Oreburgh Mine 1F, B1F | Worker **Colin** (Geodude 7, Onix 7), Worker **Mason** (Machop 9); Roark is at the far end of B1F, talks, smashes a rock, sends us to the gym. Wild: Geodude 5 to 10, Zubat 7 to 8, Onix 8 to 9. Items: X Defense 1F, Potion and Escape Rope B1F | back outside (walk or Escape Rope), then gym door (end of 01.02) | +| 7 | Oreburgh Gym | tiered floor with stairs (BDSP layout, not DP's); one or two gym trainers that bridges let us skip. **Roark**: Geodude 12 (Stealth Rock, Defense Curl, Rollout), Onix 12 (Stealth Rock, Rock Throw, Bind), Cranidos 14 (Headbutt, Bulldoze, Leer; Mold Breaker) | Coal Badge, standing at Roark (end of 01.03) | + +Sources: Serebii's BDSP pages for Route 203, Oreburgh Gate, Oreburgh Mine and the gym list +(Bulbapedia's text pages are behind a Cloudflare check; its image archive is not). Trainer +lists for the gym and the exact Roark location in the mine are still to confirm on the console. + +## 3. Battles: what Piplup is up against + +Our move picker uses the strongest known attack (Water Gun) every turn. Where that is wrong: + +| Fight | Problem | Plan | +|---|---|---| +| Barry's Turtwig Lv 9 | Grass resists Water: Water Gun with STAB ≈ 30 power, Pound 40 | add type effectiveness to the move picker (step 3 below); Potions under half HP | +| Roark's Geodude / Onix | Rock/Ground, Water is ×4 | Water Gun is right | +| Roark's Cranidos Lv 14 | Headbutt hurts; Mold Breaker irrelevant | Water Gun ×2; reach Lv 14 to 15 first; Potion under half | +| Wild battles | run, as in 00.04 | unchanged | + +**Levels.** Aim for Piplup Lv 14 before Roark. The trainers on the way (Barry, Dallas, +Sebastian, Curtis, Diana, Colin, Mason) give roughly 3 to 4 levels; if short, train on +Geodude in the mine (Water Gun one-shots them, good experience). + +**Losing.** A whiteout returns us to the last Center with half the money. Detect it (black +screen into a Center, or the "can't battle" party screen) and fail the attempt with a clear +message; do not continue blindly. + +## 4. Maps and grids + +Following segment 00's rule: plan routes offline on tile grids traced from Bulbapedia's DP maps, +and put NPCs/trainers found on the console onto the grid. + +| Map | Source image | State | +|---|---|---| +| Route 203 | `Sinnoh_Route_203_DP.png` (976×368, three stitched panels; check that the panel joins sit on the 16 px grid) | downloaded, to trace | +| Oreburgh Gate 1F | filename not found yet (`Oreburgh_Gate_DP.png` etc. 404) | find the file name, or trace from console screenshots | +| Oreburgh City | not found yet | same | +| Oreburgh Mine 1F, B1F | `Oreburgh_Mine_B1F_DP.png` (400×396); 1F not found | B1F downloaded | +| Oreburgh Gym | BDSP layout differs from DP; `Oreburgh_Gym_BDSP.png` is one screenshot | trace from console screenshots | + +Archive URL: `https://archives.bulbagarden.net/media/upload///`, h = md5 of the +file name. Downloads go to the session scratchpad, not the repo; only the ASCII grid is +committed. + +Butters' segment 01 (fork branch `bdsp/autostory-1717`, `Segment_01.cpp`, 2111 lines) is +mostly timed stick holds (`pbf_move_left_joystick(…, 2694ms)`) plus picture matching inside +the mine (crate and rock patches compared at shifted positions). Useful as a record of the +order of events and dialogue counts, not as code to keep: the same timed-leg style failed in +00.04 and 00.05 (Lessons §6). + +## 5. Work order + +1. **Trace Route 203** into `Data/PokemonBDSP_AutoStory_Route203.{h,cpp}`, joined to Jubilife's + east exit in a `MapSet`. Offline plan tests for Center → Barry → Gate. +2. **Step through 01.01 on the console** from the current save with the Lab Console + (batches of 5 to 10 commands with a screenshot each): Barry's trigger tile, trainers' + positions and sight, the Gate's Hiker, Curtis and Diana, the white exit. Record each stop. +3. **Type-aware move choice**: read the opponent's species name (OCR of the battle text box), + look up its types, and score moves by power × STAB × effectiveness. Unit tests on saved + battle screenshots. +4. **Write 01.01** on the grid with `walk_route`, `talk_to_npc`, `finish_dialogue` and the + trainer handling from 00.04. Save at the Oreburgh Center when it ends. +5. **Gate, city and mine grids** (from the images or console screenshots), then step through + 01.02, then write it. Decide walk-out vs Escape Rope after seeing the mine. +6. **Gym**: step through, write 01.03 with a heal at the Oreburgh Center first. +7. **Test** each checkpoint twice from its start save with the testing skill; then the whole + segment once from the end-of-00 save. + +## 6. Open questions + +- Is Barry's trigger a tile (walk-in) or a line across the route? Step 2 answers it. +- Do Route 203's trainers stop us on the shortest path, or can the plan go around the optional + ones? We want the experience, so the plan should walk into Dallas and Sebastian on purpose. +- Money: enough for Potions after Barry (prize money) or not? Read it in the Mart. +- Where exactly is Roark in B1F, and does the scene move us? diff --git a/docs/design/AutoStory.md b/docs/design/AutoStory.md new file mode 100644 index 0000000000..d14601e42e --- /dev/null +++ b/docs/design/AutoStory.md @@ -0,0 +1,887 @@ +# AutoStory: FRLG pilot first, shared run flow and 2D movement later + +Status: internal plan, revised 2026-10-25 after review. The upstream-facing +one-page summary is [AutoStory-Proposal.md](AutoStory-Proposal.md). The +review comment drafts for the BDSP PR are in +[AutoStory-1717-review-drafts.md](AutoStory-1717-review-drafts.md). Neither +file has been posted. +Sources: SV AutoStory on `main`; BDSP WIP +[PokemonAutomation/Arduino-Source#1717](https://github.com/PokemonAutomation/Arduino-Source/pull/1717) +at `65094d4cd`; SwSh WIP PokemonAutomation/Arduino-Source#1207 at `32d506c27`; +FRLG code on `main`; the [pret/pokefirered](https://github.com/pret/pokefirered) +decompilation; the [52poke walkthrough](https://wiki.52poke.com/wiki/%E9%99%84%E5%BD%95:%E5%AF%B6%E5%8F%AF%E5%A4%A2_%E7%81%AB%E7%B4%85%EF%BC%8F%E8%91%89%E7%B6%A0%E6%B5%81%E7%A8%8B). +Paths: `SV/` = `SerialPrograms/Source/VideoGames/PokemonSV/Programs/AutoStory/`, +`BDSP/` = `SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/` (PR ref), +`FRLG/` = `SerialPrograms/Source/VideoGames/PokemonFRLG/`. + +## 0. Summary + +**Problem.** AutoStory programs (play a game's story from a new save, +unattended) are being written one game at a time. SV has one on `main` +(22.4k lines). A team is building BDSP +(PokemonAutomation/Arduino-Source#1717, 32.7k lines, WIP), SwSh has an +early draft (PokemonAutomation/Arduino-Source#1207), and we want one for +FireRed. Each copies or reinvents the same two things: + +- **The run flow:** an ordered list of checkpoints, each starting from a + save, retried from that save when it fails, with start/end selection, + stats and notifications. +- **Walking on a grid** (FRLG and BDSP): following a path, and getting back + on it after a trainer, a wild Pokémon or a dialog interrupts the walk. + +**Situation.** + +- BDSP copied SV's run flow almost name for name, and the copies have + already drifted: different retry limits (SV allows 100 but runs 102, BDSP + 20), error reporting and day skip only in SV, BDSP's backup reload still a + TODO. SwSh does not use the framework at all. None of them has unit tests + of the flow. +- Neither SV, BDSP nor the existing FRLG code can tell which step of a path + it was on when something interrupted it. BDSP's later segments push the + player back by hard-coded amounts per trainer, keyed by strings; one of + those pushes never runs because the names do not match. Its earlier + segments have a good image-based landmark alignment loop that the rest of + the PR does not use. +- FRLG on `main` has most of the low-level pieces (detectors, fly, heal, + a tested wild-battle exit, a frame-exact tile walker inside the Elite Four + farmer) but nothing that plays the story. +- The pokefirered decompilation gives exact positions and sight ranges of + every trainer, so FRLG paths can be planned offline instead of tuned by + hand. +- It has been done before outside this project: a Poke-Controller script + (`FRLG_Story_Auto.py`, [junari's blog](https://junari.hatenablog.com/entry/2024/08/02/234339)) + plays FRLG to the Hall of Fame with the starter alone in about 12–14 + hours, on a GameCube Game Boy Player or a Switch, resetting to the last + save on failure. Users asked for the same here + ([ComputerControl#259](https://github.com/PokemonAutomation/ComputerControl/issues/259)); + Mysticial's reply there is that an AutoStory takes far more effort than it + looks (SV took over a year). + +**What that year was, honestly.** Most of it was hardware hours per +checkpoint: writing a checkpoint, then tuning it and rerunning it on a +console until it is reliable. Shared infrastructure does not shorten that. +Decomp-planned paths might reduce the tuning, but that is unproven. What we +can do is measure the cost per checkpoint early and stop if it is too high +(the gate in §7.7). + +**Order (revised 2026-10-25): own something before proposing changes to +anyone else's code.** Revised again 2026-10-27: new code goes in its final +home from its first PR (§0.1). Adding new files to a shared module changes no +one else's code, so "FRLG files only" bought nothing, and "write it in FRLG, +move it out later" was a plan for move-only PRs of our own code. + +1. **Phase A, the pilot: about 10 PRs, touching only new files and FRLG.** + Build a FireRed AutoStory from a new game to Brock. The run flow (S0) + and the tile-path executor (T1) are written in `Pokemon/AutoStory/` and + `Pokemon/Navigation/` from the start, used only by FRLG; SV and BDSP are + not touched. The Elite Four farmer moves onto the tile path (T2). Users + can run something at PR 5 (new game → rival battle); the pilot is + complete at PR 10 (Boulder Badge). +2. **Gate F3**, a public commitment in the proposal: every pilot checkpoint + passes 3 times in a row from its save, at an average of at most 3 Switch + hours per checkpoint, counting development and validation. Otherwise we + stop at Brock. +3. **Phase B:** the remaining segments to the Champion, one PR per segment. +4. **Phase C, any time and independent of A and B:** the BDSP bugs found + while reading #1717 become standalone review comments with no request + attached. They are drafted in the sibling file and not posted; the user + decides. +5. **Phase D, only after the Brock pilot runs and people use it:** "FRLG + runs on the shared core; I'll migrate SV and offer it to BDSP." Nothing + of ours moves: phase D changes SV's code to use the shared core (R1–R5), + and BDSP gets an offer. + +Requirements unchanged (§9): FireRed, to the Champion, solo with no trades, +shortcuts as options, the team worked out backwards from what the route +needs. + +**What reviewers should expect:** about 10 PRs to the pilot, most 150–500 +lines, in the order of §7.7, then a go/no-go decision with measured +numbers. The likely reviewers are the FRLG authors in §8, plus Mysticial +for the new shared folders. + +The rest of the doc gives the code layout (§0.1), the evidence (§2, §4), +the interfaces (§3, §5), the BDSP findings (§6), the FireRed plan and PR +sequence (§7), and who wrote the FRLG code we build on (§8). + +### 0.1 Code layout: reusable pieces and their homes (2026-10-27) + +Four AutoStory efforts (SV on `main`, BDSP #1717, SwSh #1207, our FRLG +plan) and our BBQ and BDSP lab work keep rebuilding the same pieces. SwSh +#1207 is timed input throughout (mash A for 30 s, hold the stick for 20 s, +fixed menu walks), as BDSP's segment 00 was; running BDSP's intro on a +console showed why that breaks (dropped inputs, menus opened by a stray A, +loops that never end). The pieces below are game-neutral and get their +final home in the PR that first adds them, even while one game uses them. + +| Piece | What it is | Home | First PR | +|---|---|---|---| +| Screen machine | Closed loop over known screens: each state has a detector, one step, the screens it may lead to, a visit limit and an in-a-row limit; a screen counts once two frames agree; fails on an unexpected screen, a stall (8 steps with no change) or a timeout. Replaces mash-for-N-seconds. | `CommonTools/` (templated on the controller context, like `run_until`) | BDSP new-game intro (lab), then FRLG D4 | +| Step trace | Logs each recognized screen and action, saves a screenshot when the screen changes, and fails with the last screens attached. | `CommonTools/` | same | +| Failure policy | On a failed operation: keep the error screenshot, go HOME, close the game, rethrow, so every run ends in a known state. | `NintendoSwitch/Programs/` (next to `GameEntry`) | same | +| List selection | Read the selected row, move until the target row, confirm, verify the result. Readers are per game (BDSP name list, FRLG start menu, SV menus). | routine in `CommonTools/`; readers per game | same | +| System keyboard | Detector, cursor finder, `type_on_system_keyboard`. | `NintendoSwitch/Inference/` | lab Lab Console PR | +| Lab Console | Probe and debug runner (command files, per-game detector registry, `detect name image` offline). | `NintendoSwitch/Programs/LabConsole/` | lab Lab Console PR | +| Run flow | Checkpoint list, save, retry from save, start/end range, stats (§3). | `Pokemon/AutoStory/` | fork #43–#46 / S0 | +| Tile path | Path data, interrupt and resume, landmark alignment (§5). | `Pokemon/Navigation/` (console-free) | T1 | +| Decision tests | Game steps injected as fakes (`WildBattleExitActions`, the BBQ Tera tests). | a test file per program, from its first PR | every program PR | + +Per game, deliberately not shared: detectors and templates, timings and +speeds, battle UI, menus, new-game screens, path data. + +Rules that follow (also in the PR workflow): no "copy now, move later" +step in any plan; tests go into the program's own test file in the PR that +adds them; a seam PR sets the interface later PRs need, and behaviour PRs +change policy, not signatures. + +## 1. Design from first principles + +Written before reading SV or BDSP; the rest of the doc checks it against them. + +1. **Checkpoint = story between two saves.** Precondition and postcondition + are observable states ("standing in Pewter Center, party healed"). On any + failure: reset to the last save and retry, up to a limit. +2. **Program = ordered checkpoint list.** User picks start and end; the save + file is the resume state. +3. **Checkpoints are short compositions of a small step vocabulary:** walk a + path, interact, clear dialog to a stop state, battle, menu choice, shop, + heal, fly, save. Each step names the screen it ends on and waits for it + with a timeout. +4. **Movement is the game-specific layer.** Tile games (FRLG, BDSP d-pad) can + walk by count; 3D (SV) aligns a camera to markers. They should not share + code. +5. **One battle handler** with policies (trainer: chosen move; wild: flee or + repel; prompts: evolution, move learn, faint switch; blackout: fail the + checkpoint). +6. **Game actions behind interfaces** so the runner, the path executor and + battle decisions get unit tests with scripted fakes, like + `WildBattleExitActions` (`FRLG/PokemonFRLG_Navigation.h:88-127`). + +SV and BDSP match 1–2 closely, partially do 3 (helpers, not a vocabulary), +and do 5–6 weakly (no tests of the flow; BDSP's smarter battle code is +unused). 4 is where BDSP split into two styles. + +## 2. Run flow: what exists + +| Element | SV (`main`) | BDSP #1717 | Verdict | +|---|---|---|---| +| Segment interface | `name/start_text/end_text/run_segment(env, ctx, AutoStoryOptions, AutoStoryStats&)` `SV/PokemonSV_AutoStoryTools.h:96-107` | identical `BDSP/PokemonBDSP_AutoStoryTools.h:126-138` | same | +| Checkpoint interface | same + `checkpoint_text()` `SVT.h:109-123` | no `checkpoint_text()` `BT.h:140-152` | same | +| Registries | function-local statics, public `ALL_AUTO_STORY_SEGMENT_LIST()` `SV/PokemonSV_AutoStory.cpp:146`, `..._CHECKPOINT_LIST()` `:321` | file-static `ALL_SEGMENTS()/ALL_CHECKPOINTS()` `BA.cpp:51,245` | renamed | +| Select databases | 6 near-duplicate `make_*_database` `SVA.cpp:152-198,326-372` | one `section_database(bool,bool)` `BA.cpp:257-283` | BDSP's is better | +| Index math | `.index() + (LAST_TUTORIAL+1)` `SVA.cpp:1102-1168` | `stoul(slug())` `BA.cpp:735-750` | same result | +| Tutorial split | hard-coded 9 / 20 `SVA.cpp:93-94` | segment 0, 6 checkpoints `BA.cpp:256` | same idea | +| start > end | `UserSetupError` `SVA.cpp:1304-1313` | none (loop clamps) | BDSP missing | +| Options | `{language, starter_choice, notif_status_update&, notif_error_recoverable&}` `SVT.h:89-94` | `{language, starter, notif_status_update&, help_mode, outside_help, save_mode, retry_scope}` `BT.h:111-119` | per game | +| Stats | Checkpoint / Segment / Reset `SVT.h:27-40` | same + non-atomic `m_first_run` `BT.h:50` | same keys | +| Save | `save_game_from_overworld` via `checkpoint_save` `SVT.cpp:1180` | verified save, 3 retries, blind fallback; FAKE diagnostic mode `BT.cpp:75-185` | per game | +| Reset | `reset_game(info, console, ctx)` | `reset_game_from_home(...)`, throws on false `BT.cpp:236-243` | per game | +| Day skip on retry | yes, if enabled `SVT.cpp:1443-1446` | no | SV only | +| Retry limit | `i > 100` → 102 attempts (off by one) `SVT.cpp:1436,1471` | 20, rethrows `BT.cpp:210,230` | diverged | +| Error report | at attempt 10, telemetry `SVT.cpp:1457-1469` | none | SV only | +| Post-success check | `enter_menu_from_overworld` (retry if not overworld) `SVT.cpp:1453` | none | SV only | +| Tutorial loop | separate copy `SVT.cpp:1491-1547` | — | SV only | +| `save=false` path | — | the retry loop calls `reload_backup_save` (`BT.cpp:245-246`), an empty TODO (`:188-197`), and reruns the action without resetting | bug | +| Dev harness | `test_checkpoints` / `test_code` `SVA.cpp:901-1080,1196-1291` | none | SV only | +| End of program | `GO_HOME_WHEN_DONE`, start-up checks | neither; language hard-coded English `BA.cpp:701` | BDSP missing | + +Coupling worth knowing before moving anything: + +- SV's segment list is used outside AutoStory: + `ClaimMysteryGift::run_autostory_until_pokeportal_unlocked` runs segments + 0..6 (`SV/../Farming/PokemonSV_ClaimMysteryGift.cpp:371-382`). Keep the + list and `AutoStoryOptions` exported. +- `ML/Inference/ML_YOLONavigation.cpp:20,87` includes SV AutoStoryTools: a + layering inversion to leave alone in this work, but not to copy. +- Segments construct their checkpoints inline + (`AutoStory_Checkpoint_04().run_checkpoint(...)`), so checkpoint order is + written twice: in each segment and in the registry. In BDSP this already + went wrong twice (§6). +- Code-health limits (fork #41: functions ≤50 lines, nesting ≤4, ≤7 params) + are exceeded by: SV constructor ~397 lines, `test_checkpoints` 180 lines / + 8 params / nesting 5, `test_code` 96, `on_config_value_changed` 87, + `program` 82, both retry loops (64 and 57 lines, 8 and 7 params), and every + `checkpoint_NN` (8 params). BDSP: constructor 282 (four copies of the + 14-move list), `program` 74, `save_game` 71, retry loop 51 / 8 params. Pure + moves keep these as legacy; anything new should not add more. + +## 3. Run flow: shared core + +The shared core is written in `Pokemon/AutoStory/` in the shape below +(hooks, a policy struct, fake-hook unit tests) by the first PR that needs +it: fork #46 already does this for SV, and S0 adds FRLG's hooks on top of +it. FRLG is its first user only if S0 lands before #46; either way the code +is written once, in its final place (§0.1). SV changes only in phase D, +BDSP not at all. + +### 3.1 What is shared, what is per game + +Shared, in a new `SerialPrograms/Source/Pokemon/AutoStory/` (that module has +no `NintendoSwitch/` includes today; keep it that way): + +- `run_checkpoint_with_retries(hooks, policy, checkpoint_text, action)`: the + loop only. +- `CheckpointRetryPolicy{max_attempts, report_at_attempt, ...}`. +- `AutoStoryStats` (same three keys; persisted stats depend on the strings). +- `make_index_database(list, begin, end)` and `to_global_range(...)`: the + select/slug/index math. +- Optionally `AutoStorySegmentT` / `CheckpointT<...>` so a + game declares `using AutoStory_Segment = ...`. + +Per game: Options struct, save/reset/day-skip, post-success check, the +start-up checks, the registries, and every checkpoint. + +```cpp +// Pokemon/AutoStory/Pokemon_AutoStoryRetry.h +namespace PokemonAutomation{ +namespace Pokemon{ + +struct CheckpointRetryPolicy{ + size_t max_attempts; // SV 100, BDSP 20 + bool fatal_when_greater; // SV: i > max (keeps today's off-by-one) + size_t report_at_attempt; // SV 10; SIZE_MAX = never +}; + +class CheckpointHooks{ +public: + virtual ~CheckpointHooks() = default; + virtual void log(const std::string& message) = 0; + virtual void save_checkpoint() = 0; // save, m_checkpoint++, status notification + virtual void before_attempt(size_t attempt) = 0; // SV: day skip + save on retries + virtual void after_success() = 0; // SV: confirm overworld; else no-op + virtual void report_recoverable(const std::string& message) = 0; + [[noreturn]] virtual void fail(const OperationFailedException& e, size_t max_attempts) = 0; + virtual void reset_for_retry() = 0; // reset, m_reset++, update stats +}; + +void run_checkpoint_with_retries( + CheckpointHooks& hooks, + const CheckpointRetryPolicy& policy, + const std::string& checkpoint_text, + const std::function& action +); + +} +} +``` + +SV keeps `checkpoint_reattempt_loop(...)` and `..._tutorial(...)` with their +current signatures as thin wrappers that build `SVCheckpointHooks` / +`SVTutorialCheckpointHooks`, so none of the ~102 call sites in 40 segment +files change. + +### 3.2 Behaviours the move must keep (SV) + +- Save on attempt 0 happens inside the `try`; if the save throws, later + attempts do not save again. +- `i > max_attempts` fires the fatal error (102 attempts run). Fix later in + its own behaviour-change PR, not in the move. +- Recoverable report exactly once at attempt 10. +- `wait_for_all_requests()` before the action and before the reset. +- `after_success` throwing (the overworld check) counts as a failed attempt. +- Each loop's own fatal message text. + +### 3.3 Phase D PR sequence (after the pilot is in use; SV only, no behaviour change) + +P1–P5 here are R1–R5 in §7.7. + +| # | PR | What moves | Risk | Review | +|---|---|---|---|---| +| P1 | Shared select databases | 6 `make_*_database` → `make_index_database`; same slugs/labels | low | `--color-moved` | +| P2 | Range math | `get_*_index` arithmetic → pure `to_global_range` + tests | low | tests | +| P3 | Shared stats | `AutoStoryStats` → `Pokemon/AutoStory`; alias in `SVT.h` | low (keys) | pure move | +| P4 | Retry core | hooks + `run_checkpoint_with_retries`; SV loops become adapters; fake-hook tests | medium | loop body moved, adapters new | +| P5 | Dev harness out of the program class | `test_checkpoints` / `test_code` → `PokemonSV_AutoStoryDevTests.cpp`; delete the dead 106-line comment block | low | `--color-moved` | +| P6 (opt.) | Templated segment/checkpoint base | aliases in SV; no segment file changes | low | small | + +Tests enabled by P2/P4 (fake hooks recording steps as strings, registered +through a new `Pokemon::add_tests_AutoStory` in +`ComputerPrograms/UnitTestRunner.cpp`, or in `PokemonSV_Tests.cpp` first): +first-try success sequence; save only on attempt 0 (also when the save +throws); day skip only on retries; report exactly once at 10; SV fatal at 101 +vs BDSP rethrow after 20; reset exception propagates; non-`OperationFailed` +exceptions are not caught; `after_success` failure retries; range math for +SV (10/21 offsets) and BDSP (1/6); slugs equal global indices. + +Separate behaviour-change PRs after the move, each titled as such: fix the +off-by-one; the BDSP-style single `section_database` if P1 did not already +get there; registry-driven segments (a segment = a range of the checkpoint +registry, removing the duplicated order). + +## 4. 2D movement: what exists + +### 4.1 Primitives + +| | FRLG (`main`) | BDSP #1717 | +|---|---|---| +| Grid walker | `WalkSegment{DpadPosition, steps}`, `walk_path` in `FRLG/Programs/Farming/PokemonFRLG_EliteFourFarmer.cpp:395-449`: decomp timings `FRAME_MS=16.74`, `TURN_FRAMES=8`, `STEP_FRAMES=16`; one continuous hold, direction switched mid last step (±8 frames tolerance) | `repeat_dpad` `BDSP/PokemonBDSP_AutoStoryTools.cpp:327-343`: one tap per tile, an extra tap when turning; 211 uses | +| Elsewhere | millisecond holds: `StarterRng.cpp:224-230,413-449`, `PickupFarmer.cpp:142-167`, `EvTrainer.cpp:206-277`, `BlindNavigation.cpp:414-477` (RNG timing budgets), 33 ms turn taps in `grass_spin` `Navigation.cpp:1571-1592` | joystick holds dominate (Seg03: 328 holds, 0 `repeat_dpad`); tile counts only in comments (`1600ms // 8`, `8100ms // 27++ (snow)`) | +| Facing | passed in by caller; not detected | `DpadState::last_dir` | +| Doors | `walk_through_door` (:451): path + 300 ms under `BlackScreenOverWatcher`; `check_room` floor colour | `BlackScreenOverWatcher` everywhere; `walk_until_doorway` `Segment_01.cpp:85-187` (black screen then battle menu = wild battle, else doorway) | +| Arrival | the transition only | transition, or landmark match (00–03) | +| Executor stubs | — | `Utils/PokemonBDSP_AutoStory_Navigation.cpp:19-52`: `walk_steps`, `walk_until_dialog`, `walk_until_battle` are TODO | + +FRLG screen geometry: `GAME_BOX` (`FRLG/PokemonFRLG_Settings.cpp:110-115`) +is exactly 6.5× of 240×160 at 1080p; one tile = 104 px; the view is 15×10 +tiles with the player in the centre tile. + +### 4.2 Interruptions + +- **FRLG:** no walking code watches for trainers, wild battles, NPC dialog or + items (the E4 paths are safe only because the League has none). + `exit_wild_battle` (`Navigation.cpp:576-660`) is a good, tested state + machine for leaving a battle, but nothing feeds position back into a path. +- **BDSP:** trainer spotting is watched with `MarkDetector` ("!"), then + battle, then `move_back_after_unexpected_battle(trainer_id)`, a per-segment + if-chain of hard-coded pushes (Seg04:188, Seg05:185, Seg06:138, Seg07:203, + Seg08:134, Seg09:66). `handle_unexpected_battle` (`Utils/..._Battle.cpp:73-100`) + re-runs the whole action after a battle instead of resuming. + `recalibrate_after_battle` and `handle_unexpected_battle` are never called + by segments. Wild battles are mostly avoided with Repel; Repel expiry is a + `ShortDialogWatcher` interrupt (Seg07:687-698). +- **Neither** reports "which step and tile was I on", handles item pickups or + generic NPC dialog during a walk, or re-localizes other than by hard-coded + pushes or a reset. + +### 4.3 Landmark alignment (BDSP 00–03) + +`match_landmarks` (`Utils/PokemonBDSP_OverworldMatchers.cpp:46-126`): +downscale to 960×540 grey (optional local-contrast or blue-fraction +features), `TM_CCOEFF_NORMED` per landmark in a search box, margin over the +best competitor outside an exclusion radius, multiple landmarks must agree +within ~1/64 × 1/54 of the screen; fine pass then a coarse pass that can +never declare "at target". Lighting variants per hour count as one landmark +(`match_lighting_pair` :309-340), with a lighting sweep test program. + +Control loop (`align_to_route204_floaroma_turn`, `Segment_02.cpp:406-463`): +snapshot → if dialog or battle, return interrupted → match → 3 consecutive +at-target hits with 150 ms waits = done → else one-axis joystick nudge of 40 +or 100 ms, 250 ms settle → "blocked" if dx moved < 1/384 over 3 moves, switch +axis → throw after 12 frames lost → give up after 60. + +This is the strongest movement code in the PR. It also fits FRLG better than +BDSP: no lighting changes, exact pixels, and an offset in game-box fractions +converts directly to whole tiles (`round(dx·15)`, `round(dy·10)`), so FRLG +alignment becomes "measure → emit a `{dir, n}` path → re-measure" instead of +nudging. Sample only while standing still (the view scrolls during a step); +avoid animated tiles; Flash-dark caves are the exception. + +## 5. 2D movement: shared design + +**Superseded 2026-10-29** by [AutoStory-Navigation-Stack.md](AutoStory-Navigation-Stack.md): a +shared core (`nav/core`: grid types, a leg planner over a per-game step model, a leg walker +with per-game movers and interrupts, the screen machine) with two children, `frlg/brock-mvp` +and `bdsp/floaroma-mvp`. The sketch below is kept for its reasoning (§5.2 still holds). + +### 5.1 Pieces + +Console-free. It lives in `SerialPrograms/Source/Pokemon/Navigation/` +from T1 on (precedent: `Pokemon/Pokemon_AdvRng.h` is shared by FRLG and +RSE), with FRLG's adapter in the FRLG module. BDSP can adopt it later +without anything moving. + +```cpp +namespace PokemonAutomation{ +namespace Pokemon{ + +enum class TileDirection{ UP, DOWN, LEFT, RIGHT }; + +struct TileStep{ + TileDirection direction; + uint16_t tiles; + bool ends_in_warp = false; // door/stairs: expect a fade at the end + std::optional trainer_stops_at; // sight line triggers after this many tiles +}; +using TilePath = std::vector; + +enum class WalkInterrupt{ NONE, WILD_BATTLE, TRAINER_BATTLE, DIALOG, ITEM, EARLY_WARP, NO_WARP, BLOCKED }; + +struct StepOutcome{ + WalkInterrupt reason = WalkInterrupt::NONE; + std::chrono::milliseconds walked{0}; // input start -> detection frame time +}; + +struct PathRunResult{ + bool completed = false; + WalkInterrupt last_reason = WalkInterrupt::NONE; + size_t step = 0; // where it stopped + uint16_t tiles_done_in_step = 0; + bool tiles_exact = false; + size_t interrupts = 0; +}; + +class TileMovementActions{ +public: + virtual ~TileMovementActions() = default; + virtual StepOutcome walk(std::optional facing, const TileStep& step) = 0; + virtual uint16_t estimate_tiles(const TileStep& step, std::chrono::milliseconds walked) const = 0; + virtual bool resolve(WalkInterrupt reason) = 0; // battle/dialog handled, back in overworld + virtual std::optional relocalize(const TileStep& step) = 0; // landmark fix, if any +}; + +class InterruptPolicy{ +public: + enum class Decision{ RESUME, RESTART_PATH, ABORT }; + virtual ~InterruptPolicy() = default; + virtual Decision on_interrupt(const PathRunResult& state) = 0; +}; + +PathRunResult run_tile_path( + Logger& logger, + TileMovementActions& actions, + InterruptPolicy& policy, + std::optional facing, + const TilePath& path, + size_t max_interrupts = 10 +); + +// Alignment: BDSP nudges the joystick, FRLG emits whole tiles. +struct LandmarkFix{ bool located = false, at_target = false, coarse = false; double dx = 0, dy = 0; }; +class AlignActions{ +public: + virtual ~AlignActions() = default; + virtual std::optional observe() = 0; // nullopt = interrupted + virtual void move_toward(double dx, double dy) = 0; + virtual void settle() = 0; +}; +struct AlignConfig{ size_t max_attempts = 60, confirmations = 3, max_missing = 12; double blocked_epsilon = 1.0/384; }; +enum class AlignResult{ ALIGNED, INTERRUPTED, LOST, DID_NOT_CONVERGE }; +AlignResult align_to_landmark(Logger& logger, AlignActions& actions, const AlignConfig& config); + +} +} +``` + +Per game (adapters in the game module, so dependencies point +VideoGames → Pokemon): frame timings and speeds (walk, run, bike, surf, +snow), how a step is sent (FRLG one continuous d-pad hold; BDSP d-pad taps), +detectors (black screen box, battle start, dialog, "!" mark, item jingle), +landmark matchers and templates, field moves, battle resolution. Do **not** +share timing constants: two engines are not evidence for a common model. + +### 5.2 Getting back on the path after an interrupt + +Options for "how many tiles of the interrupted step were done": + +- **A. Time estimate** `floor((t_detect − t_start − turn) / step)`, using the + detecting frame's timestamp and starting the clock only after + `wait_for_all_requests()`. ±1 tile from emulator/capture latency. +- **B. Path data.** A trainer's sight line triggers on a fixed tile, so the + step can declare `trainer_stops_at`. Exact. This is what BDSP's hard-coded + pushes encode, without a schema, and keyed by strings: `Segment_07.cpp:214` + matches `"route_215_skier_edward"` but the only caller (`:773`) passes + `"route_216_skier_edward"`, so the 8.1 s push never runs. +- **C. Landmark fix** (§4.3). Exact where templates exist. +- **D. Bonk anchor.** Walk into a known wall, counter or door until blocked + or warped; overshoot is harmless. Already how Pokécenter entry works. + +Recommendation: B when the step declares it; otherwise A, marked inexact, and +end with C or D before anything that needs an exact tile (doors beside walls, +interacting, the next trainer). Never chain two inexact estimates. + +### 5.3 Tests (no console) + +Following `ScriptedWildBattleExit` (`FRLG/PokemonFRLG_Tests.cpp:46-166`): +executor bookkeeping (step/tile indices, splitting the remainder after an +interrupt, early warp vs missing warp, `max_interrupts`, RESUME vs +RESTART_PATH vs ABORT); FRLG hold-duration maths (turn cost, mid-step switch) +if computed by a pure function; `estimate_tiles` boundaries; the alignment +control law fed scripted `LandmarkFix` sequences (converging, blocked, lost +13 frames, interrupted). Matchers themselves: screenshot tests in +CommandLineTests, one PR per detector (Mysticial's request on #1717). + +### 5.4 Risks + +- NSO GBA runs ~59.73 Hz inside a 60 Hz output; long holds drift. Split long + paths at anchors. +- Input and capture latency hurt option A; dropped 33–40 ms taps (use ≥80 ms + or continuous holds). +- BDSP joystick moves are not grid-locked: snap back to the grid (d-pad or a + wall bump) after any joystick alignment. +- Variable speed tiles and forced movement (snow, bike, ledges = 2 tiles, + spin tiles, currents), wandering NPCs blocking or pushing. +- Battle intro and door fade both look like a black screen: check for a + battle menu after the fade (BDSP `walk_until_doorway` does; FRLG must). + +## 6. BDSP #1717: findings (phase C) + +**Revised 2026-10-25.** We do not own anything yet, so we don't ask the BDSP +team to restructure their WIP, adopt a shared core, or reorder their PRs. +The defects below become standalone review comments with no request +attached, drafted in +[AutoStory-1717-review-drafts.md](AutoStory-1717-review-drafts.md) and +re-checked at `65094d4cd`. The user decides whether and when to post them. +Design opinions stay in this internal doc until phase D, and come up +earlier only if someone asks. + +They asked for feedback on "the SV-derived architecture, checkpoint and +recovery contracts, BDSP helper/detector boundaries, resource distribution, +and the order in which to extract focused PRs". + +**Defects (drafted as comments 1–5):** + +- `reload_backup_save` is an empty TODO (`BT.cpp:188-197`), but the retry + loop calls it when `save` is false (`:245-246`) and then reruns the action + without any reset. +- Checkpoint order is written twice (segment + registry) and has drifted: + - Segment 08's `run_segment` stops at 0821 (`Segment_08.cpp:116`), while + the registry goes on to 0826 (`BA.cpp:181-185`), so 0822–0826 (Sunyshore + and the 8th gym) never run in segment mode. + - 0717 is missing from the checkpoint list (`BA.cpp:155-156` goes 0716 → + 0718). +- `Segment_07.cpp:214` matches `"route_215_skier_edward"`, but the only + caller (`:773`) passes `"route_216_skier_edward"`, so the 8.1 s push never + runs. +- Badge texts swapped: Segment 04 (`:31`) says "Third Gym Badge acquired + (Hearthome City)", Segment 05 (`:30`) "Fifth Gym Badge acquired (Veilstone + City)"; Veilstone is the 3rd, Hearthome the 5th. + +**Question (draft 6, optional):** `handle_battle` is copied into each of +segments 04–09, keyed by trainer-id strings, while `Battle.cpp`'s +`handle_expert_battle` (`:698`) and `handle_battle_with_outside_help` +(`:208`) have no callers. + +**Held back (design opinions, for phase D or if asked):** + +- A shared run-flow core (§3) instead of per-game copies; their + `section_database` is cleaner than SV's six copies and would be the model. +- No start > end check, no recoverable-error report, no `GO_HOME_WHEN_DONE`, + language fixed to English (`BA.cpp:701`), diagnostic options marked + "remove before production" (`BA.h:42-46`). These are normal for a WIP + and not worth a comment. +- Movement: path data with `trainer_stops_at` (§5.2) instead of + per-segment `move_back_after_unexpected_battle` chains; the landmark loop + (segments 00–03) as a shared alignment routine (§5.1). +- Changes to shared `DialogDetector` boxes affect every BDSP program and + need screenshot tests; Mysticial has already asked for detectors first, + one PR each. +- Templates live only in their Packages fork, and per-hour lighting variants + multiply them. + +## 7. FRLG AutoStory plan + +### 7.0 Prior art: junari's FRLG script + +From the [blog post](https://junari.hatenablog.com/entry/2024/08/02/234339) +(Poke-Controller MODIFIED, Python; we have not read its code): + +- **Scope and time:** new game to the Network Machine, Silph Co., the + ending or the Hall of Fame (selectable stop point); about 12–14 hours to + the end. +- **Team:** the starter alone fights every trainer. Squirtle is fastest, + then Charmander, then Bulbasaur. Moves come from TMs and tutors: Squirtle + Mega Kick + Water Pulse; Charmander Mega Kick, Dig, Dragon Claw; Bulbasaur + Return, Earthquake. No transfers from another save. This confirms the + solo rule (§9) is workable and shrinks the requirements plan (§7.6) to HM + users, TMs and tutors, and money. +- **Recovery:** on a lost battle or a loop running too long, reset and + restart from the nearest save. +- **Options:** start/stop number, version, hardware, language, gender, + player/rival names, starter, fossil, Dojo Pokémon, and a "Nugget farm" + mode (lose to the Nugget Bridge Rocket repeatedly, 1.5–2 min each), the + same trick as our `NuggetBridgeFarmer`. +- **Setup it requires:** delete the save from the title screen (Y + B + Up + on Switch), 720p output, screen size 100%. +- **Images:** none shipped; it crops reference images at run time on the + first run and reuses them. + +Its terms forbid redistribution and claiming authorship, so it is a +reference for the route, team and options, not a source of code. Its demo +videos are useful for checking routes. + +### 7.1 Reuse + +Already on `main` (`FRLG/`): + +- Detectors: dialogs, battle menu/dialogs/learn/level-up/out-of-PP, party + and summary, start menu, bag, load menu (untested on a new game), + selection arrows, faint watchers, fly map, E4 rooms, Hall of Fame. +- Navigation: `fly_from_kanto_map` (13 towns), `heal_at_pokecenter`, + `enter_leave_pokecenter`, `use_teleport_from_overworld`, + `open_party_menu_from_overworld`, `save_game_to_overworld`, `soft_reset`. +- Battle: tested `exit_wild_battle`; `spam_first_move`; + `EliteFourFarmer::run_battle` (OCR the opponent, move plan per opponent, + out-of-PP) as the model for a trainer battle handler. +- Story fragments: StarterRng's `walk_to_rival_battle`, + `walk_to_route1_from_lab`, `auto_battle_rival`; SidHelper's + `finish_intro_animations`. +- **Money:** `NuggetBridgeFarmer` already loops the Nugget Bridge event + (loses the battle on purpose, ~72 s per Nugget by its "100 ≈ 2 hours" + default, Nugget sells for 5,000, so ≈250k/hour). Wrap it as an optional + "money top-up" checkpoint after Nugget Bridge (or any time later, by flying + to Cerulean), with a target amount. +- `ItemDuplication` (Retro Mail glitch; needs Farfetch'd from the Vermilion + trade): optional way to duplicate Rare Candies instead of grinding. Keep + it out of the default route; offer it as a config. + +### 7.2 Missing pieces (infrastructure, ~3–5k lines) + +1. The run flow (§3), shared core in `Pokemon/AutoStory/`; FRLG hooks + (save via start menu, reset via `soft_reset` + load menu), options, + registries in the FRLG module. +2. Tile path (§5) in `Pokemon/Navigation/`, the FRLG adapter in the FRLG + module, moving `walk_path` and the constants out of + `EliteFourFarmer.cpp` (thewhitewolfking's code, not ours); add run + (8 frames/tile), bike (6), surf (8) per decomp; ledge steps. +3. Overworld detector (overworld vs dialog vs battle vs menu without opening + the start menu) and facing detection (player sprite in the centre tile). +4. Walk interrupts: trainer "!" or approach, wild battle (black screen + + battle menu), item pickup jingle/dialog, NPC dialog; resume via §5.2. +5. Trainer battle handler: move choice (per-checkpoint plan first, type + table later), "will you switch?" (avoid by Battle Style = Set), next + Pokémon after a faint, move learn (skip or replace per config), + evolution (allow/cancel per config), whiteout → fail the checkpoint. +6. New game: intro, naming keyboard (preset names avoid it), options menu + (text Fast, animations Off, style Set), load menu on a fresh save. Built + on the screen machine and step trace (§0.1), as the BDSP intro is in the + lab: every input follows a screen read, no mash-for-N-seconds. +7. Field moves: teach HM from bag, use Cut/Surf/Strength from the party + menu, Fly (exists), Bike on/off; puzzles below. +8. Shop and PC: buy list per Mart, deposit/withdraw for HM carriers. +9. Catch routine and the requirements planner (§7.6). + +English only (decision 5), so text detectors and OCR need one language. + +### 7.3 Route data + +The decomp (`data/maps//map.json`) gives every object event with x, y, +movement type, `trainer_type`, sight range and script, plus warps, coord +events and signs; `data/layouts//map.bin` gives metatiles and collision +(behaviours such as ledges, spin tiles and currents come from tileset +attributes). That is enough to: + +- plan paths offline with a small planner over collision + ledges (prior + art: `40Cakes/pokebot-gen3` `modules/map_path.py`, GPL-3.0, which plans on + live RAM; we would run the same idea over the decomp files and **emit + reviewed C++ `TilePath` literals**, not ship the planner); +- mark each trainer's sight line on the path so `trainer_stops_at` is + computed, not guessed; +- simulate spin tiles (Rocket Hideout) and plan Strength boulder pushes + (Seafoam optional; Victory Road mandatory). + +Online maps (Bulbapedia route pages, PokeTools) list trainers but not exact +positions; the decomp is the source of truth. + +### 7.4 Segments (draft) + +| Seg | Content | Notes | +|---|---|---| +| 00 | New game → starter → lab rival | names, options, starter config | +| 01 | Parcel → Pokédex → Viridian Forest → Brock | Route 22 rival optional | +| 02 | Route 3 → Mt. Moon (fossil) → Misty → Nugget Bridge → Bill (S.S. Ticket) | money top-up available from here | +| 03 | S.S. Anne (Cut) → Surge; VS Seeker; Bike (opt.) | HM carrier for Cut | +| 04 | Route 9/10 → Rock Tunnel → Lavender → Celadon → Erika; Tea | Flash optional | +| 05 | Rocket Hideout (spin tiles, Lift Key, Silph Scope) → Pokémon Tower → Poké Flute | | +| 06 | Snorlax (Route 12 or 16) → Fuchsia → Koga → Safari (Surf, Strength) | Safari is a step-limited zone | +| 07 | Saffron: Silph Co. (Card Key, warp pads, rival, Giovanni) → Sabrina | Lapras optional | +| 08 | To Cinnabar (Route 21 by default) → Mansion (Secret Key) → Blaine | Seafoam optional; decline One Island | +| 09 | Giovanni (Viridian) → Route 22 rival → Victory Road (Strength boulders) | | +| 10 | Elite Four + Champion | reuse `EliteFourFarmer` pieces | +| 11 | Sevii post-game: Ruby (Mt. Ember), Sapphire (Dotted Hole), Network Machine | added 2026-10-29 (§9.2) | +| 12 | Mew | method to settle (§9.2); likely needs the naming typer | +| opt | Money top-up, leveling loop, gift Pokémon, fossils | config-driven, outside the main order | + +Estimate ~90–110 checkpoints. Hardest: 00 (keyboard, intro), 05 (spin +tiles), 07 (warp pads), 09 (boulders), and leveling before each gym. + +### 7.5 Config (in game order) + +Player/rival names (presets by default); options (forced by the program); +starter; nickname prompts (always no); Magikarp (skip); Mt. Moon fossil +(Dome/Helix); Old Amber (skip); Bike (yes: shortens Cycling Road and +travel); Flash (skip); Eevee and stone (skip); Game Corner prizes (skip); +Snorlax route (12 or 16) and catch or defeat; Hitmonlee/Hitmonchan; Lapras +(yes: Surf carrier); Sevii trip (decline); optional legendaries (skip); HM +carriers (Cut/Surf/Strength: default to gifts and route catches); shop list +per Mart; move learning (keep starter's best moves per a table); money +target for the top-up; leveling targets per gym; Rare Candy duplication +(off). + +Facts to design around (decomp): HM field use needs Cascade (Cut), Rainbow +(Strength), Soul (Surf); Flash/Fly/Rock Smash are optional for Kanto; Rock +Tunnel's darkness is cosmetic for movement; Saffron guards want Tea; the +Viridian Gym opens after badges 2–7; blacking out costs level × 4 × a +badge-based multiplier, not half the money. + +### 7.6 Working backwards: the requirements plan + +The team, catches and purchases follow from what the route and the chosen +options require, each with a deadline. Write this down as data, one row per +requirement, and derive the catch/gift/shop checkpoints from it: + +| Requirement | Needed by | Providers (pick per version and options) | +|---|---|---| +| Cut user (Cascade badge) | Vermilion Gym tree (seg 03) | Oddish (Routes 24/25; FireRed exclusive, Bellsprout in LeafGreen), Paras (Mt. Moon), Farfetch'd (Vermilion in-game trade, option only) | +| Surf user (Soul badge) | Cinnabar (seg 08) | Lapras (Silph Co. 7F gift, seg 07), a Water starter, Seel/Psyduck etc. | +| Strength user (Rainbow badge) | Victory Road (seg 09) | Machop line, Snorlax, Lapras | +| Battle strength per gym | each gym and the E4 | the starter alone, levelled by every trainer on the way (proven by junari's script, §7.0); TMs and tutors (e.g. Water Pulse from Misty, Mega Kick tutor, Dig, Dragon Claw, Return, Earthquake); leveling loops or Rare Candies (duplication option) as a fallback | +| Money for each Mart list | per Mart | trainer prize money; Nugget Bridge top-up option | +| Retro Mail duplication (option) | when enabled | Farfetch'd from the Vermilion trade (needs the Pokémon it asks for), Retro Mail, the item to copy; see `ItemDuplication` | +| Nugget Bridge top-up (option) | after seg 02 | party able to lose on purpose and heal; `NuggetBridgeFarmer` preconditions | + +Consequences: + +- A **catch routine** is part of the infrastructure (weaken without + fainting, throw until caught, handle box-full and nickname prompts), plus + the encounter detector to know what appeared (`WildEncounterReader` + exists). +- **HM teaching** and party order (who leads, who carries HMs) are steps + derived from the table, not written by hand in each segment. +- A small **planner** checks a chosen config before the run: every + requirement has a provider before its deadline, the money plan covers the + Mart lists, version exclusives exist in the chosen version. A config that + fails is a `UserSetupError` at start, not a failure hours in. +- The planner and the table are pure logic, so they get unit tests. + +### 7.7 PR plan for reviewers + +**2026-10-29:** T1 and T2 below become N1–N3 on `nav/core` and the FRLG child +`frlg/brock-mvp` ([AutoStory-Navigation-Stack.md](AutoStory-Navigation-Stack.md) §4). The +Elite Four farmer moves onto the walker later, not as part of the pilot. The rest of the table +stands. + +Reviewers should know up front what is coming. Phase A is about 10 PRs, +touches only FRLG files, and produces something users can run at PR 5. +Most PRs are 150–500 changed lines, each does one thing, and each says what +to read first. Upstream allows two open PRs from us at a time, so they arrive +in order; the rest wait as drafts on the fork. + +**Phase A: pilot, new game → Brock (new files and FRLG only)** + +| # | ID | PR | Depends on | Kind | Size (lines) | What the reviewer reads | +|---|---|---|---|---|---|---| +| 1 | T1 | Tile-path executor + interrupt policy + tests, in `Pokemon/Navigation/` | — | new | ~500 (half tests) | result types, resume bookkeeping | +| 2 | T2 | FRLG adapter; Elite Four farmer uses it | T1 | move + new | ~300 | walker moved out of `EliteFourFarmer.cpp`; farmer unchanged on hardware | +| 3 | D4 | Screen machine + step trace (`CommonTools/`), then FRLG new-game screens and naming on it | — | new | ~400 + images | machine tests with fake screens; detector + screenshot tests | +| 4 | S0 | Program shell: options, FRLG hooks on the shared run flow (`Pokemon/AutoStory/`, + tests), registries | — | new | ~500 (a third of it tests) | retry behaviour against fake hooks; options | +| 5 | S1 | Segment 00: new game → starter → lab rival | S0, D4, T2 | new + path data | ~500 | **first runnable**; checkpoint list, paths as data; hardware runs | +| 6 | D1 | Overworld detector | — | new | ~150 + images | detector + screenshot tests | +| 7 | D3 | Trainer spotting / item pickup watchers | — | new | ~150 + images | same | +| 8 | B1 | Walk interrupts for FRLG (battle, dialog, item) | T2, D1, D3 | new | ~300 | resolve() paths; fakes | +| 9 | B2 | Trainer battle handler + tests | — | new | ~500 (half tests) | decisions with scripted fakes | +| 10 | S2 | Segment 01: parcel → Pokédex → Viridian Forest → Brock | S1, B1, B2 | new + path data | ~700 | **pilot complete**; hardware runs of each checkpoint | + +The pilot does not need the facing detector (D2): path data knows the +facing after each step, as the E4 farmer does today. The requirements +planner (§7.6) is not needed either, because the pilot route has no HM, +shop or catch requirements. S1 reuses StarterRng's `walk_to_rival_battle` / +`auto_battle_rival` and SidHelper's `finish_intro_animations` where they +fit. + +**Phase B: to the Champion (only if gate F3 passes)** + +| ID | PR | Depends on | Kind | Size (lines) | +|---|---|---|---|---| +| D2 | Facing detector | D1 | new | ~150 + images | +| T3 | Landmark alignment loop + tests (FRLG; whole-tile output) | T1 | new | ~300 | +| B3 | Field moves: teach HM, Cut/Surf/Strength, Bike | D1 | new | ~300 | +| B4 | Mart purchases | — | new | ~250 | +| B5 | Catch routine | D1 | new | ~300 | +| S0b | Requirements plan + start-up check (§7.6) + tests | S0 | new | ~400 | +| S3–S11 | One PR per segment (02 … 10) | S2, B3–B5, S0b | new + path data | ~500–900 each | +| opt | Nugget Bridge top-up, Retro Mail duplication | S0b | new | small, one each | + +**Phase C: BDSP findings (independent, any time).** Not PRs: the review +comment drafts in +[AutoStory-1717-review-drafts.md](AutoStory-1717-review-drafts.md), for the +user to post or not. + +**Phase D: SV onto the shared code (after the pilot runs and people use +it).** Framing: "FRLG runs on the shared core; I'll migrate SV and offer it +to BDSP." Nothing of ours moves in phase D. + +| ID | PR | Depends on | Kind | Size (lines) | What the reviewer reads | +|---|---|---|---|---|---| +| R1 | Shared start/end select databases (SV) | — | move | ~150 | `--color-moved`; same slugs and labels | +| R2 | Story range maths + tests | R1 | move + tests | ~200 | the pure function and its table test | +| R3 | SV `AutoStoryStats` onto the shared one | — | move | ~80 | stat keys unchanged | +| R4 | SV retry loops become adapters over the shared core | R3 | move + new | ~400 (half tests) | §3.2 list of kept behaviours | +| R5 | Dev harness out of the SV program class | — | move | ~300, mostly a deleted dead block | `--color-moved` | + +R1–R5 touch SV AutoStory, which is someone else's merged program: pure +moves first, behaviour changes separately (§3.3). The SV AutoStory author +and Mysticial are the reviewers for these (§8). The BDSP team gets an offer, +not a request. + +Notes: + +- Segment PRs are mostly path data and short checkpoint functions. Paths + are planned offline from the decomp; the planner stays outside the repo + unless reviewers want it under `devtools/`. +- Optional features (Nugget Bridge top-up, Retro Mail duplication) come as + separate small PRs after S0b, so they never hold up a segment. + +**Milestones** + +| M | PRs (count to here) | Users can run | Validation | +|---|---|---|---| +| F1 | T1, T2 (2) | the E4 farmer, on the new walker | unit tests; E4 farmer unchanged on hardware | +| F2 | D4, S0, S1 (5) | **new game → rival battle** | each checkpoint ×3 from its save | +| F3 (gate) | D1, D3, B1, B2, S2 (10) | **new game → Boulder Badge** | each checkpoint ×3 from its save; Switch hours logged per checkpoint | +| F4–F6 | phase B | further, one segment at a time | each checkpoint from its save, then full runs | +| F7 | phase D | SV unchanged; FRLG on the shared core | SV AutoStory smoke run of a few checkpoints | + +**Gate F3 (public, in the proposal).** Continue past Brock only if every +pilot checkpoint (about 12) succeeds 3 times in a row from its own save, +**and** the measured cost is at most **3 Switch hours per checkpoint on +average**, counting both development and validation. If either target is +missed, stop at Brock: the pilot stays a working program, and we post the +measured cost. At 3 hours, the full game (~100 checkpoints) is about 300 +hardware hours. Also record lines per checkpoint against the ~100–120 that +the 10–12k-line estimate implies. (The 3-hour budget was added in this +revision; the earlier gate named only the ×3 runs. The user should confirm +it before the proposal is posted.) + +## 8. Who wrote the FRLG code we build on + +From `git log` / `git blame` on `upstream/main` at `f9cf36f7a` +(2026-10-25). Paths are under `FRLG/`. They are the likely reviewers, and +one of them may sponsor the work. + +| Author | What | Blame share | Last activity | +|---|---|---|---| +| thewhitewolfking | `Programs/Farming/PokemonFRLG_EliteFourFarmer.cpp/.h`, the tile walker T2 moves (#1536, #1544) | all 1,004 lines of the .cpp | 2026-10-08 (#1544); no other commits, no open PRs | +| theAstroGoth | `Programs/RngManipulation/PokemonFRLG_StarterRng.cpp` (the story fragments S1 reuses), most of `PokemonFRLG_Navigation.cpp` | 831 of 862 StarterRng lines; 686 Navigation lines; 88 FRLG commits, the most | 2026-10-21 (#1662); open BDSP starter RNG #1366 | +| Dalton (Dalton-V) | `Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp` (money top-up option), selection arrows and start menu (#1129), StarterRng input trim (#1679) | 213 of 235 NuggetBridge lines; 354 Navigation lines; 27 FRLG commits | 2026-10-24 (#1720, CONTRIBUTING testing additions); open #1614 FRLG box sorter | +| kichithewolf | started `PokemonFRLG_Navigation.cpp` (2025-01-29), party slot detection | 234 Navigation lines; 23 FRLG commits | 2026-09-20 (#1485) | +| Alexander Yee (Mysticial) | maintainer; moves and cleanups | 64 FRLG commits | 2026-10-25 (repo); FRLG 2026-10-15 | +| us (dayfine) | `WildBattleExitActions` seam and tests (#1663), evolution exit fix (#1684), both merged | 229 lines of `PokemonFRLG_Tests.cpp` | 2026-10-23 | + +Reading: theAstroGoth is the most likely FRLG reviewer and sponsor (largest +and most recent FRLG author, and owns the code S1 reuses). Ask +thewhitewolfking to look at T2, since it moves their walker; they have been +quiet since 2026-10-08. Dalton is active and owns the Nugget Bridge code +behind the money option. For phase D, the SV AutoStory author is jw098 (117 +commits under SV AutoStory; last SV AutoStory commit 2026-08-30, last repo +commit 2026-10-09). + +Process notes that apply to the PRs: code-health limits apply to every new +file (functions ≤50 lines); the segment style is short checkpoint functions +calling path data and helpers, which also keeps reviews small. Who does what +among the agents is recorded outside this doc. + +## 9. Decisions (user, 2026-10-25) + +1. **Shared pieces:** offer to work on them rather than waiting to be + asked. *Revised 2026-10-25 after review:* the offer comes in phase D, + after the FRLG pilot runs and people use it. Until then, BDSP gets only + the standalone bug comments (phase C). +2. **Scope:** main story to the Champion. No Sevii or post-game. + *Revised 2026-10-29:* the run ends with catching Mew, so it includes the + Sevii Islands post-game for the Ruby and Sapphire (Network Machine). Mew + has no in-game encounter in FRLG, so how it is obtained (e.g. arbitrary + code execution through box names, which needs the FRLG naming typer) is + still to be settled and becomes its own segment. +3. **Shortcuts** (Nugget Bridge money, Retro Mail duplication): available as + options. They need planning, e.g. catching the right Pokémon at the right + time, so they feed into the requirements plan (§7.6). +4. **Team:** worked out backwards from what the route and options need + (§7.6), not chosen up front. +5. **Language:** English only. + +6. **Version:** FireRed (LeafGreen is already finished on the test + console). +7. **Solo from a new game:** the program must not require a trade, a + trade-back, or a levelled Pokémon brought in from another save. Optional + trades the game itself offers (e.g. the Vermilion Farfetch'd) are + allowed only behind an option.