a last resort that cannot reach the objective's door takes a way out it can
Row 57's pocket had a way out: the road east, three tiles from the fly and resting in the blocked window. The last resort ignores that window but narrows to the ways toward the objective, and the only one was the gym's door beyond the fence, so the route search refused and the road was never tried. With the refusal withheld the pad went empty and the fly waited out the road's window, ten brain minutes. The narrowing is a preference and the dealer cannot search. When start's route search cannot reach the preferred ways, it tries the rest of the last resort, nearest reachable first, exactly as every walk chooses. The pad is unchanged; only where the pressed macro walks. The ROM proof now asserts the fly leaves the pocket inside a brain minute: frame 517 (0.14 minutes), against 36,325 (10.1) on the base.
This commit is contained in:
parent
2d04abaf52
commit
ef98b04547
4 changed files with 104 additions and 11 deletions
|
|
@ -36,7 +36,7 @@ use super::palette::{
|
||||||
precondition,
|
precondition,
|
||||||
shop_screen, stock_index, throw_slot, untalked_objects, untalked_people, ways,
|
shop_screen, stock_index, throw_slot, untalked_objects, untalked_people, ways,
|
||||||
};
|
};
|
||||||
use super::path::{self, Route, Way};
|
use super::path::{self, Exit, Route, Way};
|
||||||
use super::state::{Facing, Scene, ShopScreen};
|
use super::state::{Facing, Scene, ShopScreen};
|
||||||
|
|
||||||
/// Hard cap on one macro, in game frames. `docs/design/macros.md` section 4: "hard cap 600 frames
|
/// Hard cap on one macro, in game frames. `docs/design/macros.md` section 4: "hard cap 600 frames
|
||||||
|
|
@ -1686,7 +1686,17 @@ fn script(
|
||||||
};
|
};
|
||||||
let goals = exit_goals(state, way);
|
let goals = exit_goals(state, way);
|
||||||
unreachable.extend(goal_keys(&goals));
|
unreachable.extend(goal_keys(&goals));
|
||||||
let (walk, target) = walk_to(state, goals)?;
|
// A last resort that preferred the way toward the objective, and the route search
|
||||||
|
// cannot reach it: the rest of the last resort, nearest reachable first (row 57).
|
||||||
|
let walked = match walk_to(state, goals) {
|
||||||
|
Some(walked) => Some(walked),
|
||||||
|
None => {
|
||||||
|
let rest = super::palette::last_resort_wide(state, way);
|
||||||
|
let wide = goals_of(state, rest);
|
||||||
|
if wide.is_empty() { None } else { walk_to(state, wide) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (walk, target) = walked?;
|
||||||
aimed = target;
|
aimed = target;
|
||||||
vec![Step::Walk(walk)]
|
vec![Step::Walk(walk)]
|
||||||
}
|
}
|
||||||
|
|
@ -2075,7 +2085,13 @@ fn one_target(goals: &[Goal]) -> Option<TargetKey> {
|
||||||
/// is in the moment it comes down a staircase and lands on the warp tile. A doormat underfoot is
|
/// is in the moment it comes down a staircase and lands on the warp tile. A doormat underfoot is
|
||||||
/// different: its press is the point, so it stays.
|
/// different: its press is the point, so it stays.
|
||||||
fn exit_goals(state: &mut dyn MacroState, way: Way) -> Vec<Goal> {
|
fn exit_goals(state: &mut dyn MacroState, way: Way) -> Vec<Goal> {
|
||||||
let mut goals: Vec<Goal> = ways(state, way)
|
let exits = ways(state, way);
|
||||||
|
goals_of(state, exits)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`exit_goals`] over a list of exits the caller already has.
|
||||||
|
fn goals_of(state: &mut dyn MacroState, exits: Vec<Exit>) -> Vec<Goal> {
|
||||||
|
let mut goals: Vec<Goal> = exits
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|exit| Goal {
|
.map(|exit| Goal {
|
||||||
tile: exit.tile,
|
tile: exit.tile,
|
||||||
|
|
|
||||||
|
|
@ -1259,6 +1259,49 @@ fn last_resort(state: &mut dyn MacroState, way: Way) -> Vec<Exit> {
|
||||||
if toward.is_empty() { every } else { toward }
|
if toward.is_empty() { every } else { toward }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every way out of this kind, when [`ways`] answered with its last resort and that answer was
|
||||||
|
/// narrowed to the ways toward the objective; empty otherwise.
|
||||||
|
///
|
||||||
|
/// Row 57 (`infra/docs/macros-traps.md`). The narrowing is a preference -- "a map the fly has
|
||||||
|
/// already seen is still the way to the next rung" -- and the dealer cannot search, so it can
|
||||||
|
/// prefer a door the fly is walled off from over one it can walk to. Live in Pewter City the last
|
||||||
|
/// resort was the gym's door on the far side of a fence while the road east, resting in the
|
||||||
|
/// blocked window, was three tiles away. This is the rest of the last resort, for `start` to try
|
||||||
|
/// when its route search cannot reach the preferred ones; the choice of *which* reachable way is
|
||||||
|
/// the route search's, nearest first, exactly as it is for every walk.
|
||||||
|
pub fn last_resort_wide(state: &mut dyn MacroState, way: Way) -> Vec<Exit> {
|
||||||
|
// Only where `ways` fell through to the last resort: a tier that has anything in it is
|
||||||
|
// already the answer, and so is a room's or a floor's own unexcluded list.
|
||||||
|
if !exit_tiers(state, way).is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
match way {
|
||||||
|
Way::Exit => {
|
||||||
|
if !unexcluded_exits(state, way).is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Way::Passage => {
|
||||||
|
if path::exits(state).iter().any(|exit| exit.way == Way::Exit)
|
||||||
|
|| !unexcluded_exits(state, way).is_empty()
|
||||||
|
{
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Way::Route => {}
|
||||||
|
}
|
||||||
|
if !stranded(state) {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let every: Vec<Exit> =
|
||||||
|
path::exits(state).into_iter().filter(|exit| exit.way == way).collect();
|
||||||
|
if toward_objective(state, &every).is_empty() {
|
||||||
|
// Not narrowed: the last resort was every one of them already.
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
every
|
||||||
|
}
|
||||||
|
|
||||||
/// The exits of the current map of one kind that no ledger excludes.
|
/// The exits of the current map of one kind that no ledger excludes.
|
||||||
fn unexcluded_exits(state: &mut dyn MacroState, way: Way) -> Vec<Exit> {
|
fn unexcluded_exits(state: &mut dyn MacroState, way: Way) -> Vec<Exit> {
|
||||||
if !objective_targets(state).is_empty() {
|
if !objective_targets(state).is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -4955,6 +4955,32 @@ fn a_way_out_refused_from_here_is_not_dealt_again_from_here() {
|
||||||
assert!(names(&mut world).contains(&"GO ROUTE"), "{:?}", names(&mut world));
|
assert!(names(&mut world).contains(&"GO ROUTE"), "{:?}", names(&mut world));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_last_resort_that_cannot_reach_the_objectives_door_takes_a_way_out_it_can_reach() {
|
||||||
|
// Row 57's pocket had a way out: the road east, three tiles from the fly and resting in the
|
||||||
|
// blocked window. The last resort ignores that window but *narrows* to the ways toward the
|
||||||
|
// objective, and the only one was the gym's door on the far side of the fence -- so the
|
||||||
|
// route search refused, and the reachable road was never tried. The narrowing is a
|
||||||
|
// preference; when the route search cannot honour it, the rest of the last resort is the
|
||||||
|
// walk's to take, nearest reachable first.
|
||||||
|
let mut world = fenced_in_pewter();
|
||||||
|
world.connections = Connections { north: false, south: true, east: false, west: false };
|
||||||
|
world.seen_maps.insert(maps::ROUTE_2);
|
||||||
|
let south = TargetKey::Exit(ExitId::Edge(Edge::South));
|
||||||
|
world.targets.record_blocked(world.map, south);
|
||||||
|
world.targets.record_blocked(world.map, TargetKey::Exit(ExitId::Warp(0)));
|
||||||
|
assert!(on_the_pad(&mut world, MacroKind::GoRoute), "the last resort deals it");
|
||||||
|
|
||||||
|
let started_at = world.player;
|
||||||
|
let outcome = run(&mut world, MacroKind::GoRoute);
|
||||||
|
assert!(outcome.is_ok(), "the road it can reach is walked, not refused: {outcome:?}");
|
||||||
|
assert!(
|
||||||
|
world.player.y > started_at.y,
|
||||||
|
"south, toward the way out on this side of the fence: {:?}",
|
||||||
|
world.player
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_escorted_walk_walls_the_tile_it_reached_not_the_one_it_set_out_from() {
|
fn an_escorted_walk_walls_the_tile_it_reached_not_the_one_it_set_out_from() {
|
||||||
// Row 57's other half. Pewter City's youngster takes the joypad on four tiles by the road
|
// Row 57's other half. Pewter City's youngster takes the joypad on four tiles by the road
|
||||||
|
|
|
||||||
|
|
@ -3046,7 +3046,9 @@ impl Palette57 {
|
||||||
/// The session ledgers are what dealt that pad and a restore starts them empty, so part two seeds
|
/// The session ledgers are what dealt that pad and a restore starts them empty, so part two seeds
|
||||||
/// the pocket: the tiles every walk has stood on, the map's frontier mark, every sign and person
|
/// the pocket: the tiles every walk has stood on, the map's frontier mark, every sign and person
|
||||||
/// talked to, the road east resting, and three pushed tiles sealing the strip by the road from the
|
/// talked to, the road east resting, and three pushed tiles sealing the strip by the road from the
|
||||||
/// town. That is a reconstruction of state the checkpoint cannot carry, said as one.
|
/// town. That is a reconstruction of state the checkpoint cannot carry, said as one. In it the last
|
||||||
|
/// resort's preferred way out, the gym's door, is beyond the fence, while the road east is three
|
||||||
|
/// tiles away and resting in its window: `start` now takes the way it can reach.
|
||||||
///
|
///
|
||||||
/// ```sh
|
/// ```sh
|
||||||
/// FLY_ROM=/path/to/pokemon-red.gb \
|
/// FLY_ROM=/path/to/pokemon-red.gb \
|
||||||
|
|
@ -3132,6 +3134,7 @@ fn the_pewter_east_pad_is_never_one_dead_button_from_the_rung_ten_checkpoint() {
|
||||||
|
|
||||||
let (mut running, mut since) = (false, Palette57::HOLD_FRAMES);
|
let (mut running, mut since) = (false, Palette57::HOLD_FRAMES);
|
||||||
let mut refusals = 0u32;
|
let mut refusals = 0u32;
|
||||||
|
let mut in_the_pocket = 0u32;
|
||||||
let mut run_of_refusals = 0u32;
|
let mut run_of_refusals = 0u32;
|
||||||
let mut longest = 0u32;
|
let mut longest = 0u32;
|
||||||
let mut last_refused: Option<(&'static str, (u8, u8, u8))> = None;
|
let mut last_refused: Option<(&'static str, (u8, u8, u8))> = None;
|
||||||
|
|
@ -3144,6 +3147,9 @@ fn the_pewter_east_pad_is_never_one_dead_button_from_the_rung_ten_checkpoint() {
|
||||||
first_pad.get_or_insert(pad.clone());
|
first_pad.get_or_insert(pad.clone());
|
||||||
if let Some(flybrain_gb::Started::Refused { name: Some(name), reason }) = tried {
|
if let Some(flybrain_gb::Started::Refused { name: Some(name), reason }) = tried {
|
||||||
refusals += 1;
|
refusals += 1;
|
||||||
|
if at.0 == pewter && at.1 >= 34 {
|
||||||
|
in_the_pocket += 1;
|
||||||
|
}
|
||||||
let key = (name, at);
|
let key = (name, at);
|
||||||
run_of_refusals = if last_refused == Some(key) { run_of_refusals + 1 } else { 1 };
|
run_of_refusals = if last_refused == Some(key) { run_of_refusals + 1 } else { 1 };
|
||||||
last_refused = Some(key);
|
last_refused = Some(key);
|
||||||
|
|
@ -3159,18 +3165,20 @@ fn the_pewter_east_pad_is_never_one_dead_button_from_the_rung_ten_checkpoint() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"part two: first pad {first_pad:?}, {refusals} refusals, longest run on one tile {longest}, \
|
"part two: first pad {first_pad:?}, {refusals} refusals ({in_the_pocket} in the pocket), \
|
||||||
left the pocket at {left:?}"
|
longest run on one tile {longest}, left the pocket at {left:?}"
|
||||||
);
|
);
|
||||||
// The live pad: `GO ROUTE` refused every hold, 740 times in ten brain minutes.
|
// The live pad: `GO ROUTE` refused every hold, 740 times in ten brain minutes.
|
||||||
assert!(
|
assert!(
|
||||||
longest <= 1,
|
longest <= 1,
|
||||||
"a button was refused {longest} holds running on one tile: the pad kept dealing it"
|
"a button was refused {longest} holds running on one tile: the pad kept dealing it"
|
||||||
);
|
);
|
||||||
assert!(refusals <= 6, "{refusals} refusals in twelve brain minutes");
|
assert!(in_the_pocket <= 2, "{in_the_pocket} refusals in the pocket");
|
||||||
let Some(left) = left else { panic!("the fly never left the pocket in twelve brain minutes") };
|
let Some(left) = left else { panic!("the fly never left the pocket in twelve brain minutes") };
|
||||||
eprintln!(
|
let minutes = f64::from(left) * MS_PER_FRAME / 60_000.0;
|
||||||
"left the pocket on frame {left} ({:.2} brain minutes)",
|
eprintln!("left the pocket on frame {left} ({minutes:.2} brain minutes)");
|
||||||
f64::from(left) * MS_PER_FRAME / 60_000.0
|
// The road east is three tiles away and resting in its window; the last resort preferred the
|
||||||
);
|
// gym's door beyond the fence. On the base the fly leaves only when the road's window lapses,
|
||||||
|
// ten brain minutes in.
|
||||||
|
assert!(minutes < 1.0, "the fly waited {minutes:.2} brain minutes for a window to lapse");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue