macros: the tile a step is landing on is ground the run has covered
The other half of the same measurement. `wXCoord` and `wYCoord` are the tile the step began on until the frame it ends, so the stood ledger recorded ground the fly had already left and the tile under it stayed unstood: `path::frontier` kept offering it, `GO FRONTIER` was dealt aiming one tile away, and `Arrival::Step` reported `done` the instant the step it did not make landed. A macro that completes without changing anything, which is section 12.2's trap. A step that has begun always finishes -- the cartridge owns the animation and no press stops it -- and the screen has already centred on the tile, so it is ground this run has covered. It clears the map's frontier mark on the same rule the coordinates do: only ground never stood on before.
This commit is contained in:
parent
31f35c6f2f
commit
895fb52cda
2 changed files with 58 additions and 3 deletions
|
|
@ -438,6 +438,20 @@ pub trait MacroState: GameState {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The tile the fly is stepping onto, or `None` while it is standing still.
|
||||||
|
///
|
||||||
|
/// **Row 54 of `infra/docs/macros-traps.md`, measured on the cartridge.** `wXCoord` and
|
||||||
|
/// `wYCoord` change at the *end* of a step, so for fifteen frames of every sixteen the fly's
|
||||||
|
/// coordinates are the tile it has already left. The ground under it is unrecorded for all of
|
||||||
|
/// them, `path::frontier` keeps offering it, and `GO FRONTIER` is dealt aiming one tile away
|
||||||
|
/// -- a walk that reports `done` the instant the step it did not make lands.
|
||||||
|
///
|
||||||
|
/// The default is `None`, i.e. never mid-step, which is the narrowing every other default in
|
||||||
|
/// this trait is: the stood ledger keeps the coordinates alone, which is what it did before.
|
||||||
|
fn stepping_onto(&mut self) -> Option<Tile> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this run has already talked to `target` on the map that is loaded.
|
/// Whether this run has already talked to `target` on the map that is loaded.
|
||||||
///
|
///
|
||||||
/// `docs/design/macros.md` section 12's talked ledger, and the observable that ends the
|
/// `docs/design/macros.md` section 12's talked ledger, and the observable that ends the
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ impl MacroPalette for PokemonPalette {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observe(&mut self, memory: &mut dyn MemoryReader, ledger: &dyn RunLedger) -> Observed {
|
fn observe(&mut self, memory: &mut dyn MemoryReader, ledger: &dyn RunLedger) -> Observed {
|
||||||
let (scene, bindings, standing, approach) = {
|
let (scene, bindings, standing, stepping, approach) = {
|
||||||
let Self {
|
let Self {
|
||||||
machine,
|
machine,
|
||||||
mode,
|
mode,
|
||||||
|
|
@ -237,6 +237,9 @@ impl MacroPalette for PokemonPalette {
|
||||||
// -- the coordinates and the loaded map header are from different frames, and a tile
|
// -- the coordinates and the loaded map header are from different frames, and a tile
|
||||||
// recorded from that pair is a tile of nowhere.
|
// recorded from that pair is a tile of nowhere.
|
||||||
let standing = (!state.scripted()).then(|| state.player()).flatten();
|
let standing = (!state.scripted()).then(|| state.player()).flatten();
|
||||||
|
// And the tile the step in flight is landing on (row 54). Read from the same frame and
|
||||||
|
// behind the same "the fly is its own master" gate as the ground itself.
|
||||||
|
let stepping = standing.and_then(|_| state.stepping_onto());
|
||||||
// How far the objective is, over the same map graph `GO OBJECTIVE` walks (section
|
// How far the objective is, over the same map graph `GO OBJECTIVE` walks (section
|
||||||
// 12.15). Read from the same frame and the same state everything else is, and only
|
// 12.15). Read from the same frame and the same state everything else is, and only
|
||||||
// where the fly is its own master, for the same reason the ground is.
|
// where the fly is its own master, for the same reason the ground is.
|
||||||
|
|
@ -247,7 +250,7 @@ impl MacroPalette for PokemonPalette {
|
||||||
Some((objective.map, hops))
|
Some((objective.map, hops))
|
||||||
});
|
});
|
||||||
*cached = Some(palette);
|
*cached = Some(palette);
|
||||||
(scene, bindings, standing, approach)
|
(scene, bindings, standing, stepping, approach)
|
||||||
};
|
};
|
||||||
// Section 12.7: the macro layer's own answer to "has the run stood here", because the
|
// Section 12.7: the macro layer's own answer to "has the run stood here", because the
|
||||||
// adapter's reward ledger cannot record a doormat.
|
// adapter's reward ledger cannot record a doormat.
|
||||||
|
|
@ -258,6 +261,16 @@ impl MacroPalette for PokemonPalette {
|
||||||
if self.stood.record(player.map, Tile::new(player.x, player.y)) {
|
if self.stood.record(player.map, Tile::new(player.x, player.y)) {
|
||||||
self.frontiers.clear(player.map);
|
self.frontiers.clear(player.map);
|
||||||
}
|
}
|
||||||
|
// The tile a step in flight is landing on is ground this run has covered: the
|
||||||
|
// cartridge owns the animation and no press stops it, and the screen has already
|
||||||
|
// centred on it. Without this the fly's own next tile is a frontier for the fifteen
|
||||||
|
// frames it takes to get there, which `GO FRONTIER` arrives at without moving
|
||||||
|
// (`infra/docs/macros-traps.md` row 54).
|
||||||
|
if let Some(onto) = stepping
|
||||||
|
&& self.stood.record(player.map, onto)
|
||||||
|
{
|
||||||
|
self.frontiers.clear(player.map);
|
||||||
|
}
|
||||||
// Section 13's `areaVisited(kind, area)`: the errand is paid on *entering*, so the
|
// Section 13's `areaVisited(kind, area)`: the errand is paid on *entering*, so the
|
||||||
// ledger is written from the same frame that records the ground. Standing on the
|
// ledger is written from the same frame that records the ground. Standing on the
|
||||||
// building's own map is the whole test -- the fly is inside it -- and it is written
|
// building's own map is the whole test -- the fly is inside it -- and it is written
|
||||||
|
|
@ -454,7 +467,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::adapter::{MapEdge, MapExit};
|
use crate::adapter::{MapEdge, MapExit};
|
||||||
use crate::macros::NoLedger;
|
use crate::macros::NoLedger;
|
||||||
use crate::pokemon_red::fake_wram::{REDS_HOUSE_1F, Wram};
|
use crate::pokemon_red::fake_wram::{self, REDS_HOUSE_1F, WALL_TILE, Wram};
|
||||||
use crate::pokemon_red::macros::geography::Amenity;
|
use crate::pokemon_red::macros::geography::Amenity;
|
||||||
use crate::pokemon_red::maps;
|
use crate::pokemon_red::maps;
|
||||||
use crate::pokemon_red::macros::cartridge::{Edge, ExitId, MacroState};
|
use crate::pokemon_red::macros::cartridge::{Edge, ExitId, MacroState};
|
||||||
|
|
@ -468,6 +481,34 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_tile_a_step_is_landing_on_is_ground_the_run_has_covered() {
|
||||||
|
// Row 54 of `infra/docs/macros-traps.md`. `wXCoord` and `wYCoord` are the tile the step
|
||||||
|
// began on until the frame it ends, so without this the ground under the fly is unrecorded
|
||||||
|
// for fifteen frames of every sixteen: `path::frontier` keeps offering the tile the fly is
|
||||||
|
// already halfway onto, `GO FRONTIER` is dealt aiming at it, and `Arrival::Step` reports
|
||||||
|
// `done` the instant the step it did not make lands -- a macro that completes without
|
||||||
|
// changing anything, which is section 12.2's trap.
|
||||||
|
let (mut wram, blocks, blockset) = Wram::town();
|
||||||
|
let mut palette = PokemonPalette::new(7);
|
||||||
|
palette.observe(&mut wram, &NoLedger);
|
||||||
|
assert_eq!(palette.stood(), 1, "standing still, the tile under the fly and nothing else");
|
||||||
|
|
||||||
|
// Mid-step west: the coordinates still read (3, 4), the screen is already centred on
|
||||||
|
// (2, 4).
|
||||||
|
wram.mid_step(-1, 0, &blocks, &blockset);
|
||||||
|
palette.observe(&mut wram, &NoLedger);
|
||||||
|
assert_eq!(palette.stood(), 2, "and the tile the step is landing on");
|
||||||
|
|
||||||
|
// The step lands. The ledger had it already, so nothing new is recorded and the frontier
|
||||||
|
// mark is not cleared a second time.
|
||||||
|
wram.map(fake_wram::PALLET_TOWN, 10, 9, 2, 4)
|
||||||
|
.fill_screen(WALL_TILE)
|
||||||
|
.screen_from_blocks(&blocks, &blockset);
|
||||||
|
palette.observe(&mut wram, &NoLedger);
|
||||||
|
assert_eq!(palette.stood(), 2, "the tile it landed on was already ground it had covered");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_fresh_cartridge_reads_as_the_title_and_binds_nothing() {
|
fn a_fresh_cartridge_reads_as_the_title_and_binds_nothing() {
|
||||||
// All-zero WRAM: the game-timer bit is clear, which is the title screen.
|
// All-zero WRAM: the game-timer bit is clear, which is the title screen.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue