diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/fake_wram.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/fake_wram.rs index 02e58ac..96c4724 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/fake_wram.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/fake_wram.rs @@ -404,7 +404,9 @@ impl Wram { for x in 0..width * 2 { let Some(block) = blocks.get((y / 2) * width + (x / 2)) else { continue }; let Some(tiles) = blockset.get(usize::from(*block)) else { continue }; - let tile = tiles[(y % 2) * 2 * 4 + (x % 2) * 2]; + // The lower-left tile of the map tile's own quadrant, which is the one the + // cartridge's collision read uses (`mapgrid::ANCHOR_ROW`). + let tile = tiles[((y % 2) * 2 + 1) * 4 + (x % 2) * 2]; self.map_tile(x as u8, y as u8, tile); } } diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs index a812615..75db101 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs @@ -49,6 +49,22 @@ pub const MAP_BORDER: usize = 3; /// which is why `wCurMapWidth` in blocks is `map_size().width` in tiles divided by this. pub const TILES_PER_BLOCK: u8 = 2; +/// Which of a quadrant's two rows the collision read takes its tile id from: the lower one. +/// +/// A map tile is a 2x2 patch of screen tiles and only one of the four is ever asked about, because +/// `CheckTilePassable` matches a single tile id. Which one is **measured, not derived**: the +/// decoded ids were compared against the screen buffer on the cartridge, tile by tile, and the +/// screen agrees with the lower-left tile of each quadrant and not the upper-left -- Viridian +/// Forest's (4, 32) reads `$23`, the second row of its block, where the first row holds `$04` +/// (`tests/rom_map_grid.rs`, which is the test that pins it). +/// +/// That is the same corner `_GetTileAndCoordsInFrontOfPlayer` reads at screen `(8, 9)` for the +/// tile the player is standing on: the view is aligned so that the player's own 2x2 begins on +/// screen row 8, so row 9 is its lower half. An upper-left decode still answers, and answers +/// plausibly -- on the open ground of a town most quadrants hold one tile id four times over -- +/// which is why the cross-check in [`super::state::map_grid`] is not optional. +const ANCHOR_ROW: usize = 1; + /// Tileset ids the tile-pair lists name (`constants/tileset_constants.asm`, counted in the order /// that file declares them: OVERWORLD 0 … FOREST 3 … CAVERN 17). pub mod tileset { @@ -143,10 +159,10 @@ pub fn decode(map: u8, width_blocks: u8, height_blocks: u8, blocks: &[u8], tiles let Some(block) = blocks.get(block_index).copied() else { continue; }; - // The top left screen tile of the map tile's own 2x2 quadrant of the block, which is - // the corner every collision read in the game uses. + // The screen tile the collision read uses, inside the map tile's own 2x2 quadrant of + // the block: the **lower** left one ([`ANCHOR_ROW`]). let column = usize::from(x % TILES_PER_BLOCK) * usize::from(TILES_PER_BLOCK); - let row = usize::from(y % TILES_PER_BLOCK) * usize::from(TILES_PER_BLOCK); + let row = usize::from(y % TILES_PER_BLOCK) * usize::from(TILES_PER_BLOCK) + ANCHOR_ROW; let Some(tile) = tiles.tile(block, column, row) else { continue; }; diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid/tests.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid/tests.rs index 455ca32..da7c6c7 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid/tests.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid/tests.rs @@ -16,11 +16,13 @@ use crate::pokemon_red::macros::state::{Facing, Walkable}; fn blockset() -> Vec { let floor = [FLOOR; 16]; let wall = [WALL; 16]; + // The four ids sit on the rows the decode reads -- the *lower* row of each 2x2 quadrant + // (`ANCHOR_ROW`) -- and the rows it does not read hold ids that would be wrong answers. let quadrants = [ - NORTH_WEST, 0x90, NORTH_EAST, 0x91, // - 0x92, 0x93, 0x94, 0x95, // - SOUTH_WEST, 0x96, SOUTH_EAST, 0x97, // - 0x98, 0x99, 0x9a, 0x9b, + 0x90, 0x91, 0x92, 0x93, // + NORTH_WEST, 0x94, NORTH_EAST, 0x95, // + 0x96, 0x97, 0x98, 0x99, // + SOUTH_WEST, 0x9a, SOUTH_EAST, 0x9b, ]; let mut out = Vec::new(); out.extend_from_slice(&floor);