From d181f952a78076664e8eda64a48cbb87e0e7aae8 Mon Sep 17 00:00:00 2001 From: acamilo Date: Wed, 23 Sep 2026 14:48:25 +0000 Subject: [PATCH] tests: from the badge, the road to Mt. Moon is not a ring at the Pewter end, and Route 4's doors and sides are the cartridge's --- .../crates/flysim/tests/rom_macros_mode.rs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/services/flysim/crates/flysim/tests/rom_macros_mode.rs b/services/flysim/crates/flysim/tests/rom_macros_mode.rs index af890a2..443284a 100644 --- a/services/flysim/crates/flysim/tests/rom_macros_mode.rs +++ b/services/flysim/crates/flysim/tests/rom_macros_mode.rs @@ -3452,3 +3452,137 @@ fn tail_whip_at_its_limit_is_not_dealt_and_a_route_one_battle_is_won() { ); assert!(won > 0, "no wild battle was won in {budget} frames: {battles:?}"); } + +const ROUTE_4: u32 = 0x0f; +const MT_MOON_1F: u32 = 0x3b; + +/// A rung-11 checkpoint just after the Boulder Badge, or `None` to skip. +/// +/// Row 59's is the route survey's own: `examples/scene_probe.rs` driven from the row-58 checkpoint +/// with `FLY_PROBE_CATCH=route FLY_PROBE_PREFER="GO OBJECTIVE,TALK"` until rung 11, written by +/// `FLY_PROBE_SAVE_RANK=11` on the first safe overworld frame -- in the gym, beside BROCK. +fn badge_checkpoint() -> Option { + std::env::var_os("FLY_BADGE_CHECKPOINT").map(|path| { + flysim::store::load(std::path::Path::new(&path)) + .expect("the checkpoint should be a FLYSIM01 envelope") + }) +} + +/// From the badge: the fly takes the road to Mt. Moon instead of walking the Pewter end of Route 3. +/// +/// **What the row-58 review measured** (the route survey carried past the badge, 2026-09-23): +/// from about frame 68,000, Pewter City (39, 17) and Route 3 (0, 9) in a ring, `GO OBJECTIVE` +/// done on Route 3 538 times and `GO ROUTE` done on Pewter 537. The live fly was due to reach +/// the same state as soon as it earned the badge. +/// +/// **What was wrong** (`infra/docs/macros-traps.md` row 59), two things stacked: +/// +/// - the map graph had Route 4 east of Route 3 and a Mt. Moon door on Route 3. The headers put +/// Route 4 north of Route 3 and both of Mt. Moon's doors on Route 4, so Route 3's north edge, +/// the one road to the mountain, named no map and was nobody's first hop; and Route 4's two +/// sides, which the mountain cuts apart, were one node; +/// - Route 3's first trainer closed his challenge onto five frames of plain overworld before the +/// battle was decided, and the walk he interrupted was written as a refusal on the first of +/// them: (11, 6), the one gap between the road's west end and the rest of it, walled for the +/// session. +/// +/// The claims, none of them about which button the fly presses: +/// +/// - the fly is on **Route 4** and at **Mt. Moon's door** (map `0x3b`) inside the budget; +/// - Pewter City and Route 3 are **not a ring**: under forty crossings between them, against +/// over a thousand on the base. +/// +/// ```sh +/// FLY_ROM=/path/to/pokemon-red.gb \ +/// FLY_BADGE_CHECKPOINT=.local/checkpoints/survey-rank11-row59.checkpoint \ +/// cargo test --release -p flysim --test rom_macros_mode -- --nocapture the_road_to_mt_moon +/// ``` +#[test] +fn the_road_to_mt_moon_is_not_a_ring_at_the_pewter_end_from_the_badge_checkpoint() { + let rom = skip_without_rom!(); + let Some(checkpoint) = badge_checkpoint() else { + eprintln!("skipped: no FLY_BADGE_CHECKPOINT"); + return; + }; + let mut run = Run::resume(&rom, MacroMode::Macros, &checkpoint); + assert!(run.adapter.progress().rank >= 11, "the checkpoint is past the badge"); + + let mut crossings = 0u32; + let mut on_route_4: Option = None; + let mut at_mt_moon: Option = None; + let mut previous = run.map(); + for frame in 0..288_000u32 { + run.frame(); + let map = run.map(); + if map != previous { + if (previous == PEWTER_CITY && map == ROUTE_3) || (previous == ROUTE_3 && map == PEWTER_CITY) { + crossings += 1; + } + previous = map; + } + if on_route_4.is_none() && map == ROUTE_4 { + on_route_4 = Some(frame); + } + if at_mt_moon.is_none() && map == MT_MOON_1F { + at_mt_moon = Some(frame); + break; + } + } + let progress = run.adapter.progress(); + eprintln!( + "{:.1} brain minutes: Route 4 at {on_route_4:?}, Mt. Moon at {at_mt_moon:?}, Pewter / Route 3 \ + crossings {crossings}, rank {} ({}), route {:?}, macros {:?}", + run.ms / 60_000.0, + progress.rank, + progress.rank_label, + run.route, + run.started + ); + assert!(crossings < 40, "{crossings} crossings between Pewter City and Route 3: {:?}", run.started); + assert!(on_route_4.is_some(), "the fly never reached Route 4: {:?}", run.route); + assert!(at_mt_moon.is_some(), "the fly never reached Mt. Moon's door: {:?}", run.route); +} + +/// Route 4's two sides, read off the cartridge: the warp table the geography's pieces are keyed +/// by, and the piece the fly is standing in on arrival from Route 3. +/// +/// The table's door indices and tiles are the disassembly's (`data/maps/objects/Route4.asm`); this +/// is the same three warps read from `wWarpEntries` on the loaded map, and the piece named by the +/// decoded grid ([`geography::region_on`]) rather than by the doors. +#[test] +fn route_4s_doors_and_sides_are_the_cartridges_from_the_badge_checkpoint() { + use flybrain_gb::pokemon_red::macros::geography::{self, Region}; + let rom = skip_without_rom!(); + let Some(checkpoint) = badge_checkpoint() else { + eprintln!("skipped: no FLY_BADGE_CHECKPOINT"); + return; + }; + let mut run = Run::resume(&rom, MacroMode::Macros, &checkpoint); + let mut read = None; + for _ in 0..216_000u32 { + run.frame(); + if run.map() != ROUTE_4 { + continue; + } + let Some(player) = flybrain_gb::pokemon_red::state::player(&mut run.gb) else { continue }; + let Ok(grid) = flybrain_gb::pokemon_red::state::map_grid(&mut run.gb) else { continue }; + if u32::from(player.map) != ROUTE_4 || grid.map() != player.map { + continue; + } + let warps = flybrain_gb::pokemon_red::state::warps(&mut run.gb); + read = Some((player, warps, geography::region_on(player.map, player.x, player.y, Some(&grid)))); + break; + } + let Some((player, warps, region)) = read else { + panic!("the fly never stood on Route 4 with its grid decoded: {:?}", run.route); + }; + eprintln!("on Route 4 at ({}, {}), piece {region:?}, warps {warps:?}", player.x, player.y); + let doors: Vec<(u8, u8, u8)> = + warps.iter().map(|warp| (warp.x, warp.y, warp.destination_map)).collect(); + assert_eq!( + doors, + vec![(11, 5, 0x44), (18, 5, 0x3b), (24, 5, 0x3c)], + "the Pokécenter, the cave mouth and B1F's exit, in the table's order" + ); + assert_eq!(region, Region::piece(0x0f, 0), "arrived from Route 3, on the cave mouth's side"); +}