survey: the route probe, and a seam to rebuild a session's ledgers
FLY_PROBE_CATCH=route drives the real PokemonPalette from a checkpoint, one uniform choice per hold, prints every pad it deals and every refusal with its reason, and reads the frame the pad comes down to one refusing button on: which list emptied why, the no-window ledgers, and whether the route search reaches each way out. FLY_PROBE_SEED_* rebuild the session ledgers a restore starts empty; FLY_PROBE_RATCHET starts from the ratchet's rollback snapshot; FLY_PROBE_HOLD presses raw directions first. PokemonPalette gains inspect(), fences() and a doc(hidden) ledgers_mut() for the survey. The loop never calls them.
This commit is contained in:
parent
d5d9249ea9
commit
c9212e5498
2 changed files with 370 additions and 1 deletions
|
|
@ -165,6 +165,44 @@ impl PokemonPalette {
|
||||||
self.frontiers.len()
|
self.frontiers.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the frame exactly as the macros read it -- this session's ledgers included -- and hand
|
||||||
|
/// the state to `visit`. A survey seam (`examples/scene_probe.rs`), not a decision path: it
|
||||||
|
/// writes nothing, and what it sees is what the next `observe` would deal from.
|
||||||
|
pub fn inspect<R>(
|
||||||
|
&mut self,
|
||||||
|
memory: &mut dyn MemoryReader,
|
||||||
|
ledger: &dyn RunLedger,
|
||||||
|
visit: impl FnOnce(&mut dyn MacroState) -> R,
|
||||||
|
) -> R {
|
||||||
|
let mut state = PokeState::with_ledgers(
|
||||||
|
memory,
|
||||||
|
ledger,
|
||||||
|
&self.talked,
|
||||||
|
&self.targets,
|
||||||
|
&self.stood,
|
||||||
|
&self.areas,
|
||||||
|
&self.pushed,
|
||||||
|
)
|
||||||
|
.caching_grid(&mut self.grids)
|
||||||
|
.with_frontiers(&self.frontiers);
|
||||||
|
visit(&mut state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The session's no-window ledgers, for a survey line: the tiles the cartridge has pushed the
|
||||||
|
/// fly off and the maps whose frontier is marked unreachable.
|
||||||
|
pub fn fences(&self) -> (&Pushed, &Frontiers) {
|
||||||
|
(&self.pushed, &self.frontiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The session's ledgers, writable, for a survey that has to rebuild a live session's state
|
||||||
|
/// from a checkpoint (a restore starts them empty by design). Never called by the loop.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn ledgers_mut(
|
||||||
|
&mut self,
|
||||||
|
) -> (&mut Talked, &mut Targets, &mut Stood, &mut Pushed, &mut Frontiers) {
|
||||||
|
(&mut self.talked, &mut self.targets, &mut self.stood, &mut self.pushed, &mut self.frontiers)
|
||||||
|
}
|
||||||
|
|
||||||
/// Take whatever the machine's last finished macro earned into the session's ledgers.
|
/// Take whatever the machine's last finished macro earned into the session's ledgers.
|
||||||
fn record_talk(&mut self) {
|
fn record_talk(&mut self) {
|
||||||
if let Some((map, target)) = self.machine.take_talked() {
|
if let Some((map, target)) = self.machine.take_talked() {
|
||||||
|
|
|
||||||
|
|
@ -1337,6 +1337,324 @@ fn dialog_survey(gb: &mut Emulator, adapter: &mut PokemonRedReward, ms: &mut f64
|
||||||
println!("\n{}", separator_table(&classes));
|
println!("\n{}", separator_table(&classes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rebuild a live session's ledgers from the environment, because a restore starts them empty.
|
||||||
|
///
|
||||||
|
/// `FLY_PROBE_SEED_PUSHED="x,y;x,y"` walls tiles of the loaded map the way a scripted push-back
|
||||||
|
/// does; `FLY_PROBE_SEED_EXHAUSTED=1` marks its frontier unreachable (section 12.14);
|
||||||
|
/// `FLY_PROBE_SEED_TALKED=1` writes every person and sign on it into the talked ledger;
|
||||||
|
/// `FLY_PROBE_SEED_BLOCKED="warp2;east;south"` rests those exits for the window. The tile underfoot is
|
||||||
|
/// always recorded as stood on.
|
||||||
|
fn seed_ledgers(
|
||||||
|
macros: &mut flybrain_gb::pokemon_red::macros::PokemonPalette,
|
||||||
|
gb: &mut Emulator,
|
||||||
|
adapter: &PokemonRedReward,
|
||||||
|
ms: f64,
|
||||||
|
) {
|
||||||
|
use flybrain_gb::MacroPalette;
|
||||||
|
use flybrain_gb::pokemon_red::macros::cartridge::{Edge, ExitId, MacroState, TargetKey};
|
||||||
|
use flybrain_gb::pokemon_red::macros::{Tile, path};
|
||||||
|
|
||||||
|
let Some(player) = state::player(gb) else { return };
|
||||||
|
let map = player.map;
|
||||||
|
macros.clock(ms);
|
||||||
|
let (things, covered): (Vec<_>, Vec<Tile>) = {
|
||||||
|
let ledger = AdapterLedger(adapter);
|
||||||
|
macros.inspect(gb, &ledger, |state: &mut dyn MacroState| {
|
||||||
|
let mut all = path::person_targets(state);
|
||||||
|
all.extend(path::interactable_targets(state));
|
||||||
|
let mut covered = Vec::new();
|
||||||
|
if let Some(size) = state.map_size() {
|
||||||
|
for y in 0..size.height {
|
||||||
|
for x in 0..size.width {
|
||||||
|
// `all` seeds every tile: the live fact "no new ground for hours",
|
||||||
|
// which is the one thing that would have cleared the frontier mark.
|
||||||
|
let all = std::env::var("FLY_PROBE_SEED_STOOD").is_ok_and(|v| v == "all");
|
||||||
|
if all || state.tile_visited(x, y) {
|
||||||
|
covered.push(Tile::new(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(all, covered)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let (talked, targets, stood, pushed, frontiers) = macros.ledgers_mut();
|
||||||
|
targets.clock(ms);
|
||||||
|
// The tile underfoot is ground the live session had stood on, or its first observe here
|
||||||
|
// would read it as new ground and clear the frontier mark being rebuilt.
|
||||||
|
stood.record(map, Tile::new(player.x, player.y));
|
||||||
|
// `FLY_PROBE_SEED_STOOD=1`: every tile of this map the adapter's lifetime ledger has, as a
|
||||||
|
// session that had walked the town for an hour would have them.
|
||||||
|
if std::env::var("FLY_PROBE_SEED_STOOD").is_ok_and(|value| value == "1" || value == "all") {
|
||||||
|
for tile in &covered {
|
||||||
|
stood.record(map, *tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(tiles) = std::env::var("FLY_PROBE_SEED_PUSHED") {
|
||||||
|
for pair in tiles.split(';').filter(|pair| !pair.is_empty()) {
|
||||||
|
let mut xy = pair.split(',').map(|n| n.trim().parse::<u8>().expect("x,y"));
|
||||||
|
let (x, y) = (xy.next().expect("x"), xy.next().expect("y"));
|
||||||
|
pushed.record(map, Tile::new(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if std::env::var("FLY_PROBE_SEED_EXHAUSTED").is_ok_and(|value| value == "1") {
|
||||||
|
frontiers.record(map);
|
||||||
|
}
|
||||||
|
if std::env::var("FLY_PROBE_SEED_TALKED").is_ok_and(|value| value == "1") {
|
||||||
|
for (_, target) in &things {
|
||||||
|
talked.record(map, *target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(keys) = std::env::var("FLY_PROBE_SEED_BLOCKED") {
|
||||||
|
for key in keys.split(';').filter(|key| !key.is_empty()) {
|
||||||
|
let id = match key {
|
||||||
|
"north" => ExitId::Edge(Edge::North),
|
||||||
|
"south" => ExitId::Edge(Edge::South),
|
||||||
|
"east" => ExitId::Edge(Edge::East),
|
||||||
|
"west" => ExitId::Edge(Edge::West),
|
||||||
|
warp => ExitId::Warp(warp.trim_start_matches("warp").parse().expect("warpN")),
|
||||||
|
};
|
||||||
|
targets.record_blocked(map, TargetKey::Exit(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"\n- seeded: pushed {:?}, frontier marks {:?}, talked {} things",
|
||||||
|
pushed,
|
||||||
|
frontiers,
|
||||||
|
talked.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `FLY_PROBE_CATCH=route`. The live trap was map 2, scene `overworld`, a pad of `GO ROUTE` alone,
|
||||||
|
/// refused about 740 times per ten brain minutes for hours with no button pressed. The ledgers
|
||||||
|
/// that dealt that pad are session state and a restore starts them empty, so this *earns* them:
|
||||||
|
/// it drives the real [`PokemonPalette`] from the checkpoint, choosing uniformly among whatever the
|
||||||
|
/// scene binds once per hold (the brain's hold, not a ranking), and prints every pad it deals and
|
||||||
|
/// every refusal with its reason. When the pad is down to one button that keeps refusing, it reads
|
||||||
|
/// the frame the way the macros do -- ledgers included -- and says which list emptied why, and
|
||||||
|
/// whether the route search can reach any of the one button's goals.
|
||||||
|
///
|
||||||
|
/// `FLY_PROBE_FRAMES` bounds the drive; `FLY_PROBE_SEED` changes the choices; `FLY_PROBE_PREFER`
|
||||||
|
/// (comma-separated names) presses those buttons whenever they are dealt.
|
||||||
|
///
|
||||||
|
/// [`PokemonPalette`]: flybrain_gb::pokemon_red::macros::PokemonPalette
|
||||||
|
fn route_survey(gb: &mut Emulator, adapter: &mut PokemonRedReward, ms: &mut f64) {
|
||||||
|
use flybrain_gb::MacroPalette;
|
||||||
|
use flybrain_gb::pokemon_red::macros::cartridge::{MacroState, TargetKey};
|
||||||
|
use flybrain_gb::pokemon_red::macros::path::Way;
|
||||||
|
use flybrain_gb::pokemon_red::macros::{PokemonPalette, palette, path};
|
||||||
|
|
||||||
|
let budget = env_usize("FLY_PROBE_FRAMES", 240_000);
|
||||||
|
let mut rng = env_usize("FLY_PROBE_SEED", 20_260_923) as u32 | 1;
|
||||||
|
let hold_frames = 48usize;
|
||||||
|
let trace_frames = env_usize("FLY_PROBE_TRACE_FRAMES", 0);
|
||||||
|
// `FLY_PROBE_CATCH_AFTER=0` reads the frame at once, before any choice.
|
||||||
|
let catch_after = env_usize("FLY_PROBE_CATCH_AFTER", 20) as u32;
|
||||||
|
let prefer: Vec<String> = std::env::var("FLY_PROBE_PREFER")
|
||||||
|
.map(|value| value.split(',').map(|name| name.trim().to_string()).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut macros = PokemonPalette::new(SEED);
|
||||||
|
seed_ledgers(&mut macros, gb, adapter, *ms);
|
||||||
|
let mut running = false;
|
||||||
|
let mut since_decision = hold_frames;
|
||||||
|
let mut last_pad = String::new();
|
||||||
|
let mut refusals: BTreeMap<String, u64> = BTreeMap::new();
|
||||||
|
let mut outcomes: BTreeMap<String, u64> = BTreeMap::new();
|
||||||
|
let mut single_refusals = 0u32;
|
||||||
|
let mut caught_at: Option<usize> = None;
|
||||||
|
|
||||||
|
// `FLY_PROBE_HOLD=right:96,up:32` holds raw directions first and prints where the fly is
|
||||||
|
// every eight frames: what the cartridge does with a press, before any macro is asked.
|
||||||
|
if let Ok(holds) = std::env::var("FLY_PROBE_HOLD") {
|
||||||
|
println!("\n## Raw holds before the drive\n\n```");
|
||||||
|
for hold in holds.split(',') {
|
||||||
|
let (name, frames) = hold.split_once(':').unwrap_or((hold, "32"));
|
||||||
|
let mask = match name {
|
||||||
|
"right" => flybrain_gb::buttons::RIGHT,
|
||||||
|
"left" => flybrain_gb::buttons::LEFT,
|
||||||
|
"up" => flybrain_gb::buttons::UP,
|
||||||
|
"down" => flybrain_gb::buttons::DOWN,
|
||||||
|
"a" => flybrain_gb::buttons::A,
|
||||||
|
"b" => flybrain_gb::buttons::B,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
for index in 0..frames.parse::<usize>().unwrap_or(32) {
|
||||||
|
gb.set_buttons(mask);
|
||||||
|
gb.run_frame().expect("a frame should complete");
|
||||||
|
*ms += MS_PER_FRAME;
|
||||||
|
adapter.sample(gb, *ms);
|
||||||
|
if index % 8 == 7 {
|
||||||
|
println!(
|
||||||
|
"{name:>5} +{index:<3} {:?} scene {:?} sim={} flags5={:#04x} joyignore={:#04x}",
|
||||||
|
state::player(gb).map(|p| (p.map, p.x, p.y, p.facing)),
|
||||||
|
scene::detect(gb),
|
||||||
|
gb.read8(ram::wSimulatedJoypadStatesIndex),
|
||||||
|
gb.read8(ram::wStatusFlags5),
|
||||||
|
gb.read8(ram::wJoyIgnore),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("```");
|
||||||
|
}
|
||||||
|
println!("\n## The drive: the real palette, one uniform choice per hold\n\n```");
|
||||||
|
for frame in 0..budget {
|
||||||
|
macros.clock(*ms);
|
||||||
|
let observed = {
|
||||||
|
let ledger = AdapterLedger(&*adapter);
|
||||||
|
macros.observe(gb, &ledger)
|
||||||
|
};
|
||||||
|
let names: Vec<&str> = observed.bindings.iter().map(|binding| binding.name).collect();
|
||||||
|
let player = state::player(gb);
|
||||||
|
let pad = format!("{:?} {names:?}", observed.scene);
|
||||||
|
if pad != last_pad {
|
||||||
|
println!("f{frame:<6} {:?} pad {pad}", player.map(|p| (p.map, p.x, p.y)));
|
||||||
|
last_pad = pad;
|
||||||
|
}
|
||||||
|
let mut mask = 0u8;
|
||||||
|
if running {
|
||||||
|
let ledger = AdapterLedger(&*adapter);
|
||||||
|
match macros.step(gb, &ledger) {
|
||||||
|
Some(held) => mask = held,
|
||||||
|
None => running = false,
|
||||||
|
}
|
||||||
|
} else if since_decision >= hold_frames && !observed.bindings.is_empty() {
|
||||||
|
since_decision = 0;
|
||||||
|
rng ^= rng << 13;
|
||||||
|
rng ^= rng >> 17;
|
||||||
|
rng ^= rng << 5;
|
||||||
|
// `FLY_PROBE_PREFER` names buttons to press whenever they are dealt, first one first:
|
||||||
|
// the live brain's measured favourites, so the survey can earn the ledgers the live
|
||||||
|
// session earned. A survey's driver, never the fly's: nothing in the crate reads it.
|
||||||
|
let preferred = prefer
|
||||||
|
.iter()
|
||||||
|
.find_map(|want| observed.bindings.iter().find(|binding| binding.name == want));
|
||||||
|
let binding = preferred
|
||||||
|
.unwrap_or(&observed.bindings[rng as usize % observed.bindings.len()]);
|
||||||
|
let ledger = AdapterLedger(&*adapter);
|
||||||
|
match macros.start(binding.slot, gb, &ledger) {
|
||||||
|
flybrain_gb::Started::Running(_) => {
|
||||||
|
running = true;
|
||||||
|
if let Some(held) = macros.step(gb, &ledger) {
|
||||||
|
mask = held;
|
||||||
|
} else {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flybrain_gb::Started::Refused { name, reason } => {
|
||||||
|
let key = format!("{} {reason}", name.unwrap_or("-"));
|
||||||
|
*refusals.entry(key.clone()).or_default() += 1;
|
||||||
|
if names.len() == 1 && name.is_some() {
|
||||||
|
single_refusals += 1;
|
||||||
|
} else {
|
||||||
|
single_refusals = 0;
|
||||||
|
}
|
||||||
|
if refusals[&key] <= 3 || single_refusals == 1 {
|
||||||
|
println!(
|
||||||
|
"f{frame:<6} {:?} refused {key} (pad {names:?})",
|
||||||
|
player.map(|p| (p.map, p.x, p.y))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((name, outcome)) = macros.take_finished() {
|
||||||
|
*outcomes.entry(format!("{name} {outcome:?}")).or_default() += 1;
|
||||||
|
if !matches!(outcome, flybrain_gb::Outcome::Done) {
|
||||||
|
println!(
|
||||||
|
"f{frame:<6} {:?} {name} {outcome:?}",
|
||||||
|
state::player(gb).map(|p| (p.map, p.x, p.y))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
since_decision += 1;
|
||||||
|
if frame < trace_frames {
|
||||||
|
println!(
|
||||||
|
" t{frame:<5} {:?} mask {mask:#04x} running {:?} marks {:?} stood {}",
|
||||||
|
state::player(gb).map(|p| (p.map, p.x, p.y, p.facing)),
|
||||||
|
macros.running(),
|
||||||
|
macros.fences().1,
|
||||||
|
macros.stood()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
gb.set_buttons(mask);
|
||||||
|
gb.run_frame().expect("a frame should complete");
|
||||||
|
*ms += MS_PER_FRAME;
|
||||||
|
adapter.sample(gb, *ms);
|
||||||
|
if single_refusals >= catch_after {
|
||||||
|
caught_at = Some(frame);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("```\n");
|
||||||
|
println!("- refusals: {refusals:?}");
|
||||||
|
println!("- outcomes: {outcomes:?}");
|
||||||
|
let Some(frame) = caught_at else {
|
||||||
|
println!("- pushed tiles at the end (no window): {:?}", macros.fences().0);
|
||||||
|
println!("\nThe pad never came down to one refusing button in {budget} frames.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"\n## Caught on frame {frame} ({:.1} brain minutes): one button, refused twenty holds running\n",
|
||||||
|
frame as f64 * MS_PER_FRAME / 60_000.0
|
||||||
|
);
|
||||||
|
let (pushed, frontiers) = macros.fences();
|
||||||
|
println!("- pushed tiles (no window): {pushed:?}");
|
||||||
|
println!("- frontier marks (no window): {frontiers:?}");
|
||||||
|
let ledger = AdapterLedger(&*adapter);
|
||||||
|
macros.inspect(gb, &ledger, |state: &mut dyn MacroState| {
|
||||||
|
let player = state.player().expect("a loaded map");
|
||||||
|
println!("- player: {player:?}");
|
||||||
|
println!("- objective: {:?}", state.objective());
|
||||||
|
println!("- `objective_goals`: {:?}", palette::objective_goals(state));
|
||||||
|
println!("- `objective_targets`: {:?}", palette::objective_targets(state));
|
||||||
|
println!("- `untalked_people`: {:?}", palette::untalked_people(state));
|
||||||
|
println!("- `untalked_objects`: {:?}", palette::untalked_objects(state));
|
||||||
|
println!("- `facing_untalked`: {}", palette::facing_untalked(state));
|
||||||
|
println!("- `frontier_aims`: {} tiles", palette::frontier_aims(state).len());
|
||||||
|
println!("- `frontier_exhausted`: {}", state.frontier_exhausted());
|
||||||
|
println!("- `stranded`: {}", palette::stranded(state));
|
||||||
|
for way in [Way::Exit, Way::Passage, Way::Route] {
|
||||||
|
println!("- `ways({way:?})`: {:?}", palette::ways(state, way).iter().map(|exit| (exit.id, exit.tile)).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
|
println!("\n### Every way out, and whether the route search reaches it from here\n");
|
||||||
|
for exit in path::exits(state) {
|
||||||
|
let reach = path::route(state, &[exit.tile]).map(|route| route.goal.is_some());
|
||||||
|
println!(
|
||||||
|
"- {:?} at ({:2},{:2}) way {:?} -> {:?}: map_visited {:?}, blocked {}, reachable {:?}",
|
||||||
|
exit.id,
|
||||||
|
exit.tile.x,
|
||||||
|
exit.tile.y,
|
||||||
|
exit.way,
|
||||||
|
exit.destination(player.map),
|
||||||
|
exit.destination(player.map).map(|map| state.map_visited(map)),
|
||||||
|
state.blocked(TargetKey::Exit(exit.id)),
|
||||||
|
reach
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("\n### The fly's own neighbourhood (pushed = `P`, player = `@`)\n\n```");
|
||||||
|
for y in player.y.saturating_sub(3)..=player.y.saturating_add(3) {
|
||||||
|
let row: String = (player.x.saturating_sub(6)..=player.x.saturating_add(6))
|
||||||
|
.map(|x| {
|
||||||
|
if x == player.x && y == player.y {
|
||||||
|
'@'
|
||||||
|
} else if state.pushed_tile(x, y) {
|
||||||
|
'P'
|
||||||
|
} else {
|
||||||
|
match state.walkable(x, y) {
|
||||||
|
flybrain_gb::pokemon_red::macros::state::Walkable::Yes => '.',
|
||||||
|
flybrain_gb::pokemon_red::macros::state::Walkable::No => '#',
|
||||||
|
flybrain_gb::pokemon_red::macros::state::Walkable::Unknown => '?',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
println!("y{y:2} x{:2}.. {row}", player.x.saturating_sub(6));
|
||||||
|
}
|
||||||
|
println!("```");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// How the candidate readings of "a two-option box is up" separate the frames of a survey.
|
/// How the candidate readings of "a two-option box is up" separate the frames of a survey.
|
||||||
///
|
///
|
||||||
/// Three columns, because three things could say it and only a measurement says which: the
|
/// Three columns, because three things could say it and only a measurement says which: the
|
||||||
|
|
@ -1372,7 +1690,13 @@ fn main() {
|
||||||
let mut gb = Emulator::new(&rom, DEFAULT_AUDIO_FREQUENCY, DEFAULT_AUDIO_FRAMES)
|
let mut gb = Emulator::new(&rom, DEFAULT_AUDIO_FREQUENCY, DEFAULT_AUDIO_FRAMES)
|
||||||
.expect("binjgb should accept the cartridge");
|
.expect("binjgb should accept the cartridge");
|
||||||
let mut adapter = PokemonRedReward::new();
|
let mut adapter = PokemonRedReward::new();
|
||||||
gb.import_state(&checkpoint.runtime.emulator).expect("the checkpoint's emulator state");
|
// `FLY_PROBE_RATCHET=1` starts from the ratchet's best snapshot instead: where a "Stuck"
|
||||||
|
// rollback puts the fly, which is where a live session that spent its rollbacks resumed from.
|
||||||
|
if std::env::var("FLY_PROBE_RATCHET").is_ok_and(|value| value == "1") {
|
||||||
|
gb.import_state(&checkpoint.runtime.ratchet_game).expect("the ratchet's snapshot");
|
||||||
|
} else {
|
||||||
|
gb.import_state(&checkpoint.runtime.emulator).expect("the checkpoint's emulator state");
|
||||||
|
}
|
||||||
adapter.import_state(&checkpoint.runtime.reward).expect("the checkpoint's reward ledger");
|
adapter.import_state(&checkpoint.runtime.reward).expect("the checkpoint's reward ledger");
|
||||||
|
|
||||||
let channels = flybrain_gb::macro_channels("pokemon-red");
|
let channels = flybrain_gb::macro_channels("pokemon-red");
|
||||||
|
|
@ -1436,6 +1760,13 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row 57's pad survey: earn the session's ledgers from the checkpoint with the real palette,
|
||||||
|
// and read the frame the pad comes down to one refusing button on.
|
||||||
|
if std::env::var("FLY_PROBE_CATCH").is_ok_and(|value| value == "route") {
|
||||||
|
route_survey(&mut gb, &mut adapter, &mut ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let budget = env_usize("FLY_PROBE_FRAMES", 200_000);
|
let budget = env_usize("FLY_PROBE_FRAMES", 200_000);
|
||||||
let stuck_after = env_usize("FLY_PROBE_STUCK", 600);
|
let stuck_after = env_usize("FLY_PROBE_STUCK", 600);
|
||||||
let mut next_burst = ms;
|
let mut next_burst = ms;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue