flybrain/services/flysim/crates/flybrain-gb/src/pokemon_red/macros/state.rs

709 lines
26 KiB
Rust

//! The game-state interface the macro palette is written against.
//!
//! `docs/design/macros.md` section 8 splits the work: agent A reads WRAM and agent B writes the
//! executor, and this file is the seam. It is deliberately **self-contained** — no `use`, no
//! dependency on anything else in this crate — so that it can be read, copied and compiled on its
//! own while the two halves are built in parallel.
//!
//! Three rules shaped it:
//!
//! - **No raw bytes leave.** Every accessor returns a named, typed value. A caller never sees an
//! address, a bit mask, a BCD digit or a `$ff` terminator; `docs/design/macros-wram.md` holds
//! those, one row per symbol, with the pokered evidence for each.
//! - **Every read is `&mut self`,** because reads are memoized per frame behind this trait
//! (`Emulator` caches each address for the current frame and the Pokémon adapter caches again
//! per sample). Nothing here mutates the game.
//! - **Absence is a value, not a guess.** A state the cartridge is not currently in reads as
//! `None`, and a tile whose walkability cannot be known from WRAM reads as
//! [`Walkable::Unknown`]. A macro is expected to refuse rather than to act on a guess
//! (`docs/design/macros.md` section 4: an unbound slot or a failed precondition presses
//! nothing).
//!
//! [`Scene`] lives here, rather than in `pokemon_red/scene.rs` where `macros.md` section 2 names
//! it, for exactly one reason: this file may not depend on that one, and two copies of an enum are
//! not one type. `pokemon_red::scene` re-exports it, so `pokemon_red::scene::Scene` is the path
//! the contract promises and it is this type.
/// Which of the game's interaction modes the player is in, as
/// `docs/design/macros.md` section 2 declares it.
///
/// Detection is from WRAM only, once per game frame, by `pokemon_red::scene::detect`. It is
/// deliberately conservative: a state that cannot be identified is [`Scene::Unknown`], never the
/// nearest guess, because the cost of a wrong scene is a palette of actions that do not apply.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scene {
/// Not playable yet: title, intro, naming screens. No palette; the readout's boot variant
/// applies, which is what lets Start fire.
Title,
/// The player can walk.
Overworld,
/// A text box is open and waiting.
Dialog,
/// Start menu or one of its submenus, outside battle.
Menu,
/// In a battle. `own_turn` is any battle menu waiting for input -- the top-level
/// FIGHT/PKMN/ITEM/RUN one, the move list, the party list or the bag;
/// `forced_switch` is the party list the game opens when the active Pokémon has fainted, which
/// cannot be backed out of.
Battle { own_turn: bool, forced_switch: bool },
/// A mart's buy/sell menu.
Shop,
/// A PC menu.
Pc,
/// Detection failed. Treated like [`Scene::Dialog`] (advance only).
Unknown,
}
impl Scene {
/// Whether the fly can be offered a palette at all. False for [`Scene::Title`], where the
/// readout's boot variant applies instead.
pub fn playable(self) -> bool {
!matches!(self, Scene::Title)
}
/// Short upper-case name for the screen and the feed (`game.scene`).
pub fn label(self) -> &'static str {
match self {
Scene::Title => "TITLE",
Scene::Overworld => "OVERWORLD",
Scene::Dialog => "DIALOG",
Scene::Menu => "MENU",
Scene::Battle { forced_switch: true, .. } => "BATTLE SWITCH",
Scene::Battle { own_turn: true, .. } => "BATTLE TURN",
Scene::Battle { .. } => "BATTLE",
Scene::Shop => "SHOP",
Scene::Pc => "PC",
Scene::Unknown => "UNKNOWN",
}
}
}
/// A facing, for the player and for every NPC sprite.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Facing {
Down,
Up,
Left,
Right,
}
impl Facing {
/// The tile offset this facing points at, as `(dx, dy)`.
pub fn delta(self) -> (i16, i16) {
match self {
Facing::Down => (0, 1),
Facing::Up => (0, -1),
Facing::Left => (-1, 0),
Facing::Right => (1, 0),
}
}
}
/// Where the player is standing, and which way it looks.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Player {
/// `wCurMap`.
pub map: u8,
pub x: u8,
pub y: u8,
pub facing: Facing,
}
/// The current map's size in walkable tiles.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MapSize {
pub width: u8,
pub height: u8,
}
/// A non-volatile status condition. Sleep carries its remaining turns.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
Healthy,
Sleep(u8),
Poison,
Burn,
Freeze,
Paralysis,
}
/// One move slot. An empty slot is `None` in [`Mon::moves`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Move {
/// Move id, as `constants/move_constants.asm` numbers them.
pub id: u8,
/// Remaining PP.
pub pp: u8,
/// How many PP Ups have been applied, 0 to 3.
pub pp_up: u8,
}
/// One Pokémon, whether a party member or the active battler.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mon {
/// Party slot, 0-based.
pub slot: u8,
/// Species id, as `constants/pokemon_constants.asm` numbers them — the cartridge's
/// *internal* index, not the Pokédex number. Charmander is `$b0`, not 4. (The reward
/// adapter's `wPokedexOwned` bitset is by Pokédex number; these two numberings are not the
/// same and nothing converts between them here.)
pub species: u8,
pub level: u8,
pub hp: u16,
pub max_hp: u16,
pub status: Status,
pub moves: [Option<Move>; 4],
}
impl Mon {
pub fn fainted(self) -> bool {
self.hp == 0
}
/// HP as a fraction of maximum, 0.0 for a fainted or unreadable Pokémon.
pub fn hp_fraction(self) -> f64 {
if self.max_hp == 0 {
return 0.0;
}
f64::from(self.hp) / f64::from(self.max_hp)
}
}
/// The player's party, in slot order.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Party {
pub mons: Vec<Mon>,
/// Slot of the Pokémon that is out, during a battle.
pub active: Option<u8>,
}
impl Party {
/// The healthiest Pokémon that is neither fainted nor the active one, by HP fraction, ties
/// broken by slot — `docs/design/macros.md` section 3's "healthiest".
pub fn healthiest_reserve(&self) -> Option<&Mon> {
self.mons
.iter()
.filter(|mon| !mon.fainted() && Some(mon.slot) != self.active)
.max_by(|a, b| {
a.hp_fraction()
.total_cmp(&b.hp_fraction())
.then(b.slot.cmp(&a.slot))
})
}
/// The Pokémon that is out, during a battle.
pub fn active_mon(&self) -> Option<&Mon> {
let active = self.active?;
self.mons.iter().find(|mon| mon.slot == active)
}
}
/// The opposing Pokémon.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnemyMon {
/// Species id, the internal index, as [`Mon::species`].
pub species: u8,
pub level: u8,
pub hp: u16,
pub max_hp: u16,
}
/// What kind of battle is running.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BattleKind {
/// A wild Pokémon: RUN is available.
Wild,
/// A trainer: RUN is not.
Trainer,
}
/// Which menu inside a battle is waiting for input.
///
/// The cursors are already translated out of pokered's own indexing, which differs per menu; the
/// raw geometry is in `docs/design/macros-wram.md`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BattleMenu {
/// Nothing is waiting: text, an animation, or the turn resolving.
None,
/// FIGHT / PKMN / ITEM / RUN, a 2x2 grid. `cursor` is 0 FIGHT, 1 PKMN, 2 ITEM, 3 RUN: UP and
/// DOWN move inside a column, LEFT and RIGHT change column.
Main { cursor: u8 },
/// The move list. `cursor` is the 0-based move slot when it names one.
Moves { cursor: Option<u8>, count: u8 },
/// The party list. `cursor` is the 0-based party slot.
Party { cursor: u8 },
/// The bag, opened from a battle's ITEM entry: `wListMenuID` is `ITEMLISTMENU`.
///
/// Not one of the three `docs/design/macros.md` section 12.6 named, and the gap was
/// observable twice over. First the list needed a *cursor* the scripts can read, which is what
/// `ITEM` and section 14's `THROW BALL` navigate by (2026-09-17). Then it needed to be the
/// fly's *turn*: a bag reading as nobody's turn landed on the between-turns row, whose `NEXT`
/// is the A that advances text and on an open bag is the A that uses an item (12.10).
Bag { cursor: u8, count: u8 },
}
/// Everything a battle macro needs, when a battle is running.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Battle {
pub kind: BattleKind,
/// A battle menu is open and waiting for the fly: the top-level one, the move list, the bag,
/// or the party list outside a forced switch (`pokemon_red::state::battle`). Every frame with a
/// cursor accepting input is one of these, which is section 12.10's invariant.
pub own_turn: bool,
/// The party list is open because the active Pokémon fainted; it cannot be cancelled.
pub forced_switch: bool,
pub menu: BattleMenu,
/// The Pokémon that is out. It is a copy of its party entry and it is the copy the battle
/// engine damages, so this is "own HP" in a battle.
pub own: Option<Mon>,
pub enemy: Option<EnemyMon>,
}
/// Whether a text box is on screen, and whether it is the kind that waits for a button.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextBox {
/// A text display is open: the game has loaded the dialogue font and the player cannot walk.
pub open: bool,
/// The full-width dialogue box is drawn along the bottom of the screen. The game is either
/// printing into it or waiting for A; in both cases A is the button that advances it.
pub waiting: bool,
}
/// A menu cursor, as `HandleMenuInput` keeps it. Shared by every menu in the game.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cursor {
/// Currently highlighted item, 0-based in the menu's own indexing.
pub current: u8,
/// Highest selectable index.
pub max: u8,
/// Screen row and column of the first item, which is what identifies *which* menu is up.
pub top_y: u8,
pub top_x: u8,
/// The buttons this menu reacts to, as a Game Boy pad mask. A menu that omits B cannot be
/// backed out of.
pub watched_keys: u8,
}
impl Cursor {
/// Whether B closes this menu.
pub fn cancellable(self) -> bool {
const PAD_B: u8 = 1 << 1;
self.watched_keys & PAD_B != 0
}
}
/// The start menu, when it is open.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StartMenu {
pub cursor: Cursor,
/// Number of entries: 7 once the Pokédex is in hand, 6 before.
pub items: u8,
}
/// Which screen of a mart is up.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShopScreen {
/// BUY / SELL / QUIT.
BuySellQuit,
/// The priced buy list.
Buying,
/// The bag list, for selling.
Selling,
/// The clerk is talking: the counter is open, and the thing on screen is a dialogue box
/// waiting for a press rather than a list waiting for a cursor.
///
/// Row 55 of `infra/docs/macros-traps.md`. `wListMenuID` is not cleared while the mart prints
/// its own text -- "Here you are! Thank you!" goes through `PrintText` inside
/// `DisplayPokemartDialogue_` and not through `DisplayTextIDInit` -- so the byte that says
/// "the priced buy list" outlives the list by the whole of the clerk's conversation, while the
/// cursor bytes hold a two-option box's leftovers. A purchase started on one of those frames
/// navigates a list that is not there.
Talking,
}
/// A mart, when one is open.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Shop {
pub screen: ShopScreen,
pub cursor: Cursor,
}
/// A PC, when one is open.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pc {
pub cursor: Cursor,
}
/// One stack in the bag.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BagItem {
/// Item id, as `constants/item_constants.asm` numbers them.
pub id: u8,
pub count: u8,
}
/// A visible NPC sprite on the current map.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Npc {
/// Sprite slot, 1 to 15. Slot 0 is the player and is never reported here.
pub slot: u8,
/// Picture id, which is the sprite's appearance rather than its identity.
pub picture: u8,
pub x: u8,
pub y: u8,
pub facing: Facing,
}
impl Npc {
/// Whether this sprite is a person rather than an object on the floor.
///
/// pokered's sprite list is ordered, and `FIRST_STILL_SPRITE` is where it stops being people:
/// everything from `SPRITE_POKE_BALL` (`$3d`) up is a four-tile still sprite — a ball, a
/// fossil, a boulder, a clipboard, a sleeping Snorlax — and `map_sprites.asm` uses exactly
/// this comparison to tell one from a walker (`constants/sprite_constants.asm`,
/// `engine/overworld/map_sprites.asm:101`). It is an *appearance* test, which is all a picture
/// id can carry: the sleeping gambler is furniture by this rule and the Snorlax in the road is
/// an object, and both of those are things to walk up to and press A at, which is what the
/// test is for.
pub fn person(self) -> bool {
const FIRST_STILL_SPRITE: u8 = 0x3d;
self.picture < FIRST_STILL_SPRITE
}
}
/// One sign on the current map: a `bg_event`, i.e. a tile that prints text when it is faced.
///
/// Signs, bookshelves, televisions, maps on a wall and the Pokémon-centre notice boards are all
/// this. They are not sprites and they are not walkable, so nothing about them reaches
/// [`GameState::npcs`] or [`GameState::walkable`]; the tile named here is the tile to *face*, not
/// one to stand on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sign {
pub x: u8,
pub y: u8,
/// Text id the game prints for it, which is its identity on this map and nothing more.
pub text_id: u8,
}
/// Whether a tile can be walked onto.
///
/// `Unknown` is load-bearing: the tile ids a walkability test needs live in the screen buffer, so
/// only the 10x9 window around the player can be answered at all, and only while the overworld is
/// on screen. See `docs/design/macros-wram.md`, "The walkable predicate and its window".
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Walkable {
Yes,
No,
Unknown,
}
impl Walkable {
/// True only for [`Walkable::Yes`]: an unknown tile is not a tile to path through.
pub fn is_walkable(self) -> bool {
matches!(self, Walkable::Yes)
}
}
/// Every tile of one map, answered: `docs/design/macros.md` section 15.
///
/// [`GameState::walkable`] answers about the ten-by-nine window of the screen buffer and
/// [`Walkable::Unknown`] elsewhere. This is the same predicate over the whole loaded map, decoded
/// from the block and collision tables the cartridge has loaded
/// ([`crate::pokemon_red::mapgrid`]), so a walk can be planned once instead of guessed at and
/// re-planned at every window edge.
///
/// It carries three things and no policy at all:
///
/// - the walkability of every tile, in map-tile coordinates -- the same unit the player's
/// coordinates, the warp table and the sign table are in;
/// - the tile id each answer came from, which is what the cross-check against the window
/// predicate compares and what the tile-pair rules are keyed on;
/// - **directed walls**: one step out of one tile in one direction that the cartridge refuses
/// although both tiles are passable. Pokered has two such rules and the tile-pair lists are the
/// one that can be read ahead of time; a ledge is already [`Walkable::No`] in the collision
/// list, and a person in the way is the sprite list's answer, not the ground's.
///
/// Nothing here is a fact about the run: no ledger, no visit, no target. Those stay where they
/// are, session state in the executor layer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MapGrid {
map: u8,
width: u8,
height: u8,
/// Row-major, `width * height`, [`Walkable::Unknown`] until [`MapGrid::set`] says otherwise.
tiles: Vec<Walkable>,
/// Row-major screen tile id per map tile, `None` where the tile was never decoded.
ids: Vec<Option<u8>>,
/// Row-major bitmask of the directions a step out of this tile is refused in
/// ([`MapGrid::wall`]).
walls: Vec<u8>,
}
impl MapGrid {
/// An all-[`Walkable::Unknown`] grid of this size, for a decoder to fill in.
pub fn new(map: u8, width: u8, height: u8) -> Self {
let cells = usize::from(width) * usize::from(height);
Self {
map,
width,
height,
tiles: vec![Walkable::Unknown; cells],
ids: vec![None; cells],
walls: vec![0; cells],
}
}
/// Which map this grid is of. A grid is only ever valid for the loaded map.
pub fn map(&self) -> u8 {
self.map
}
pub fn width(&self) -> u8 {
self.width
}
pub fn height(&self) -> u8 {
self.height
}
fn index(&self, x: u8, y: u8) -> Option<usize> {
(x < self.width && y < self.height)
.then(|| usize::from(y) * usize::from(self.width) + usize::from(x))
}
/// Record a decoded tile: its screen tile id and what the collision list makes of it.
pub fn set(&mut self, x: u8, y: u8, tile: u8, walkable: Walkable) {
if let Some(index) = self.index(x, y) {
self.tiles[index] = walkable;
self.ids[index] = Some(tile);
}
}
/// Record that the cartridge refuses the step out of `(x, y)` in `facing`.
pub fn wall(&mut self, x: u8, y: u8, facing: Facing) {
if let Some(index) = self.index(x, y) {
self.walls[index] |= wall_bit(facing);
}
}
/// Whether the player could stand on this tile. Off the map is [`Walkable::No`], which is the
/// window predicate's own answer for it.
pub fn walkable(&self, x: u8, y: u8) -> Walkable {
match self.index(x, y) {
None => Walkable::No,
Some(index) => self.tiles[index],
}
}
/// The screen tile id this tile's answer came from, or `None` for a tile never decoded.
pub fn tile_id(&self, x: u8, y: u8) -> Option<u8> {
self.index(x, y).and_then(|index| self.ids[index])
}
/// Whether the step out of `(x, y)` in `facing` is one the cartridge refuses.
pub fn walled(&self, x: u8, y: u8, facing: Facing) -> bool {
self.index(x, y).is_some_and(|index| self.walls[index] & wall_bit(facing) != 0)
}
/// How many tiles of the map the player could stand on.
pub fn walkable_count(&self) -> usize {
self.tiles.iter().filter(|tile| tile.is_walkable()).count()
}
/// How many tiles the map has that were never decoded, i.e. [`Walkable::Unknown`].
pub fn unknown_count(&self) -> usize {
self.tiles.iter().filter(|tile| matches!(tile, Walkable::Unknown)).count()
}
/// Every walkable tile, in row-major order, as `(x, y)`.
pub fn walkable_tiles(&self) -> Vec<(u8, u8)> {
let mut out = Vec::with_capacity(self.walkable_count());
for y in 0..self.height {
for x in 0..self.width {
if self.walkable(x, y).is_walkable() {
out.push((x, y));
}
}
}
out
}
/// How many walkable tiles a walk from `(x, y)` could reach, the directed walls respected.
///
/// The starting tile counts whether or not it is walkable, for the same reason the route
/// search treats it as passable: the fly is standing on it. What this number is *for* is
/// reading a stalled walk at a glance -- a fly on a route with 600 walkable tiles and 4
/// reachable ones is fenced in, and no amount of re-planning is going to help it
/// (`docs/design/macros.md` section 15, `examples/scene_probe.rs`).
pub fn reachable_from(&self, x: u8, y: u8) -> usize {
self.reachable(x, y).iter().filter(|seen| **seen).count()
}
/// Whether `(tx, ty)` is among the tiles [`MapGrid::reachable_from`] counts from `(x, y)`.
///
/// The whole flood at once, row-major like the grid itself, so a caller asking about several
/// tiles pays for one walk. Off the map is never reachable.
pub fn reachable(&self, x: u8, y: u8) -> Reachable {
let mut seen = vec![false; self.tiles.len()];
let Some(start) = self.index(x, y) else {
return Reachable { width: self.width, seen };
};
seen[start] = true;
let mut queue = std::collections::VecDeque::new();
queue.push_back((x, y));
while let Some((tx, ty)) = queue.pop_front() {
for facing in [Facing::Up, Facing::Down, Facing::Left, Facing::Right] {
if self.walled(tx, ty, facing) {
continue;
}
let (dx, dy) = facing.delta();
let Ok(nx) = u8::try_from(i16::from(tx) + dx) else { continue };
let Ok(ny) = u8::try_from(i16::from(ty) + dy) else { continue };
let Some(index) = self.index(nx, ny) else { continue };
if seen[index] || !self.tiles[index].is_walkable() {
continue;
}
seen[index] = true;
queue.push_back((nx, ny));
}
}
Reachable { width: self.width, seen }
}
}
/// The tiles a walk from one tile of a [`MapGrid`] could reach ([`MapGrid::reachable`]).
#[derive(Debug, Clone)]
pub struct Reachable {
width: u8,
seen: Vec<bool>,
}
impl Reachable {
/// Whether the walk reaches `(x, y)`.
pub fn contains(&self, x: u8, y: u8) -> bool {
x < self.width
&& self
.seen
.get(usize::from(y) * usize::from(self.width) + usize::from(x))
.copied()
.unwrap_or(false)
}
fn iter(&self) -> impl Iterator<Item = &bool> {
self.seen.iter()
}
}
/// Which bit of a [`MapGrid`] wall mask a direction is.
fn wall_bit(facing: Facing) -> u8 {
match facing {
Facing::Up => 1,
Facing::Down => 2,
Facing::Left => 4,
Facing::Right => 8,
}
}
/// One entry of the current map's warp table: a door, a staircase, a cave mouth.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Warp {
pub x: u8,
pub y: u8,
/// Which warp of the destination map the player arrives at.
pub destination_warp: u8,
/// Destination map id. `$ff` means "the map the player came from".
pub destination_map: u8,
}
/// Which edges of the current map lead to another map.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Connections {
pub north: bool,
pub south: bool,
pub east: bool,
pub west: bool,
}
impl Connections {
pub fn any(self) -> bool {
self.north || self.south || self.east || self.west
}
}
/// Everything the macro palette may know about the game.
///
/// One implementation reads the live cartridge (`pokemon_red::state::PokeState`); tests build
/// others from synthetic WRAM. Nothing here presses a button, chooses an action or knows what a
/// macro is.
pub trait GameState {
/// Which scene the palette should be dealt for.
fn scene(&mut self) -> Scene;
/// Where the player is, when the overworld is loaded.
fn player(&mut self) -> Option<Player>;
/// The current map's size in walkable tiles, when the overworld is loaded.
fn map_size(&mut self) -> Option<MapSize>;
/// The party, in slot order. Empty before the starter.
///
/// One frame-level caveat, measured on the cartridge: `wPartyCount` leads the party structs.
/// `AddPartyMon` writes the count first and fills the 44 bytes over the frames that follow —
/// 3,245 of them when Oak hands over the starter, because the gift is spread across the
/// script — so a member can be reported with species 0, level 0 and no HP. A caller that
/// cares should require a non-zero species, which is the check the ROM-gated test uses.
fn party(&mut self) -> Party;
/// The battle, when one is running.
fn battle(&mut self) -> Option<Battle>;
/// Whether a text box is open, and whether it waits for a button.
fn text_box(&mut self) -> TextBox;
/// The start menu, when it is open.
fn start_menu(&mut self) -> Option<StartMenu>;
/// The mart, when one is open.
fn shop(&mut self) -> Option<Shop>;
/// The PC, when one is open.
fn pc(&mut self) -> Option<Pc>;
/// Money, in whole units.
fn money(&mut self) -> u32;
/// The bag, in bag order.
fn bag(&mut self) -> Vec<BagItem>;
/// Visible NPC sprites on the current map.
///
/// Every sprite slot the map loaded, people and objects alike: an item ball on the floor and
/// the starter Pokéballs on Oak's table are `object_event`s like any villager and they occupy
/// the same sixteen slots. [`Npc::person`] is the test that separates the two.
fn npcs(&mut self) -> Vec<Npc>;
/// Sprites of the current map the cartridge is not drawing only because they are off the
/// screen (row 58, `pokemon_red::state::offscreen_npcs`).
///
/// Defaulted to none, which narrows: a seam that cannot answer knows the drawn sprites and
/// nothing more, which is what every reader had before row 58.
fn offscreen_npcs(&mut self) -> Vec<Npc> {
Vec::new()
}
/// The current map's signs, i.e. its `bg_event` text tiles.
///
/// Empty on a map with none. Required rather than defaulted like the rest of this trait: an
/// implementation that cannot answer should say "no signs" in its own words, where the reason
/// is visible, and not inherit it from here.
fn signs(&mut self) -> Vec<Sign>;
/// Whether the player could stand on this tile of the current map.
fn walkable(&mut self, x: u8, y: u8) -> Walkable;
/// The current map's warp table.
fn warps(&mut self) -> Vec<Warp>;
/// Which of the current map's edges lead somewhere.
fn connections(&mut self) -> Connections;
}