diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/executor.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/executor.rs index 1d320a2..1c3667f 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/executor.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/executor.rs @@ -716,6 +716,13 @@ impl MacroMachine { // all" -- and the route search is the real one, so a bound macro can refuse `no route` // once per hold for ever while the candidate list never changes. Recording what it // could not reach is what empties the list and takes the button off the pad. + // Row 57: a refusal that teaches the blocked ledger nothing new -- every goal it could + // not reach is already resting there -- is one the dealer will deal again unchanged, + // because only a last resort deals goals the ledger is resting. That refusal is the + // one remembered where the fly stands. A refusal that writes a new exclusion changes + // the next deal by itself and is not held against the tile: the next deal may be the + // last resort, whose own walk can go where this one could not. + let taught = unreachable.iter().any(|key| !state.blocked(*key)); if let Some(map) = state.player().map(|player| player.map) { self.blocked.extend(unreachable.into_iter().map(|key| (map, key))); // And for the frontier, the same fact one level up: every tile of this map the @@ -726,7 +733,9 @@ impl MacroMachine { self.exhausted = Some(map); } } - refused_here(self); + if !taught { + refused_here(self); + } return refuse(self, Refusal::NoRoute); }; // The map the target was chosen on, so an entry cannot be read back on another map. diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/tests.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/tests.rs index 5fe1580..5aee4e0 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/tests.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/tests.rs @@ -4979,6 +4979,32 @@ fn a_last_resort_that_cannot_reach_the_objectives_door_takes_a_way_out_it_can_re "south, toward the way out on this side of the fence: {:?}", world.player ); + + // The order the real brain pressed them in, measured on the seeded pocket: `GO ROUTE` first, + // while the door is still the second tier's answer rather than the last resort's. That refusal + // teaches the ledger the door, so it is not held against the tile -- the next deal is the last + // resort, and its walk goes where the first could not. + let mut world = fenced_in_pewter(); + world.connections = Connections { north: false, south: true, east: false, west: false }; + world.seen_maps.insert(maps::ROUTE_2); + world.targets.record_blocked(world.map, south); + world.exhausted.insert(world.map); + world.objective = Some(Objective { + map: maps::PEWTER_GYM, + tile: None, + warp: None, + edge: None, + target: Some(PlaceKind::Person), + }); + assert_eq!( + run(&mut world, MacroKind::GoRoute).map_err(|refused| refused.reason), + Err(Refusal::NoRoute), + "the door, through the second tier, beyond the fence" + ); + assert!(on_the_pad(&mut world, MacroKind::GoRoute), "a refusal that taught the ledger is not held"); + let started_at = world.player; + assert!(run(&mut world, MacroKind::GoRoute).is_ok(), "the last resort walks where it can"); + assert!(world.player.y > started_at.y, "{:?}", world.player); } #[test]