flysim: the map grid in the probes
scene_probe prints the grid s size, its walkable count, the count reachable from where the fly stands and the count never stood on, draws the ground with the reading it actually used, and names the refusal when there is no grid. trap_hunt carries the same line into every trace line and into the summary, so a stalled walk can be read at a glance: a fly with 719 walkable tiles and 4 reachable ones is fenced in and no re-plan will help it.
This commit is contained in:
parent
bcae388d99
commit
246e0c7d4c
2 changed files with 97 additions and 4 deletions
|
|
@ -157,6 +157,10 @@ fn pad(gb: &mut Emulator, adapter: &PokemonRedReward, label: &str) {
|
|||
use flybrain_gb::pokemon_red::macros::{geography, palette, path, plan};
|
||||
use flybrain_gb::pokemon_red::macros::state::Walkable;
|
||||
|
||||
// Why the grid could not be decoded, read before the state borrows the emulator: it is the
|
||||
// same call `MacroState::map_grid` makes, and the only reading that can say *which* of section
|
||||
// 15's refusals a frame is.
|
||||
let refusal = flybrain_gb::pokemon_red::state::map_grid(gb).err();
|
||||
let ledger = AdapterLedger(adapter);
|
||||
let mut poke = flybrain_gb::pokemon_red::state::PokeState::with_ledger(gb, &ledger);
|
||||
let state: &mut dyn MacroState = &mut poke;
|
||||
|
|
@ -182,16 +186,52 @@ fn pad(gb: &mut Emulator, adapter: &PokemonRedReward, label: &str) {
|
|||
let names: Vec<&str> =
|
||||
plan.slots.iter().flatten().map(|spec| spec.name).collect();
|
||||
println!("- the pad: {names:?}");
|
||||
// The whole-map grid (`docs/design/macros.md` section 15), which is what the walks plan over
|
||||
// now. Three numbers read a stalled walk: how much of the map is ground, how much of that the
|
||||
// fly can actually get to from where it stands, and how much of *that* it has never stood on.
|
||||
// A fly with 600 walkable tiles and 4 reachable ones is fenced in and no re-plan will help.
|
||||
match state.map_grid() {
|
||||
None => println!(
|
||||
"- the map grid: none ({})",
|
||||
refusal.map_or("unknown", |refusal| refusal.label())
|
||||
),
|
||||
Some(grid) => {
|
||||
let unstood = grid
|
||||
.walkable_tiles()
|
||||
.into_iter()
|
||||
.filter(|(x, y)| !state.tile_visited(*x, *y))
|
||||
.count();
|
||||
println!(
|
||||
"- the map grid: {}x{} walkable {} reachable {} unstood {} unknown {}",
|
||||
grid.width(),
|
||||
grid.height(),
|
||||
grid.walkable_count(),
|
||||
grid.reachable_from(player.x, player.y),
|
||||
unstood,
|
||||
grid.unknown_count()
|
||||
);
|
||||
}
|
||||
}
|
||||
// The `v` column is the *adapter's* lifetime exploration ledger and nothing else. The
|
||||
// session's own stood ledger (`docs/design/macros.md` section 12.7) is owned by the driver's
|
||||
// palette, which this probe does not reach into, so a doormat the running fly has already
|
||||
// marked still prints as unrecorded here. That is the point of the column: it shows what the
|
||||
// reward ledger can and cannot answer.
|
||||
println!("\n### The ground (`v` = the adapter's lifetime ledger only)\n\n```");
|
||||
// The grid's reading of the ground where there is one, the window's otherwise, said out loud
|
||||
// so the map below cannot be mistaken for the other reading.
|
||||
let grid = state.map_grid();
|
||||
println!(
|
||||
"\n### The ground, as the {} reads it (`v` = the adapter's lifetime ledger only)\n\n```",
|
||||
if grid.is_some() { "map grid" } else { "ten-by-nine window" }
|
||||
);
|
||||
for y in 0..size.height {
|
||||
let row: Vec<String> = (0..size.width)
|
||||
.map(|x| {
|
||||
let walk = match state.walkable(x, y) {
|
||||
let answer = match grid.as_deref() {
|
||||
Some(grid) => grid.walkable(x, y),
|
||||
None => state.walkable(x, y),
|
||||
};
|
||||
let walk = match answer {
|
||||
Walkable::Yes => '.',
|
||||
Walkable::No => '#',
|
||||
Walkable::Unknown => '?',
|
||||
|
|
@ -208,7 +248,11 @@ fn pad(gb: &mut Emulator, adapter: &PokemonRedReward, label: &str) {
|
|||
let mut n = 0;
|
||||
for y in 0..size.height {
|
||||
for x in 0..size.width {
|
||||
if state.walkable(x, y) == Walkable::Yes && !state.tile_visited(x, y) {
|
||||
let answer = match grid.as_deref() {
|
||||
Some(grid) => grid.walkable(x, y),
|
||||
None => state.walkable(x, y),
|
||||
};
|
||||
if answer == Walkable::Yes && !state.tile_visited(x, y) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,12 @@ struct Trace {
|
|||
/// Every input of the detector's disputed branch on the last frame
|
||||
/// (`pokemon_red::scene::why_unknown`).
|
||||
ended_why: String,
|
||||
/// The whole-map grid on the last frame, as [`grid_line`] reads it
|
||||
/// (`docs/design/macros.md` section 15): how much of the map is ground, how much of it the fly
|
||||
/// could reach from where it stopped, and how much of that it had never stood on. A hunt that
|
||||
/// ends with reachable far below walkable ended fenced in, which no amount of re-planning was
|
||||
/// ever going to fix.
|
||||
ended_grid: String,
|
||||
/// How the dialog branch's two halves agreed, per frame: `wFontLoaded`'s bit against the four
|
||||
/// corners and against the whole `TextBoxBorder` (`pokemon_red::state::dialog_border`).
|
||||
///
|
||||
|
|
@ -346,6 +352,7 @@ fn run(
|
|||
ended_in: ("", String::new()),
|
||||
longest_scene: BTreeMap::new(),
|
||||
ended_why: String::new(),
|
||||
ended_grid: String::new(),
|
||||
font_corners_border: 0,
|
||||
font_corners_no_border: 0,
|
||||
font_no_corners: 0,
|
||||
|
|
@ -504,9 +511,10 @@ fn run(
|
|||
let ahead = Tile::new(player.x, player.y).step(player.facing)?;
|
||||
flybrain_gb::pokemon_red::macros::path::target_at(state, ahead)
|
||||
});
|
||||
let ground = grid_line(state, player);
|
||||
let why = flybrain_gb::pokemon_red::scene::why_unknown(&mut emulator);
|
||||
println!(
|
||||
"trace {:7.2} min scene={scene:<9} player={player:?} ahead={ahead:?}\n {why}",
|
||||
"trace {:7.2} min scene={scene:<9} player={player:?} ahead={ahead:?}\n {why}\n {ground}",
|
||||
(ms - began_ms) / MINUTE_MS
|
||||
);
|
||||
}
|
||||
|
|
@ -577,6 +585,13 @@ fn run(
|
|||
adapter.mode().to_string(),
|
||||
);
|
||||
trace.ended_why = flybrain_gb::pokemon_red::scene::why_unknown(&mut emulator);
|
||||
trace.ended_grid = {
|
||||
use flybrain_gb::pokemon_red::macros::cartridge::MacroState;
|
||||
let mut state = flybrain_gb::pokemon_red::state::PokeState::new(&mut emulator);
|
||||
let state: &mut dyn MacroState = &mut state;
|
||||
let player = state.player();
|
||||
grid_line(state, player)
|
||||
};
|
||||
trace.ended_ms = agent.network.ms;
|
||||
trace.wall_seconds = began_wall.elapsed().as_secs_f64();
|
||||
trace
|
||||
|
|
@ -675,6 +690,39 @@ fn walk_report(trace: &Trace) {
|
|||
}
|
||||
}
|
||||
|
||||
/// The whole-map grid in one line: what a stalled walk looks like from outside.
|
||||
///
|
||||
/// `docs/design/macros.md` section 15. Walkable is how much of the map is ground, reachable is
|
||||
/// how much of that the fly can get to from where it is standing (the directed walls respected),
|
||||
/// and unstood is how much of *that* this run has never been on -- which is the frontier's own
|
||||
/// candidate pool. A walk that cannot finish is one of three shapes and these numbers tell them
|
||||
/// apart: fenced in (reachable far below walkable), nothing left to explore (unstood zero), or no
|
||||
/// grid at all, in which case the walks are back on the ten-by-nine window and the reason is
|
||||
/// named.
|
||||
fn grid_line(
|
||||
state: &mut dyn flybrain_gb::pokemon_red::macros::cartridge::MacroState,
|
||||
player: Option<flybrain_gb::pokemon_red::macros::state::Player>,
|
||||
) -> String {
|
||||
let Some(player) = player else { return "grid: no player".to_string() };
|
||||
let Some(grid) = state.map_grid() else {
|
||||
return "grid: none".to_string();
|
||||
};
|
||||
let unstood = grid
|
||||
.walkable_tiles()
|
||||
.into_iter()
|
||||
.filter(|(x, y)| !state.tile_visited(*x, *y))
|
||||
.count();
|
||||
format!(
|
||||
"grid map={:#04x} {}x{} walkable={} reachable={} unstood={}",
|
||||
grid.map(),
|
||||
grid.width(),
|
||||
grid.height(),
|
||||
grid.walkable_count(),
|
||||
grid.reachable_from(player.x, player.y),
|
||||
unstood
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let Some(path) = std::env::var_os("FLY_ROM") else {
|
||||
println!(
|
||||
|
|
@ -749,6 +797,7 @@ fn main() {
|
|||
println!("| --- | ---: |");
|
||||
println!("| rung reached | {} |", trace.rungs.iter().map(|(rank, ..)| *rank).max().unwrap_or(0));
|
||||
println!("| distinct (map, tile) | {} |", ground.len());
|
||||
println!("| the map at the end | {} |", trace.ended_grid);
|
||||
println!("| macros started | {} |", trace.starts.len());
|
||||
for (outcome, count) in &trace.outcomes {
|
||||
println!("| {outcome} | {count} |");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue