probes: a mid-step survey, and the errands on the pad dump
`FLY_PROBE_CATCH=step` holds one direction from a checkpoint and prints, per frame, the coordinates, the grid's verdict, the tiles the cross-check disagreed on and every candidate for "a step is in progress". It is what row 54 turns on: whether the coordinates change at the start of a step or at its end. The pad dump gains section 13's two errands -- which building each names, whether the ledgers have paid it, and what the walk would aim at -- because an aim with no press settles where it stands.
This commit is contained in:
parent
cb9a88c7a3
commit
ed1d321b02
1 changed files with 135 additions and 0 deletions
|
|
@ -313,6 +313,27 @@ fn pad(gb: &mut Emulator, adapter: &PokemonRedReward, label: &str) {
|
||||||
ways.iter().map(|exit| exit.id).collect::<Vec<_>>()
|
ways.iter().map(|exit| exit.id).collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Section 13's two errands, which is what row 54's `GO HEAL` cycle turns on: which building
|
||||||
|
// the errand names, whether the ledgers have paid it, and what the walk would aim at. An aim
|
||||||
|
// with no press settles where it stands, so an aim on the fly's own tile is a macro that
|
||||||
|
// completes without moving.
|
||||||
|
println!("\n### The errands\n");
|
||||||
|
println!("- `area_here` = {:?}", palette::area_here(state));
|
||||||
|
for kind in [geography::Amenity::Mart, geography::Amenity::Center] {
|
||||||
|
let at = geography::amenity_of(palette::area_here(state).unwrap_or(0), kind);
|
||||||
|
let paid = at.is_some_and(|map| state.map_visited(map));
|
||||||
|
println!(
|
||||||
|
"- {kind:?}: building {:?} (map_visited {paid}), `errand` = {:?}, `amenity_goals` = {:?}",
|
||||||
|
at,
|
||||||
|
palette::errand(state, kind),
|
||||||
|
palette::amenity_goals(state, kind),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("- `counter_pending` = {}", palette::counter_pending(state));
|
||||||
|
println!("- `heal_goals` = {:?}", palette::heal_goals(state));
|
||||||
|
println!("- `errand_place` = {:?}", palette::errand_place(state));
|
||||||
|
println!("- `stranded` = {}", palette::stranded(state));
|
||||||
|
println!();
|
||||||
println!("- `objective_goals` = {:?}", palette::objective_goals(state));
|
println!("- `objective_goals` = {:?}", palette::objective_goals(state));
|
||||||
println!("- `objective_targets` = {:?}", palette::objective_targets(state));
|
println!("- `objective_targets` = {:?}", palette::objective_targets(state));
|
||||||
println!("- `untalked_people` = {:?}", palette::untalked_people(state));
|
println!("- `untalked_people` = {:?}", palette::untalked_people(state));
|
||||||
|
|
@ -482,6 +503,113 @@ fn nurse_survey(gb: &mut Emulator, adapter: &mut PokemonRedReward, ms: &mut f64)
|
||||||
println!("```");
|
println!("```");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The survey the whole-map grid's mid-step refusal turns on (`infra/docs/macros-traps.md` row 54).
|
||||||
|
///
|
||||||
|
/// Section 15 checks the decode against the screen buffer over the fly's own tile and its four
|
||||||
|
/// neighbours, and the residual of 2026-09-22 measured that check refusing on **every frame the
|
||||||
|
/// fly is mid-step**: standing still Pewter City decoded on 118 of 120 frames, and the one frame
|
||||||
|
/// the survey caught disagreed by exactly one tile row in the direction of travel. A walk planned
|
||||||
|
/// on such a frame is planned over the ten-by-nine window, which is the oscillation of row 23.
|
||||||
|
///
|
||||||
|
/// Two things have to be measured before that can be fixed honestly, and neither can be argued
|
||||||
|
/// from the disassembly alone:
|
||||||
|
///
|
||||||
|
/// 1. **when `wXCoord` / `wYCoord` change** — at the start of a step or at the end of it. That
|
||||||
|
/// decides whether the screen is behind the coordinates or the coordinates ahead of the screen.
|
||||||
|
/// 2. **which byte says "a step is in progress"**. `docs/design/macros-wram.md` says the reviewed
|
||||||
|
/// symbol list carries none, so every plausible candidate is dumped across a whole step and the
|
||||||
|
/// one that tracks it is the reading.
|
||||||
|
///
|
||||||
|
/// It holds one direction from the checkpoint and prints a line per frame: the coordinates, the
|
||||||
|
/// grid's verdict, the tiles the cross-check disagreed on, and the candidates.
|
||||||
|
fn step_survey(gb: &mut Emulator, adapter: &mut PokemonRedReward, ms: &mut f64) {
|
||||||
|
use flybrain_gb::pokemon_red::macros::state::Walkable;
|
||||||
|
|
||||||
|
let frames = env_usize("FLY_PROBE_STEP_FRAMES", 96);
|
||||||
|
let step = |gb: &mut Emulator, adapter: &mut PokemonRedReward, ms: &mut f64, mask: u8| {
|
||||||
|
gb.set_buttons(mask);
|
||||||
|
gb.run_frame().expect("a frame should complete");
|
||||||
|
*ms += MS_PER_FRAME;
|
||||||
|
adapter.sample(gb, *ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Somewhere the fly is its own master and the map is on screen, so that a refusal below is
|
||||||
|
// about the step and not about a text box.
|
||||||
|
for _ in 0..600 {
|
||||||
|
if scene::detect(gb) == scene::Scene::Overworld && state::controllable(gb) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
step(gb, adapter, ms, flybrain_gb::buttons::B);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(here) = state::player(gb) else {
|
||||||
|
println!("\nNo player at the checkpoint, so there is no step to survey.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
println!("\n## The mid-step survey, on map {:#04x} from ({}, {})\n", here.map, here.x, here.y);
|
||||||
|
|
||||||
|
// A direction with walkable ground on the other side of it, so the hold is a step rather than
|
||||||
|
// a turn into a wall.
|
||||||
|
let facings = [
|
||||||
|
(flybrain_gb::buttons::DOWN, 0i16, 1i16, "DOWN"),
|
||||||
|
(flybrain_gb::buttons::UP, 0, -1, "UP"),
|
||||||
|
(flybrain_gb::buttons::LEFT, -1, 0, "LEFT"),
|
||||||
|
(flybrain_gb::buttons::RIGHT, 1, 0, "RIGHT"),
|
||||||
|
];
|
||||||
|
let mut chosen = None;
|
||||||
|
for (mask, dx, dy, name) in facings {
|
||||||
|
let (Ok(x), Ok(y)) =
|
||||||
|
(u8::try_from(i16::from(here.x) + dx), u8::try_from(i16::from(here.y) + dy))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if state::walkable(gb, x, y) == Walkable::Yes {
|
||||||
|
chosen = Some((mask, name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some((mask, name)) = chosen else {
|
||||||
|
println!("Every neighbour of the fly is a wall, so there is no step to survey.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
println!("Holding {name} for {frames} frames.\n");
|
||||||
|
println!("```");
|
||||||
|
println!(
|
||||||
|
"frame coords grid s1+1,+3,+5,+7,+8,+9 scy scx cfc5 d730 d736"
|
||||||
|
);
|
||||||
|
for frame in 0..frames {
|
||||||
|
let sprite: Vec<String> = [1u16, 3, 5, 7, 8, 9]
|
||||||
|
.into_iter()
|
||||||
|
.map(|offset| format!("{:02x}", gb.read8(ram::wSpriteStateData1 + offset)))
|
||||||
|
.collect();
|
||||||
|
let scy = gb.read8(0xff42);
|
||||||
|
let scx = gb.read8(0xff43);
|
||||||
|
let cfc5 = gb.read8(0xcfc5);
|
||||||
|
let d730 = gb.read8(ram::wStatusFlags5);
|
||||||
|
let d736 = gb.read8(ram::wMovementFlags);
|
||||||
|
let verdict = match state::map_grid(gb) {
|
||||||
|
Ok(_) => "ok".to_string(),
|
||||||
|
Err(refusal) => {
|
||||||
|
let shown: Vec<String> = state::grid_disagreement(gb)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, _, decoded, screen)| decoded != screen)
|
||||||
|
.map(|(x, y, decoded, screen)| format!("({x},{y}){decoded:?}/{screen:?}"))
|
||||||
|
.collect();
|
||||||
|
format!("{} {}", refusal.label(), shown.join(" "))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let coords = state::player(gb)
|
||||||
|
.map(|player| format!("({:>2},{:>2})", player.x, player.y))
|
||||||
|
.unwrap_or_else(|| " none ".to_string());
|
||||||
|
println!(
|
||||||
|
"{frame:>5} {coords} {verdict:<30} {} {scy:>3} {scx:>3} {cfc5:02x} {d730:02x} {d736:02x}",
|
||||||
|
sprite.join(",")
|
||||||
|
);
|
||||||
|
step(gb, adapter, ms, mask);
|
||||||
|
}
|
||||||
|
println!("```");
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let Some(path) = std::env::var_os("FLY_ROM") else {
|
let Some(path) = std::env::var_os("FLY_ROM") else {
|
||||||
println!("FLY_ROM is not set, so there is nothing to probe.");
|
println!("FLY_ROM is not set, so there is nothing to probe.");
|
||||||
|
|
@ -527,6 +655,13 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row 54's mid-step survey: hold one direction and watch the grid's cross-check, the
|
||||||
|
// coordinates and every candidate for "a step is in progress" across a whole step.
|
||||||
|
if std::env::var("FLY_PROBE_CATCH").is_ok_and(|value| value == "step") {
|
||||||
|
step_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