# Section 14 - Ho-Oh (Gold) and Lugia (Silver) Source: `../section-14-ho-oh-gold-and-lugia-silver.txt` Maps covered: `ECRUTEAK_TIN_TOWER_ENTRANCE`, `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE`, `TIN_TOWER_1F`, `TIN_TOWER_2F`, `TIN_TOWER_3F`, `TIN_TOWER_4F`, `TIN_TOWER_5F`, `TIN_TOWER_6F`, `TIN_TOWER_7F`, `TIN_TOWER_8F`, `TIN_TOWER_9F`, `TIN_TOWER_ROOF`, `ROUTE_41` (transit only), `WHIRL_ISLAND_NE`, `WHIRL_ISLAND_NW`, `WHIRL_ISLAND_SW`, `WHIRL_ISLAND_SE`, `WHIRL_ISLAND_CAVE`, `WHIRL_ISLAND_B1F`, `WHIRL_ISLAND_B2F`, `WHIRL_ISLAND_LUGIA_CHAMBER` Badges / key milestones in this section: no badge is awarded here. The milestones are `EVENT_FOUGHT_HO_OH` (Gold, Tin Tower Roof) and `EVENT_FOUGHT_LUGIA` (Silver, Whirl Island Lugia Chamber). Both are one-shot: the callback that spawns the legendary refuses to re-spawn it once the flag is set. Version split, straight from the asm (both legendaries exist in both carts, only the wing you own differs): - `maps/RadioTower5F.asm` after the Rocket takeover runs `checkver` / `iftrue .SilverWing`. Gold (`checkver` false) gets `verbosegiveitem RAINBOW_WING` plus `setevent EVENT_GOT_RAINBOW_WING` and `setevent EVENT_TEAM_ROCKET_DISBANDED`; Silver gets `verbosegiveitem SILVER_WING` / `setevent EVENT_GOT_SILVER_WING`. - `maps/PewterCity.asm` (`PewterCityGrampsScript`) is the mirror image and hands out the *other* wing in Kanto: Gold gets `SILVER_WING`, Silver gets `RAINBOW_WING` (and Silver's branch is the one that sets `EVENT_TEAM_ROCKET_DISBANDED` there). So in Gold this section is Tin Tower / Ho-Oh; in Silver it is Whirl Islands / Lugia; the other one is a Kanto-era return trip. --- ## 1. Route order | # | Map constant | asm file | Enter via | Leaves via | Why (walkthrough beat) | |---|---|---|---|---|---| | 1 | `ECRUTEAK_CITY` | `maps/EcruteakCity.asm` | fly / Route 42 | warp 3 at (18,11) | Gold branch starts here, "go back to Ecruteak City" | | 2 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | `maps/EcruteakTinTowerEntrance.asm` | warp 1 (4,17) / warp 2 (5,17) | warp 3 (5,3) -> in-map warp 4 (17,15); then warp 5 (17,3) | "go into the Bell Tower entry house. Talk to the man and he'll let you through" | | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | `maps/EcruteakTinTowerBackEntrance.asm` | warp 3 (2,4) | warp 1 (7,4) / warp 2 (7,5) -> `ECRUTEAK_CITY` warps 4/5 at (20,2)/(20,3) | "follow up the trail to the tower itself" | | 4 | `ECRUTEAK_CITY` (north yard) | `maps/EcruteakCity.asm` | warps 4/5 | warp 12 at (37,7) | walk east to the tower door | | 5 | `TIN_TOWER_1F` | `maps/TinTower1F.asm` | warp 1 (9,15) / warp 2 (10,15) | warp 3 (10,2) | "another bald man will let you through because you have the beautiful Rainbow Wing" | | 6 | `TIN_TOWER_2F` | `maps/TinTower2F.asm` | warp 2 (10,2) | warp 1 (10,14) | "wild Rattatas will start converging on you like zombies" | | 7 | `TIN_TOWER_3F` | `maps/TinTower3F.asm` | warp 1 (10,14) | warp 2 (16,2) | Full Heal grab | | 8 | `TIN_TOWER_4F` | `maps/TinTower4F.asm` | warp 2 (16,2) | warp 1 (2,4), warp 3 (2,14) or warp 4 (17,15) | Ultra Ball / Escape Rope hop puzzle | | 9 | `TIN_TOWER_5F` | `maps/TinTower5F.asm` | warps 2/3/4 | warp 1 (11,15) | Rare Candy | | 10 | `TIN_TOWER_6F` | `maps/TinTower6F.asm` | warp 2 (11,15) | warp 1 (3,9) | bridge floor | | 11 | `TIN_TOWER_7F` | `maps/TinTower7F.asm` | warp 1 (3,9) | warp 2 (10,15) (also in-map pair warp 3 (12,7) <-> warp 4 (8,3), and warp 5 (6,9) -> 9F) | Max Revive | | 12 | `TIN_TOWER_8F` | `maps/TinTower8F.asm` | warp 1 (2,5) | warps 2-6 -> 9F | Max Elixer / Nugget / Full Restore | | 13 | `TIN_TOWER_9F` | `maps/TinTower9F.asm` | warps 1/2/3/6/7 | warp 4 (7,9) | "go up the ladder, cross the planks down, then go up the ladder" | | 14 | `TIN_TOWER_ROOF` | `maps/TinTowerRoof.asm` | warp 1 (9,13) | warp 1 back to 9F, or Escape Rope | level 40 Ho-Oh | | 15 | `ROUTE_41` | `maps/Route41.asm` | surf south from Route 40 | warp 2 (36,19) | Silver branch: "surf southwest and west until you reach the northeast cave" | | 16 | `WHIRL_ISLAND_NE` | `maps/WhirlIslandNE.asm` | warp 1 (3,13) | warp 2 (17,3) or warp 3 (13,11) | Flash, Ultra Ball | | 17 | `WHIRL_ISLAND_B1F` | `maps/WhirlIslandB1F.asm` | warps 2/3 from NE | warp 7 (25,21) / warp 8 (13,27) to B2F | Escape Rope, Calcium, boulder | | 18 | `WHIRL_ISLAND_NW` | `maps/WhirlIslandNW.asm` | warp 2 (5,3) from B1F warp 1 | warp 1 (5,7) to Route 41, warp 3 (3,15) to SW, warp 4 (7,15) to Cave | "head out the door to breathe" | | 19 | `WHIRL_ISLAND_CAVE` | `maps/WhirlIslandCave.asm` | warp 2 (3,13) from NW | warp 1 (7,5) to B1F | short connector | | 20 | `WHIRL_ISLAND_SW` | `maps/WhirlIslandSW.asm` | warps 2/3 from B1F, warp 4 from NW | warp 5 (17,15) to B2F, warp 1 (5,7) to Route 41 | Guard Spec | | 21 | `WHIRL_ISLAND_SE` | `maps/WhirlIslandSE.asm` | warp 2 (5,3) from B1F warp 6 | warp 1 (5,13) to Route 41 | "go down out the door" | | 22 | `WHIRL_ISLAND_B2F` | `maps/WhirlIslandB2F.asm` | warps 1/2 from B1F, warp 4 from SW | warp 3 (7,25) | Max Revive, waterfall descent | | 23 | `WHIRL_ISLAND_LUGIA_CHAMBER` | `maps/WhirlIslandLugiaChamber.asm` | warp 1 (9,13) | warp 1 back to B2F, or Escape Rope | "surf north to Lugia" | Spill-over: the walkthrough's Silver branch opens on Route 41 with Swimmer Kara and the whirlpool tiles. Route 41 itself (its ten swimmers, the hidden Max Ether at (9,35), the Olivine/Cianwood crossing) belongs to the earlier Olivine -> Cianwood section; only its four island warps are treated here. The closing lines ("fly back to Blackthorn City" / "fly back to Mahogany Town") also belong to the next section. --- ## 2. Maps ### MAP_ECRUTEAK_TIN_TOWER_ENTRANCE - Script: `maps/EcruteakTinTowerEntrance.asm` (sym `52:4000 EcruteakTinTowerEntrance_MapScripts`, `52:4210 EcruteakTinTowerEntrance_MapEvents`) - Blocks: `maps/EcruteakTinTowerEntrance.blk` (sym `37:452c EcruteakTinTowerEntrance_Blocks`) - Header: `data/maps/maps.asm:165` -> `TILESET_TOWER, INDOOR, LANDMARK_ECRUTEAK_CITY, MUSIC_ECRUTEAK_CITY, phone FALSE, PALETTE_DAY, FISHGROUP_SHORE` - Dimensions: `constants/map_constants.asm:151` -> `map_const ECRUTEAK_TIN_TOWER_ENTRANCE, 10, 9` - Connections: none (`data/maps/attributes.asm:495`, border block `$00`) **Warps** (`def_warp_events`) | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 4 | 17 | `ECRUTEAK_CITY` | 3 | | 2 | 5 | 17 | `ECRUTEAK_CITY` | 3 | | 3 | 5 | 3 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 4 | | 4 | 17 | 15 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 3 | | 5 | 17 | 3 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | 3 | Warps 3 and 4 are an in-map teleport pair: the map is one walled corridor drawn in two halves, so the "trail" the walkthrough mentions is a warp, not a walk. **Coord events** (`def_coord_events`) | scene | x | y | script label | effect | |---|---|---|---|---| | `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (=0) | 4 | 7 | `EcruteakTinTowerEntranceSageBlocksLeft` (`52:400c`) | if `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_RIGHT` already set, no-op; else `applymovement SAGE2` one step LEFT, `moveobject SAGE1, 4, 6`, `appear SAGE1`, `disappear SAGE2` | | `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` (=0) | 5 | 7 | `EcruteakTinTowerEntranceSageBlocksRight` (`52:4021`) | mirror image: `applymovement SAGE1` one step RIGHT, `moveobject SAGE2, 5, 6`, `appear SAGE2`, `disappear SAGE1` | Scene ids are defined inline by the `def_scene_scripts` / `scene_script` macro (`macros/scripts/maps.asm:12-35`), so `SAGE_BLOCKS` = 0 and `NOOP` = 1. **BG events** (`def_bg_events`) None. **Object events** (`def_object_events`) | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `ECRUTEAKTINTOWERENTRANCE_SAGE1` | `SPRITE_SAGE` | 4 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` | | `ECRUTEAKTINTOWERENTRANCE_SAGE2` | `SPRITE_SAGE` | 5 | 6 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceSageScript` | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_RIGHT` | | `ECRUTEAKTINTOWERENTRANCE_SAGE3` | `SPRITE_SAGE` | 6 | 9 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceWanderingSageScript` | -1 | | `ECRUTEAKTINTOWERENTRANCE_GRAMPS` | `SPRITE_GRAMPS` | 3 | 11 | `SPRITEMOVEDATA_WANDER` (radius 1,1) | `OBJECTTYPE_SCRIPT` | `EcruteakTinTowerEntranceGrampsScript` | -1 | Object visibility semantics (`engine/overworld/map_objects_2.asm:31 CheckObjectFlag`): the object is masked (hidden) when its event flag is **set**, and `-1` means "always appear". **Scripts of interest** - `EcruteakTinTowerEntranceSageScript` (`52:4037`): `faceplayer` / `opentext` / `checkflag ENGINE_FOGBADGE`. With the badge it prints `EcruteakTinTowerEntranceSageText_GotFogBadge` ("Please, go right through"); without it, `EcruteakTinTowerEntranceSageText` ("TIN TOWER is off limits"). Important: the sage script itself never opens the path. The physical block is the pair of sage objects standing on (4,6)/(5,6) plus the two coord events at y=7 that shuffle one of them into your lane. - `EcruteakGymMortyScript` in `maps/EcruteakGym.asm:34` is what actually opens it: on receiving `ENGINE_FOGBADGE` it runs `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP`, which retargets the coord events at (4,7)/(5,7) to a scene whose script is `end`. `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` is additionally set at new-game init (`engine/events/std_scripts.asm:533`), so SAGE1 is hidden from the start and only SAGE2 exists until the coord events run. - `EcruteakTinTowerEntranceWanderingSageScript`: `checkevent EVENT_GOT_RAINBOW_WING` swaps his line to "The TIN TOWER shook! A #MON must have returned to the top!" Pure flavour, useful as a bot assertion that the wing is in hand. **Flags and events** | constant | defined in | read/written by | meaning for a bot | |---|---|---|---| | `ENGINE_FOGBADGE` | `constants/engine_flags.asm:41` | read by `EcruteakTinTowerEntranceSageScript`; set by `maps/EcruteakGym.asm` | gates the sage dialogue and (via `setmapscene`) the blocking scene | | `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS` / `_NOOP` | inline in `maps/EcruteakTinTowerEntrance.asm:9-10` | `setmapscene` from Ecruteak Gym | 0 = sages step into your path, 1 = free passage | | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_LEFT` | `constants/event_flags.asm:1288` | set by `engine/events/std_scripts.asm:533`, read by `SageBlocksRight` | when set, SAGE1 is hidden | | `EVENT_ECRUTEAK_TIN_TOWER_ENTRANCE_SAGE_RIGHT` | `constants/event_flags.asm:1289` | read by `SageBlocksLeft` | when set, SAGE2 is hidden | | `EVENT_GOT_RAINBOW_WING` | `constants/event_flags.asm:129` | read here, set in `maps/RadioTower5F.asm:129` (Gold) / `maps/PewterCity.asm:50` (Silver) | wing possession | **Items / Trainers / Wild encounters** None on this map (no `def_grass_wildmons ECRUTEAK_TIN_TOWER_ENTRANCE` entry). --- ### MAP_ECRUTEAK_TIN_TOWER_BACK_ENTRANCE - Script: `maps/EcruteakTinTowerBackEntrance.asm` - Header: `data/maps/maps.asm:166` -> `TILESET_TRADITIONAL_HOUSE, INDOOR, LANDMARK_ECRUTEAK_CITY, MUSIC_ECRUTEAK_CITY, FALSE, PALETTE_DAY, FISHGROUP_SHORE` - Dimensions: `constants/map_constants.asm:152` -> `map_const ECRUTEAK_TIN_TOWER_BACK_ENTRANCE, 4, 4` - Connections: none (`data/maps/attributes.asm:496`) **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 7 | 4 | `ECRUTEAK_CITY` | 4 | | 2 | 7 | 5 | `ECRUTEAK_CITY` | 5 | | 3 | 2 | 4 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | 5 | No coord events, bg events, object events, items, trainers or wild data. --- ### MAP_TIN_TOWER_1F - Script: `maps/TinTower1F.asm` (sym `42:4b1b TinTower1F_MapScripts`, `42:4c6a TinTower1F_MapEvents`) - Blocks: `maps/TinTower1F.blk` - Header: `data/maps/maps.asm:82` -> `TILESET_TOWER, DUNGEON, LANDMARK_TIN_TOWER, MUSIC_TIN_TOWER, FALSE, PALETTE_DAY, FISHGROUP_SHORE` - Dimensions: `constants/map_constants.asm:69` -> `map_const TIN_TOWER_1F, 10, 9` - Connections: none (`data/maps/attributes.asm:401`) **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 9 | 15 | `ECRUTEAK_CITY` | 12 | | 2 | 10 | 15 | `ECRUTEAK_CITY` | 12 | | 3 | 10 | 2 | `TIN_TOWER_2F` | 2 | **Coord events / BG events**: none. **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER1F_SAGE` | `SPRITE_SAGE` | 10 | 2 | `SPRITEMOVEDATA_STANDING_DOWN` | `OBJECTTYPE_SCRIPT` | `TinTowerSageScript` (`42:4b1d`) | `EVENT_TEAM_ROCKET_DISBANDED` | **Scripts of interest** - `TinTowerSageScript` is only `jumptextfaceplayer TinTowerSageText` ("if I had what the #MON has... A RAINBOW WING!"). The gate is geometric, not scripted: the sage object sits exactly on warp 3 at (10,2), so while he is visible the stairs are unreachable. He is hidden the moment `EVENT_TEAM_ROCKET_DISBANDED` is set, which `maps/RadioTower5F.asm:130` does one opcode after `setevent EVENT_GOT_RAINBOW_WING`. That is the whole of the walkthrough's "another bald man will let you through because you have the Rainbow Wing". **Flags and events** | constant | defined in | read/written by | meaning for a bot | |---|---|---|---| | `EVENT_TEAM_ROCKET_DISBANDED` | `constants/event_flags.asm:1283` | set by `maps/RadioTower5F.asm:130` (Gold) and `maps/PewterCity.asm:51` (Silver); read as the sage's object flag | while clear, the 1F stairs are body-blocked | **Wild encounters**: none. `data/wild/johto_grass.asm` has no `def_grass_wildmons TIN_TOWER_1F` (the block before `TIN_TOWER_2F` at line 61 is `SPROUT_TOWER_3F`). --- ### MAP_TIN_TOWER_2F - Script: `maps/TinTower2F.asm` (sym `42:4c8c`, events `42:4c8e`) - Header: `data/maps/maps.asm:83` (same tileset/music/palette as 1F) - Dimensions: `constants/map_constants.asm:70` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 10 | 14 | `TIN_TOWER_3F` | 1 | | 2 | 10 | 2 | `TIN_TOWER_1F` | 3 | No coord events, bg events or object events. **Wild encounters** (`data/wild/johto_grass.asm:61`, `def_grass_wildmons TIN_TOWER_2F`, rates `2%/2%/2%` morn/day/nite): | slot | morn | day | nite | |---|---|---|---| | 1 | L20 Rattata | L20 Rattata | L20 Gastly | | 2 | L21 Rattata | L21 Rattata | L21 Gastly | | 3 | L22 Rattata | L22 Rattata | L22 Gastly | | 4 | L22 Rattata | L22 Rattata | L22 Rattata | | 5 | L23 Rattata | L23 Rattata | L23 Rattata | | 6 | L24 Rattata | L24 Rattata | L24 Rattata | | 7 | L24 Rattata | L24 Rattata | L24 Rattata | `TIN_TOWER_3F` through `TIN_TOWER_9F` (`data/wild/johto_grass.asm:89, 117, 145, 173, 201, 229, 257`) all repeat this identical table, so the whole tower is Rattata by day and Rattata/Gastly at night at a 2% step rate. That is the walkthrough's "zombies", and a Max Repel genuinely suppresses all of it (nothing in the tower is above L24). --- ### MAP_TIN_TOWER_3F - Script: `maps/TinTower3F.asm` (sym `42:4c9e`, events `42:4ca2`) - Header: `data/maps/maps.asm:84`; Dimensions `constants/map_constants.asm:71` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 10 | 14 | `TIN_TOWER_2F` | 1 | | 2 | 16 | 2 | `TIN_TOWER_4F` | 2 | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER3F_POKE_BALL` | `SPRITE_POKE_BALL` | 3 | 14 | `SPRITEMOVEDATA_STILL` | `OBJECTTYPE_ITEMBALL` | `TinTower3FFullHeal` (`itemball FULL_HEAL`) | `EVENT_TIN_TOWER_3F_FULL_HEAL` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `FULL_HEAL` | walk onto ball at (3,14) | `TinTower3FFullHeal` | `EVENT_TIN_TOWER_3F_FULL_HEAL` (`constants/event_flags.asm:1004`) | --- ### MAP_TIN_TOWER_4F - Script: `maps/TinTower4F.asm` (sym `42:4cbf`, events `42:4cca`) - Header: `data/maps/maps.asm:85`; Dimensions `constants/map_constants.asm:72` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 2 | 4 | `TIN_TOWER_5F` | 2 | | 2 | 16 | 2 | `TIN_TOWER_3F` | 2 | | 3 | 2 | 14 | `TIN_TOWER_5F` | 3 | | 4 | 17 | 15 | `TIN_TOWER_5F` | 4 | **BG events** | x | y | type | script/item | |---|---|---|---| | 11 | 6 | `BGEVENT_ITEM` | `TinTower4FHiddenMaxPotion` (`42:4cc7`) -> `hiddenitem MAX_POTION, EVENT_TIN_TOWER_4F_HIDDEN_MAX_POTION` | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER4F_POKE_BALL1` | `SPRITE_POKE_BALL` | 14 | 10 | `STILL` | `ITEMBALL` | `TinTower4FUltraBall` (`itemball ULTRA_BALL`) | `EVENT_TIN_TOWER_4F_ULTRA_BALL` | | `TINTOWER4F_POKE_BALL2` | `SPRITE_POKE_BALL` | 17 | 14 | `STILL` | `ITEMBALL` | `TinTower4FSuperPotion` (`itemball SUPER_POTION`) | `EVENT_TIN_TOWER_4F_SUPER_POTION` | | `TINTOWER4F_POKE_BALL3` | `SPRITE_POKE_BALL` | 2 | 12 | `STILL` | `ITEMBALL` | `TinTower4FEscapeRope` (`itemball ESCAPE_ROPE`) | `EVENT_TIN_TOWER_4F_ESCAPE_ROPE` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `ULTRA_BALL` | ball at (14,10) | `TinTower4FUltraBall` | `EVENT_TIN_TOWER_4F_ULTRA_BALL` (`event_flags.asm:1005`) | | `SUPER_POTION` | ball at (17,14) | `TinTower4FSuperPotion` | `EVENT_TIN_TOWER_4F_SUPER_POTION` (`:1006`) | | `ESCAPE_ROPE` | ball at (2,12) | `TinTower4FEscapeRope` | `EVENT_TIN_TOWER_4F_ESCAPE_ROPE` (`:1007`) | | `MAX_POTION` | hidden, press A at (11,6) | bg_event -> `TinTower4FHiddenMaxPotion` | `EVENT_TIN_TOWER_4F_HIDDEN_MAX_POTION` (`:135`) | --- ### MAP_TIN_TOWER_5F - Script: `maps/TinTower5F.asm` (sym `42:4d10`, events `42:4d1a`) - Header: `data/maps/maps.asm:86`; Dimensions `constants/map_constants.asm:73` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 11 | 15 | `TIN_TOWER_6F` | 2 | | 2 | 2 | 4 | `TIN_TOWER_4F` | 1 | | 3 | 2 | 14 | `TIN_TOWER_4F` | 3 | | 4 | 17 | 15 | `TIN_TOWER_4F` | 4 | **BG events** | x | y | type | script/item | |---|---|---|---| | 16 | 14 | `BGEVENT_ITEM` | `TinTower5FHiddenFullRestore` (`42:4d14`) -> `hiddenitem FULL_RESTORE, EVENT_TIN_TOWER_5F_HIDDEN_FULL_RESTORE` | | 3 | 15 | `BGEVENT_ITEM` | `TinTower5FHiddenCarbos` (`42:4d17`) -> `hiddenitem CARBOS, EVENT_TIN_TOWER_5F_HIDDEN_CARBOS` | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER5F_POKE_BALL` | `SPRITE_POKE_BALL` | 9 | 9 | `STILL` | `ITEMBALL` | `TinTower5FRareCandy` (`itemball RARE_CANDY`) | `EVENT_TIN_TOWER_5F_RARE_CANDY` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `RARE_CANDY` | ball at (9,9) | `TinTower5FRareCandy` | `EVENT_TIN_TOWER_5F_RARE_CANDY` (`:1008`) | | `FULL_RESTORE` | hidden at (16,14) | bg_event | `EVENT_TIN_TOWER_5F_HIDDEN_FULL_RESTORE` (`:136`) | | `CARBOS` | hidden at (3,15) | bg_event | `EVENT_TIN_TOWER_5F_HIDDEN_CARBOS` (`:137`) | --- ### MAP_TIN_TOWER_6F - Script: `maps/TinTower6F.asm` (sym `42:4d4b`, events `42:4d4d`) - Header: `data/maps/maps.asm:87`; Dimensions `constants/map_constants.asm:74` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 3 | 9 | `TIN_TOWER_7F` | 1 | | 2 | 11 | 15 | `TIN_TOWER_5F` | 1 | No coord events, bg events or object events. In particular there is **no** item on this floor, contrary to the walkthrough (see section 6). --- ### MAP_TIN_TOWER_7F - Script: `maps/TinTower7F.asm` (sym `42:4d5d`, events `42:4d61`) - Header: `data/maps/maps.asm:88`; Dimensions `constants/map_constants.asm:75` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 3 | 9 | `TIN_TOWER_6F` | 1 | | 2 | 10 | 15 | `TIN_TOWER_8F` | 1 | | 3 | 12 | 7 | `TIN_TOWER_7F` | 4 | | 4 | 8 | 3 | `TIN_TOWER_7F` | 3 | | 5 | 6 | 9 | `TIN_TOWER_9F` | 5 | Warps 3/4 are another in-map teleport pair (the walkthrough's "go through the next two warps"). **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER7F_POKE_BALL` | `SPRITE_POKE_BALL` | 16 | 1 | `STILL` | `ITEMBALL` | `TinTower7FMaxRevive` (`itemball MAX_REVIVE`) | `EVENT_TIN_TOWER_7F_MAX_REVIVE` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `MAX_REVIVE` | ball at (16,1) | `TinTower7FMaxRevive` | `EVENT_TIN_TOWER_7F_MAX_REVIVE` (`:1009`) | --- ### MAP_TIN_TOWER_8F - Script: `maps/TinTower8F.asm` (sym `42:4d8d`, events `42:4d95`) - Header: `data/maps/maps.asm:89`; Dimensions `constants/map_constants.asm:76` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 2 | 5 | `TIN_TOWER_7F` | 2 | | 2 | 2 | 11 | `TIN_TOWER_9F` | 1 | | 3 | 16 | 7 | `TIN_TOWER_9F` | 2 | | 4 | 10 | 3 | `TIN_TOWER_9F` | 3 | | 5 | 14 | 15 | `TIN_TOWER_9F` | 6 | | 6 | 6 | 9 | `TIN_TOWER_9F` | 7 | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWER8F_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 13 | `STILL` | `ITEMBALL` | `TinTower8FNugget` (`itemball NUGGET`) | `EVENT_TIN_TOWER_8F_NUGGET` | | `TINTOWER8F_POKE_BALL2` | `SPRITE_POKE_BALL` | 11 | 6 | `STILL` | `ITEMBALL` | `TinTower8FMaxElixer` (`itemball MAX_ELIXER`) | `EVENT_TIN_TOWER_8F_MAX_ELIXER` | | `TINTOWER8F_POKE_BALL3` | `SPRITE_POKE_BALL` | 3 | 1 | `STILL` | `ITEMBALL` | `TinTower8FFullRestore` (`itemball FULL_RESTORE`) | `EVENT_TIN_TOWER_8F_FULL_RESTORE` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `NUGGET` | ball at (7,13) | `TinTower8FNugget` | `EVENT_TIN_TOWER_8F_NUGGET` (`:1010`) | | `MAX_ELIXER` | ball at (11,6) | `TinTower8FMaxElixer` | `EVENT_TIN_TOWER_8F_MAX_ELIXER` (`:1011`) | | `FULL_RESTORE` | ball at (3,1) | `TinTower8FFullRestore` | `EVENT_TIN_TOWER_8F_FULL_RESTORE` (`:1012`) | --- ### MAP_TIN_TOWER_9F - Script: `maps/TinTower9F.asm` (sym `42:4de0`, events `42:4e03`) - Header: `data/maps/maps.asm:90`; Dimensions `constants/map_constants.asm:77` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 12 | 3 | `TIN_TOWER_8F` | 2 | | 2 | 2 | 5 | `TIN_TOWER_8F` | 3 | | 3 | 12 | 7 | `TIN_TOWER_8F` | 4 | | 4 | 7 | 9 | `TIN_TOWER_ROOF` | 1 | | 5 | 16 | 7 | `TIN_TOWER_7F` | 5 | | 6 | 6 | 13 | `TIN_TOWER_8F` | 5 | | 7 | 8 | 13 | `TIN_TOWER_8F` | 6 | No coord events, bg events or object events. The file carries two unreferenced strings, `TinTower9FUnusedHoOhText` (`42:4de2`) and `TinTower9FUnusedLugiaText` (`42:4df3`), leftovers from when the legendary stood on 9F. --- ### MAP_TIN_TOWER_ROOF - Script: `maps/TinTowerRoof.asm` (sym `5b:68fa TinTowerRoof_MapScripts`, `5b:6945 TinTowerRoof_MapEvents`) - Blocks: `maps/TinTowerRoof.blk` (sym `2b:62b9 TinTowerRoof_Blocks`) - Header: `data/maps/maps.asm:344` -> `TILESET_TOWER, ROUTE, LANDMARK_TIN_TOWER, MUSIC_TIN_TOWER, FALSE, PALETTE_AUTO, FISHGROUP_SHORE` - Dimensions: `constants/map_constants.asm:318` -> `map_const TIN_TOWER_ROOF, 10, 9`. Note it lives in `newgroup FAST_SHIP` (group 15, map 12), **not** in the `newgroup DUNGEONS` block that holds `TIN_TOWER_1F..9F` (group 3, maps 4-12). A bot resolving map ids by group must not assume the roof follows 9F. - Connections: none (`data/maps/attributes.asm:597`) **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 9 | 13 | `TIN_TOWER_9F` | 4 | **Coord events / BG events**: none. **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `TINTOWERROOF_HO_OH` | `SPRITE_HO_OH` | 9 | 5 | `SPRITEMOVEDATA_POKEMON`, palette `PAL_NPC_RED` | `OBJECTTYPE_SCRIPT` | `TinTowerHoOh` (`5b:6913`) | `EVENT_TIN_TOWER_ROOF_HO_OH` | **Scripts of interest** - `TinTowerRoofHoOhCallback` (`5b:68ff`), registered as `callback MAPCALLBACK_OBJECTS`: ``` checkevent EVENT_FOUGHT_HO_OH iftrue .NoAppear checkitem RAINBOW_WING iftrue .Appear sjump .NoAppear ``` So Ho-Oh spawns only while the `RAINBOW_WING` is **in the bag** and `EVENT_FOUGHT_HO_OH` is clear. A bot that stores the wing in the PC, or that has already triggered the encounter once, will find an empty roof. - `TinTowerHoOh` (`5b:6913`): `faceplayer` / `opentext` / `writetext HoOhText` ("Shaoooh!") / `cry HO_OH` / `pause 15` / `closetext` / `setevent EVENT_FOUGHT_HO_OH` / `checkver` / `iftrue .Silver`. - Gold branch: `loadvar VAR_BATTLETYPE, BATTLETYPE_FORCEITEM`, `loadwildmon HO_OH, 40`, `startbattle`, `disappear TINTOWERROOF_HO_OH`, `reloadmapafterbattle`. - `.Silver` (`5b:6930`): identical but `loadwildmon HO_OH, 70`. Note that `setevent EVENT_FOUGHT_HO_OH` fires **before** the battle, so fleeing or blacking out still burns the encounter. This is exactly why the walkthrough insists on saving first: the only recovery is a reset. **Flags and events** | constant | defined in | read/written by | meaning for a bot | |---|---|---|---| | `EVENT_FOUGHT_HO_OH` | `constants/event_flags.asm:454` | read by the callback, set by `TinTowerHoOh` before `startbattle` | one-shot latch on the encounter | | `EVENT_TIN_TOWER_ROOF_HO_OH` | `constants/event_flags.asm:1246` | the object's own visibility flag, toggled by `appear`/`disappear` in the callback | runtime spawn state | | `RAINBOW_WING` (item, not a flag) | `constants/item_constants.asm` | `checkitem` in the callback | must be carried in the bag | | `BATTLETYPE_FORCEITEM` | `constants/battle_constants.asm:101` | `loadvar VAR_BATTLETYPE`; consumed at `engine/battle/core.asm:5771` | guarantees Ho-Oh holds its `SACRED_ASH` | **Battle data** - `data/pokemon/base_stats/ho_oh.asm`: 106/130/90/90/110/154, FIRE/FLYING, **catch rate 3**, base exp 220, `db SACRED_ASH, SACRED_ASH ; items`, `GENDER_UNKNOWN`, `GROWTH_SLOW`. - Level-up set (`data/pokemon/evos_attacks.asm:3324 HoOhEvosAttacks`): 1 Sacred Fire, 11 Safeguard, 22 Gust, 33 Recover, 44 Fire Blast, 55 Sunny Day, 66 Swift, 77 Whirlwind, 88 AncientPower, 99 Future Sight. A **level 40** Ho-Oh therefore knows **Sacred Fire, Safeguard, Gust, Recover**. - Battle music: `engine/battle/start_battle.asm` has no legendary case, so this is `MUSIC_JOHTO_WILD_BATTLE` (or `..._NIGHT`). The music the walkthrough is praising is the map's own `MUSIC_TIN_TOWER`. **Wild encounters**: none on the roof. --- ### MAP_ROUTE_41 (transit only) - Script: `maps/Route41.asm` - Header: `data/maps/maps.asm` (Route 41 row); attributes `data/maps/attributes.asm:227` -> border `$35`, `connection north, Route40, ROUTE_40, 15`, `connection west, CianwoodCity, CIANWOOD_CITY, 0` - Dimensions: `constants/map_constants.asm:410` -> `map_const ROUTE_41, 25, 27` **Warps** (`def_warp_events`) - the four island doors | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 12 | 17 | `WHIRL_ISLAND_NW` | 1 | | 2 | 36 | 19 | `WHIRL_ISLAND_NE` | 1 | | 3 | 12 | 37 | `WHIRL_ISLAND_SW` | 1 | | 4 | 36 | 45 | `WHIRL_ISLAND_SE` | 1 | **BG events** | x | y | type | script/item | |---|---|---|---| | 9 | 35 | `BGEVENT_ITEM` | `Route41HiddenMaxEther` -> `hiddenitem MAX_ETHER, EVENT_ROUTE_41_HIDDEN_MAX_ETHER` | **Object events** (all `OBJECTTYPE_TRAINER`; sight range is the numeric column) | const | sprite | x | y | movement | sight | script label | |---|---|---|---|---|---|---| | `ROUTE41_OLIVINE_RIVAL1` | `SPRITE_OLIVINE_RIVAL` | 32 | 6 | `SPINRANDOM_FAST` | 3 | `TrainerSwimmermCharlie` | | `ROUTE41_OLIVINE_RIVAL2` | `SPRITE_OLIVINE_RIVAL` | 46 | 8 | `SPINRANDOM_FAST` | 3 | `TrainerSwimmermGeorge` | | `ROUTE41_OLIVINE_RIVAL3` | `SPRITE_OLIVINE_RIVAL` | 20 | 26 | `SPINCOUNTERCLOCKWISE` | 3 | `TrainerSwimmermBerke` | | `ROUTE41_OLIVINE_RIVAL4` | `SPRITE_OLIVINE_RIVAL` | 32 | 30 | `SPINCLOCKWISE` | 3 | `TrainerSwimmermKirk` | | `ROUTE41_OLIVINE_RIVAL5` | `SPRITE_OLIVINE_RIVAL` | 19 | 46 | `SPINCOUNTERCLOCKWISE` | 3 | `TrainerSwimmermMathew` | | `ROUTE41_SWIMMER_GIRL1` | `SPRITE_SWIMMER_GIRL` | 17 | 4 | `SPINRANDOM_FAST` | 3 | `TrainerSwimmerfKaylee` | | `ROUTE41_SWIMMER_GIRL2` | `SPRITE_SWIMMER_GIRL` | 23 | 19 | `STANDING_UP` | 3 | `TrainerSwimmerfSusie` | | `ROUTE41_SWIMMER_GIRL3` | `SPRITE_SWIMMER_GIRL` | 27 | 34 | `STANDING_LEFT` | 3 | `TrainerSwimmerfDenise` | | `ROUTE41_SWIMMER_GIRL4` | `SPRITE_SWIMMER_GIRL` | 44 | 28 | `STANDING_RIGHT` | 4 | `TrainerSwimmerfKara` | | `ROUTE41_SWIMMER_GIRL5` | `SPRITE_SWIMMER_GIRL` | 9 | 50 | `SPINRANDOM_FAST` | 2 | `TrainerSwimmerfWendy` | **Trainers** (the one the walkthrough uses as a landmark) | const | class | id | party (`data/trainers/parties.asm`) | script label | rematch/phone | |---|---|---|---|---|---| | `SWIMMERF, KARA` | `SWIMMERF` (class 27, `constants/trainer_constants.asm:375`) | `KARA` (`:381`, 6th in class) | `SwimmerFGroup` entry 6 (`data/trainers/parties.asm:1811`): `TRAINERTYPE_NORMAL`, L20 STARYU, L20 STARMIE | `TrainerSwimmerfKara`, flag `EVENT_BEAT_SWIMMERF_KARA` | none | "The whirlpool is just northwest of where you fought Swimmer Kara" therefore means: northwest of Route 41 (44,28), i.e. the whirlpool guarding warp 2 at (36,19) into `WHIRL_ISLAND_NE`. **Wild encounters**: `data/wild/johto_water.asm:170` `def_water_wildmons ROUTE_41`, 6% encounter rate; Gold slots are L20 Tentacool / L20 Tentacruel / L20 Mantine (Silver's third slot differs). Fish group is `FISHGROUP_WHIRL_ISLANDS` only inside the islands, not on Route 41 itself. --- ### MAP_WHIRL_ISLAND_NE - Script: `maps/WhirlIslandNE.asm` (sym `47:401c WhirlIslandNE_MapScripts`, `47:4020 WhirlIslandNE_MapEvents`) - Blocks: `maps/WhirlIslandNE.blk` - Header: `data/maps/maps.asm:137` -> `TILESET_DARK_CAVE, CAVE, LANDMARK_WHIRL_ISLANDS, MUSIC_UNION_CAVE, phone TRUE, PALETTE_DARK, FISHGROUP_WHIRL_ISLANDS` - Dimensions: `constants/map_constants.asm:124` -> `map_const WHIRL_ISLAND_NE, 10, 9` - Connections: none; border block `$09` (`data/maps/attributes.asm:456`) **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 3 | 13 | `ROUTE_41` | 2 | | 2 | 17 | 3 | `WHIRL_ISLAND_B1F` | 2 | | 3 | 13 | 11 | `WHIRL_ISLAND_B1F` | 3 | **Coord events / BG events**: none. **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `WHIRLISLANDNE_POKE_BALL` | `SPRITE_POKE_BALL` | 11 | 11 | `STILL` | `ITEMBALL` | `WhirlIslandNEUltraBall` (`itemball ULTRA_BALL`) | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `ULTRA_BALL` | ball at (11,11) | `WhirlIslandNEUltraBall` | `EVENT_WHIRL_ISLAND_NE_ULTRA_BALL` (`event_flags.asm:1072`) | **Wild encounters** (`data/wild/johto_grass.asm:1101`, 6%/6%/6%, identical morn/day/nite): L22 Krabby, L23 Zubat, L24 Krabby, L22 Seel, L23 Golbat, L24 Seel, L24 Seel. `WHIRL_ISLAND_NW` (`:1073`), `SW` (`:1129`), `CAVE` (`:1157`) and `SE` (`:1185`) use the exact same table. --- ### MAP_WHIRL_ISLAND_NW - Script: `maps/WhirlIslandNW.asm` (sym `47:4000`, events `47:4002`) - Header: `data/maps/maps.asm:136` (same as NE) - Dimensions: `constants/map_constants.asm:123` -> `5, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 5 | 7 | `ROUTE_41` | 1 | | 2 | 5 | 3 | `WHIRL_ISLAND_B1F` | 1 | | 3 | 3 | 15 | `WHIRL_ISLAND_SW` | 4 | | 4 | 7 | 15 | `WHIRL_ISLAND_CAVE` | 2 | No coord events, bg events, object events or items. **Wild encounters**: `data/wild/johto_grass.asm:1073`, same L22-24 Krabby/Zubat/Seel/Golbat table as NE. --- ### MAP_WHIRL_ISLAND_CAVE - Script: `maps/WhirlIslandCave.asm` (sym `47:4072`, events `47:4074`) - Header: `data/maps/maps.asm:139` - Dimensions: `constants/map_constants.asm:126` -> `5, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 7 | 5 | `WHIRL_ISLAND_B1F` | 9 | | 2 | 3 | 13 | `WHIRL_ISLAND_NW` | 4 | No coord events, bg events, object events or items. Wild table `data/wild/johto_grass.asm:1157`, same as NE. --- ### MAP_WHIRL_ISLAND_SW - Script: `maps/WhirlIslandSW.asm` (sym `47:4042`, events `47:4046`) - Header: `data/maps/maps.asm:138` - Dimensions: `constants/map_constants.asm:125` -> `10, 9` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 5 | 7 | `ROUTE_41` | 3 | | 2 | 17 | 3 | `WHIRL_ISLAND_B1F` | 5 | | 3 | 3 | 3 | `WHIRL_ISLAND_B1F` | 4 | | 4 | 3 | 15 | `WHIRL_ISLAND_NW` | 3 | | 5 | 17 | 15 | `WHIRL_ISLAND_B2F` | 4 | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `WHIRLISLANDSW_POKE_BALL` | `SPRITE_POKE_BALL` | 15 | 2 | `STILL` | `ITEMBALL` | `WhirlIslandSWGuardSpec` (`itemball GUARD_SPEC`) | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `GUARD_SPEC` | ball at (15,2) | `WhirlIslandSWGuardSpec` | `EVENT_WHIRL_ISLAND_SW_GUARD_SPEC` (`:1073`) | **Wild encounters**: grass `data/wild/johto_grass.asm:1129` (as NE); water `data/wild/johto_water.asm:79` `def_water_wildmons WHIRL_ISLAND_SW`, 4% rate, L20 Tentacool / L15 Horsea / L20 Tentacruel. --- ### MAP_WHIRL_ISLAND_SE - Script: `maps/WhirlIslandSE.asm` (sym `47:4084`, events `47:4086`) - Header: `data/maps/maps.asm:140` - Dimensions: `constants/map_constants.asm:127` -> `5, 9`; border block `$0f` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 5 | 13 | `ROUTE_41` | 4 | | 2 | 5 | 3 | `WHIRL_ISLAND_B1F` | 6 | No coord events, bg events, object events or items. Wild table `data/wild/johto_grass.asm:1185`, same as NE. --- ### MAP_WHIRL_ISLAND_B1F - Script: `maps/WhirlIslandB1F.asm` (sym `47:4096 WhirlIslandB1F_MapScripts`, `47:40ae WhirlIslandB1F_MapEvents`) - Blocks: `maps/WhirlIslandB1F.blk` - Header: `data/maps/maps.asm:141` -> `TILESET_DARK_CAVE, CAVE, LANDMARK_WHIRL_ISLANDS, MUSIC_UNION_CAVE, TRUE, PALETTE_DARK, FISHGROUP_WHIRL_ISLANDS` - Dimensions: `constants/map_constants.asm:128` -> `map_const WHIRL_ISLAND_B1F, 20, 18` (the big one) - Connections: none; border block `$09` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 5 | 5 | `WHIRL_ISLAND_NW` | 2 | | 2 | 35 | 3 | `WHIRL_ISLAND_NE` | 2 | | 3 | 29 | 9 | `WHIRL_ISLAND_NE` | 3 | | 4 | 9 | 31 | `WHIRL_ISLAND_SW` | 3 | | 5 | 23 | 31 | `WHIRL_ISLAND_SW` | 2 | | 6 | 31 | 29 | `WHIRL_ISLAND_SE` | 2 | | 7 | 25 | 21 | `WHIRL_ISLAND_B2F` | 1 | | 8 | 13 | 27 | `WHIRL_ISLAND_B2F` | 2 | | 9 | 17 | 21 | `WHIRL_ISLAND_CAVE` | 1 | **Coord events**: none. **BG events** | x | y | type | script/item | |---|---|---|---| | 30 | 4 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenRareCandy` (`47:40a5`) -> `hiddenitem RARE_CANDY, EVENT_WHIRL_ISLAND_B1F_HIDDEN_RARE_CANDY` | | 36 | 18 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenUltraBall` (`47:40a8`) -> `hiddenitem ULTRA_BALL, EVENT_WHIRL_ISLAND_B1F_HIDDEN_ULTRA_BALL` | | 2 | 23 | `BGEVENT_ITEM` | `WhirlIslandB1FHiddenFullRestore` (`47:40ab`) -> `hiddenitem FULL_RESTORE, EVENT_WHIRL_ISLAND_B1F_HIDDEN_FULL_RESTORE` | **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `WHIRLISLANDB1F_POKE_BALL1` | `SPRITE_POKE_BALL` | 7 | 13 | `STILL` | `ITEMBALL` | `WhirlIslandB1FFullRestore` | `EVENT_WHIRL_ISLAND_B1F_FULL_RESTORE` | | `WHIRLISLANDB1F_POKE_BALL2` | `SPRITE_POKE_BALL` | 2 | 18 | `STILL` | `ITEMBALL` | `WhirlIslandB1FCarbos` | `EVENT_WHIRL_ISLAND_B1F_CARBOS` | | `WHIRLISLANDB1F_POKE_BALL3` | `SPRITE_POKE_BALL` | 33 | 23 | `STILL` | `ITEMBALL` | `WhirlIslandB1FCalcium` | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` | | `WHIRLISLANDB1F_POKE_BALL4` | `SPRITE_POKE_BALL` | 17 | 8 | `STILL` | `ITEMBALL` | `WhirlIslandB1FNugget` | `EVENT_WHIRL_ISLAND_B1F_NUGGET` | | `WHIRLISLANDB1F_POKE_BALL5` | `SPRITE_POKE_BALL` | 19 | 26 | `STILL` | `ITEMBALL` | `WhirlIslandB1FEscapeRope` | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` | | `WHIRLISLANDB1F_BOULDER` | `SPRITE_BOULDER` | 23 | 26 | `SPRITEMOVEDATA_STRENGTH_BOULDER` | `OBJECTTYPE_SCRIPT` | `WhirlIslandB1FBoulder` (`47:40a2`, `jumpstd StrengthBoulderScript`) | -1 | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `FULL_RESTORE` | ball at (7,13) | `WhirlIslandB1FFullRestore` | `EVENT_WHIRL_ISLAND_B1F_FULL_RESTORE` (`:1074`) | | `CARBOS` | ball at (2,18) | `WhirlIslandB1FCarbos` | `EVENT_WHIRL_ISLAND_B1F_CARBOS` (`:1075`) | | `CALCIUM` | ball at (33,23) | `WhirlIslandB1FCalcium` | `EVENT_WHIRL_ISLAND_B1F_CALCIUM` (`:1076`) | | `NUGGET` | ball at (17,8) | `WhirlIslandB1FNugget` | `EVENT_WHIRL_ISLAND_B1F_NUGGET` (`:1077`) | | `ESCAPE_ROPE` | ball at (19,26) | `WhirlIslandB1FEscapeRope` | `EVENT_WHIRL_ISLAND_B1F_ESCAPE_ROPE` (`:1078`) | | `RARE_CANDY` | hidden at (30,4) | bg_event | `EVENT_WHIRL_ISLAND_B1F_HIDDEN_RARE_CANDY` (`:161`) | | `ULTRA_BALL` | hidden at (36,18) | bg_event | `EVENT_WHIRL_ISLAND_B1F_HIDDEN_ULTRA_BALL` (`:162`) | | `FULL_RESTORE` | hidden at (2,23) | bg_event | `EVENT_WHIRL_ISLAND_B1F_HIDDEN_FULL_RESTORE` (`:163`) | **Wild encounters**: `data/wild/johto_grass.asm:1213`, 6%: L23 Krabby, L24 Zubat, L25 Krabby, L23 Seel, L24 Golbat, L25 Seel, L25 Seel (no morn/day/nite split). No water table for B1F. --- ### MAP_WHIRL_ISLAND_B2F - Script: `maps/WhirlIslandB2F.asm` (sym `47:413e WhirlIslandB2F_MapScripts`, `47:4146 WhirlIslandB2F_MapEvents`) - Blocks: `maps/WhirlIslandB2F.blk` - Header: `data/maps/maps.asm:142` (same as B1F) - Dimensions: `constants/map_constants.asm:129` -> `map_const WHIRL_ISLAND_B2F, 10, 18` - Connections: none; border block `$2e` (`data/maps/attributes.asm:461`) **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 11 | 5 | `WHIRL_ISLAND_B1F` | 7 | | 2 | 7 | 11 | `WHIRL_ISLAND_B1F` | 8 | | 3 | 7 | 25 | `WHIRL_ISLAND_LUGIA_CHAMBER` | 1 | | 4 | 13 | 31 | `WHIRL_ISLAND_SW` | 5 | **Coord events / BG events**: none. **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `WHIRLISLANDB2F_POKE_BALL1` | `SPRITE_POKE_BALL` | 10 | 11 | `STILL` | `ITEMBALL` | `WhirlIslandB2FFullRestore` | `EVENT_WHIRL_ISLAND_B2F_FULL_RESTORE` | | `WHIRLISLANDB2F_POKE_BALL2` | `SPRITE_POKE_BALL` | 6 | 4 | `STILL` | `ITEMBALL` | `WhirlIslandB2FMaxRevive` | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` | | `WHIRLISLANDB2F_POKE_BALL3` | `SPRITE_POKE_BALL` | 5 | 12 | `STILL` | `ITEMBALL` | `WhirlIslandB2FMaxElixer` | `EVENT_WHIRL_ISLAND_B2F_MAX_ELIXER` | **Items** | item | how obtained | source | one-time flag | |---|---|---|---| | `FULL_RESTORE` | ball at (10,11) | `WhirlIslandB2FFullRestore` | `EVENT_WHIRL_ISLAND_B2F_FULL_RESTORE` (`:1079`) | | `MAX_REVIVE` | ball at (6,4) | `WhirlIslandB2FMaxRevive` | `EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE` (`:1080`) | | `MAX_ELIXER` | ball at (5,12) | `WhirlIslandB2FMaxElixer` | `EVENT_WHIRL_ISLAND_B2F_MAX_ELIXER` (`:1081`) | **Wild encounters**: grass `data/wild/johto_grass.asm:1241` (L23-25 Krabby/Zubat/Seel/Golbat, as B1F); water `data/wild/johto_water.asm:86`, 4%: L15 Horsea / L20 Horsea / L20 Tentacruel. The waterfall the walkthrough rides down is a `COLL_WATERFALL` ($33) tile in `maps/WhirlIslandB2F.blk`, not a warp row: `engine/overworld/player_movement.asm` `.CheckTile` / `.water_table` forces `DOWN` on that collision, so descending needs no HM and no badge. Climbing back up does (`WaterfallFunction.TryWaterfall`, `ENGINE_RISINGBADGE`). --- ### MAP_WHIRL_ISLAND_LUGIA_CHAMBER - Script: `maps/WhirlIslandLugiaChamber.asm` (sym `47:4187 WhirlIslandLugiaChamber_MapScripts`, `47:41d1 WhirlIslandLugiaChamber_MapEvents`) - Blocks: `maps/WhirlIslandLugiaChamber.blk` (sym `2b:717d WhirlIslandLugiaChamber_Blocks`) - Header: `data/maps/maps.asm:143` -> `TILESET_DARK_CAVE, CAVE, LANDMARK_WHIRL_ISLANDS, MUSIC_UNION_CAVE, TRUE, PALETTE_DARK, FISHGROUP_WHIRL_ISLANDS` - Dimensions: `constants/map_constants.asm:130` -> `map_const WHIRL_ISLAND_LUGIA_CHAMBER, 10, 9` - Connections: none; border block `$0f` **Warps** | idx | x | y | destination map | dest warp | |---|---|---|---|---| | 1 | 9 | 13 | `WHIRL_ISLAND_B2F` | 3 | **Coord events / BG events**: none. **Object events** | const | sprite | x | y | movement | type | script label | event flag | |---|---|---|---|---|---|---|---| | `WHIRLISLANDLUGIACHAMBER_LUGIA` | `SPRITE_LUGIA` | 9 | 5 | `SPRITEMOVEDATA_POKEMON`, palette `PAL_NPC_BLUE` | `OBJECTTYPE_SCRIPT` | `Lugia` (`47:41a0`) | `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` | **Scripts of interest** - `WhirlIslandLugiaChamberLugiaCallback` (`47:418c`), `callback MAPCALLBACK_OBJECTS`: `checkevent EVENT_FOUGHT_LUGIA` / `iftrue .NoAppear` / `checkitem SILVER_WING` / `iftrue .Appear` / `sjump .NoAppear`. Same shape as Ho-Oh's: the `SILVER_WING` must be in the bag. - `Lugia` (`47:41a0`): `faceplayer` / `opentext` / `writetext LugiaText` ("Gyaaas!") / `cry LUGIA` / `pause 15` / `closetext` / `setevent EVENT_FOUGHT_LUGIA` / `checkver` / `iftrue .Silver`. - Gold branch: `BATTLETYPE_FORCEITEM`, `loadwildmon LUGIA, 70`. - `.Silver` branch: `BATTLETYPE_FORCEITEM`, `loadwildmon LUGIA, 40`. Note the level pairing is the inverse of Ho-Oh's: the legendary you meet *early* in your own version is level 40, the cross-version one is level 70. Again `setevent` precedes `startbattle`, so the encounter is spent the moment the text closes. **Flags and events** | constant | defined in | read/written by | meaning for a bot | |---|---|---|---| | `EVENT_FOUGHT_LUGIA` | `constants/event_flags.asm:455` | read by the callback, set by `Lugia` before `startbattle` | one-shot latch | | `EVENT_WHIRL_ISLAND_LUGIA_CHAMBER_LUGIA` | `constants/event_flags.asm:1247` | object visibility, toggled by `appear`/`disappear` | runtime spawn state | | `SILVER_WING` (item) | `constants/item_constants.asm` | `checkitem` in the callback | must be carried | | `EVENT_GOT_SILVER_WING` | `constants/event_flags.asm:130` | set in `maps/RadioTower5F.asm:139` (Silver) / `maps/PewterCity.asm:34` (Gold) | wing acquisition | **Battle data** - `data/pokemon/base_stats/lugia.asm`: 106/90/130/110/90/154, PSYCHIC/FLYING, **catch rate 3**, base exp 220, `db NO_ITEM, NO_ITEM ; items` (so `BATTLETYPE_FORCEITEM` forces nothing here), `GENDER_UNKNOWN`, `GROWTH_SLOW`. - Level-up set (`data/pokemon/evos_attacks.asm:3310 LugiaEvosAttacks`): 1 Aeroblast, 11 Safeguard, 22 Gust, 33 Recover, 44 Hydro Pump, 55 Rain Dance, 66 Swift, 77 Whirlwind, 88 AncientPower, 99 Future Sight. A **level 40** Lugia (Silver) knows **Aeroblast, Safeguard, Gust, Recover**. **Wild encounters**: grass `data/wild/johto_grass.asm:1269`, 6%: L24 Krabby, L25 Zubat, L26 Krabby, L24 Seel, L25 Golbat, L26 Seel, L26 Seel. Water `data/wild/johto_water.asm:93`, 4%: L20 Horsea / L20 Tentacruel / L20 Seadra (the Seadra the walkthrough lists). **Fishing** (any Whirl Islands map, `FISHGROUP_WHIRL_ISLANDS`, `data/wild/fish.asm:21` / `:162`): | rod | slots | |---|---| | Old (`.WhirlIslands_Old`) | Magikarp L10 x2, Krabby L10 | | Good (`.WhirlIslands_Good`) | Magikarp L20, Krabby L20 x2, `time_group 18` | | Super (`.WhirlIslands_Super`) | Krabby L40, `time_group 19`, Kingler L40, Seadra L40 | --- ## 3. Blockers and gates | Gate | Enforced by (file:label) | Requirement | Unlock condition | |---|---|---|---| | Tin Tower path (Ecruteak entrance house) | `maps/EcruteakTinTowerEntrance.asm` coord events (4,7)/(5,7) under `SCENE_ECRUTEAKTINTOWERENTRANCE_SAGE_BLOCKS`; sage objects on (4,6)/(5,6) | Fog Badge | `maps/EcruteakGym.asm:34` runs `setmapscene ECRUTEAK_TIN_TOWER_ENTRANCE, SCENE_ECRUTEAKTINTOWERENTRANCE_NOOP` when `ENGINE_FOGBADGE` is granted | | Tin Tower stairs (1F) | `maps/TinTower1F.asm:56` - the `TINTOWER1F_SAGE` object literally occupies warp 3 at (10,2) | `EVENT_TEAM_ROCKET_DISBANDED` clear = sage present | `maps/RadioTower5F.asm:130` sets it right after `verbosegiveitem RAINBOW_WING` | | Ho-Oh spawn | `maps/TinTowerRoof.asm:TinTowerRoofHoOhCallback` | `RAINBOW_WING` in bag AND `EVENT_FOUGHT_HO_OH` clear | Gold: Radio Tower director. Silver: Pewter City gramps | | Lugia spawn | `maps/WhirlIslandLugiaChamber.asm:WhirlIslandLugiaChamberLugiaCallback` | `SILVER_WING` in bag AND `EVENT_FOUGHT_LUGIA` clear | Silver: Radio Tower director. Gold: Pewter City gramps | | Reaching Route 41 at all | `engine/events/overworld.asm:322 SurfFunction`, `.TrySurf` at `:339` (`ld de, ENGINE_FOGBADGE / call CheckBadge`); overworld path `:490` | SURF in party + `ENGINE_FOGBADGE` | beat Morty | | Whirlpools around the islands | `engine/events/overworld.asm:1061 WhirlpoolFunction .TryWhirlpool` and `:1167 TryWhirlpoolOW` (`ld de, ENGINE_GLACIERBADGE / call CheckBadge` / `CheckEngineFlag`) | WHIRLPOOL in party + `ENGINE_GLACIERBADGE` (badge **7**, Pryce) | beat Pryce in Mahogany. Refusal line is `Script_MightyWhirlpool` | | Darkness inside every Whirl Islands map | `engine/events/overworld.asm:271 FlashFunction .CheckUseFlash`: `ld de, ENGINE_ZEPHYRBADGE / farcall CheckBadge`, then `ld a, [wTimeOfDayPalset] / cp DARKNESS_PALSET` | FLASH in party + `ENGINE_ZEPHYRBADGE`; map header must be `PALETTE_DARK` (`data/maps/maps.asm:136-143`) | Falkner's badge. FLASH is refused outright on a lit map | | Whirl Island B1F boulder | `maps/WhirlIslandB1F.asm:WhirlIslandB1FBoulder` -> `jumpstd StrengthBoulderScript` | STRENGTH usable | Olivine Cafe / Plain Badge chain (`ENGINE_PLAINBADGE`, `engine/events/overworld.asm:934 StrengthFunction`, badge check at `:941`) | | Climbing back up the B2F waterfall | `engine/events/overworld.asm:611 WaterfallFunction .TryWaterfall` (`:618`) / `:683 TryWaterfallOW` (`:687`) (`ENGINE_RISINGBADGE`) | WATERFALL + badge 8 | Clair. Descending needs neither (`player_movement.asm` `.water_table` forces DOWN on `COLL_WATERFALL`) | Nothing in this section gates on `VAR_BADGES` count, so "you'll need eight badges for Whirlpool" is not what the code says (see section 6). --- ## 4. Bot checklist Preconditions for the whole section: `ENGINE_FOGBADGE` set (Surf + Tin Tower entrance), and for the Silver branch also `ENGINE_ZEPHYRBADGE` (Flash), `ENGINE_GLACIERBADGE` (Whirlpool), `ENGINE_PLAINBADGE` (Strength). A party member with SURF, one with FLASH, one with WHIRLPOOL, one with STRENGTH. Stock about 30 `ULTRA_BALL` plus `REVIVE`/`HYPER_POTION`. **Gold: Tin Tower / Ho-Oh** | # | Map | Target | Intent | Precondition | Postcondition | |---|---|---|---|---|---| | 1 | `ECRUTEAK_CITY` | warp 3 (18,11) | walk | `ENGINE_FOGBADGE`, `EVENT_GOT_RAINBOW_WING` | in `ECRUTEAK_TIN_TOWER_ENTRANCE` | | 2 | `ECRUTEAK_TIN_TOWER_ENTRANCE` | (4,7) or (5,7) | walk north | map scene == `_NOOP` (1) | no sage cutscene fires | | 3 | same | warp 3 (5,3) | walk | - | teleported to (17,15) | | 4 | same | warp 5 (17,3) | walk | - | in `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | | 5 | `ECRUTEAK_TIN_TOWER_BACK_ENTRANCE` | warp 1 (7,4) | walk south | - | back on `ECRUTEAK_CITY` at (20,2) | | 6 | `ECRUTEAK_CITY` | warp 12 (37,7) | walk east | - | in `TIN_TOWER_1F` | | 7 | `TIN_TOWER_1F` | warp 3 (10,2) | walk | `EVENT_TEAM_ROCKET_DISBANDED` set (sage gone) | `TIN_TOWER_2F` | | 8 | `TIN_TOWER_2F` | warp 1 (10,14) | walk (Max Repel active) | - | `TIN_TOWER_3F` | | 9 | `TIN_TOWER_3F` | ball (3,14) | walk onto | `EVENT_TIN_TOWER_3F_FULL_HEAL` clear | `FULL_HEAL`, flag set | | 10 | `TIN_TOWER_3F` | warp 2 (16,2) | walk | - | `TIN_TOWER_4F` | | 11 | `TIN_TOWER_4F` | balls (14,10), (17,14), (2,12); hidden (11,6) | walk on / press A | respective flags clear | `ULTRA_BALL`, `SUPER_POTION`, `ESCAPE_ROPE`, `MAX_POTION` | | 12 | `TIN_TOWER_4F` | warp 1 (2,4) | walk | - | `TIN_TOWER_5F` | | 13 | `TIN_TOWER_5F` | ball (9,9); hidden (16,14), (3,15) | walk on / press A | flags clear | `RARE_CANDY`, `FULL_RESTORE`, `CARBOS` | | 14 | `TIN_TOWER_5F` | warp 1 (11,15) | walk | - | `TIN_TOWER_6F` | | 15 | `TIN_TOWER_6F` | warp 1 (3,9) | walk | - | `TIN_TOWER_7F` | | 16 | `TIN_TOWER_7F` | ball (16,1) | walk on | flag clear | `MAX_REVIVE` | | 17 | `TIN_TOWER_7F` | warp 2 (10,15) | walk | - | `TIN_TOWER_8F` at (2,5) | | 18 | `TIN_TOWER_8F` | balls (7,13), (11,6), (3,1) | walk on | flags clear | `NUGGET`, `MAX_ELIXER`, `FULL_RESTORE` | | 19 | `TIN_TOWER_8F` | any of warps 2-6 | walk | - | `TIN_TOWER_9F` | | 20 | `TIN_TOWER_9F` | warp 4 (7,9) | walk | - | `TIN_TOWER_ROOF` at (9,13) | | 21 | `TIN_TOWER_ROOF` | - | **save** | - | restore point before the one-shot | | 22 | `TIN_TOWER_ROOF` | `TINTOWERROOF_HO_OH` at (9,5) | talk (face north from (9,6)) | `RAINBOW_WING` in bag, `EVENT_FOUGHT_HO_OH` clear | `EVENT_FOUGHT_HO_OH` set, wild L40 Ho-Oh battle, holds `SACRED_ASH` | | 23 | battle | throw `ULTRA_BALL` | weaken to red, prefer sleep/freeze | see catch math below | caught or reset to step 21 | | 24 | `TIN_TOWER_ROOF` | `ESCAPE_ROPE` | use item | tileset is `TILESET_TOWER` | back outside | **Silver: Whirl Islands / Lugia** | # | Map | Target | Intent | Precondition | Postcondition | |---|---|---|---|---|---| | 1 | `OLIVINE_CITY` | - | fly | `ENGINE_FLYPOINT_OLIVINE` | at Olivine | | 2 | `ROUTE_40`/`ROUTE_41` | south then west | surf | SURF + `ENGINE_FOGBADGE` | on Route 41 | | 3 | `ROUTE_41` | whirlpool tile NW of (44,28) | press A -> `TryWhirlpoolOW` | WHIRLPOOL + `ENGINE_GLACIERBADGE` | whirlpool block replaced (`DisappearWhirlpool`) | | 4 | `ROUTE_41` | warp 2 (36,19) | walk/surf onto | - | `WHIRL_ISLAND_NE` at (3,13) | | 5 | `WHIRL_ISLAND_NE` | - | use FLASH | FLASH + `ENGINE_ZEPHYRBADGE`, map is `PALETTE_DARK` | cave lit | | 6 | `WHIRL_ISLAND_NE` | ball (11,11) | walk on | flag clear | `ULTRA_BALL` | | 7 | `WHIRL_ISLAND_NE` | warp 2 (17,3) or warp 3 (13,11) | walk | - | `WHIRL_ISLAND_B1F` at (35,3)/(29,9) | | 8 | `WHIRL_ISLAND_B1F` | balls (17,8), (33,23), (19,26), (7,13), (2,18); hidden (30,4), (36,18), (2,23) | walk on / press A | flags clear | Nugget, Calcium, Escape Rope, Full Restore, Carbos, Rare Candy, Ultra Ball, Full Restore | | 9 | `WHIRL_ISLAND_B1F` | boulder at (23,26) | push with STRENGTH | STRENGTH enabled | path opened | | 10 | `WHIRL_ISLAND_SW` (via B1F warp 4/5) | ball (15,2) | walk on | flag clear | `GUARD_SPEC` | | 11 | `WHIRL_ISLAND_B1F` | warp 8 (13,27) | walk | - | `WHIRL_ISLAND_B2F` at (7,11) | | 12 | `WHIRL_ISLAND_B2F` | balls (6,4), (10,11), (5,12) | walk on | flags clear | `MAX_REVIVE`, `FULL_RESTORE`, `MAX_ELIXER` | | 13 | `WHIRL_ISLAND_B2F` | surf south onto the `COLL_WATERFALL` tile | walk down | SURF only, no badge needed | carried to the lower pool | | 14 | `WHIRL_ISLAND_B2F` | warp 3 (7,25) | walk | - | `WHIRL_ISLAND_LUGIA_CHAMBER` at (9,13) | | 15 | `WHIRL_ISLAND_LUGIA_CHAMBER` | - | **save** | - | restore point | | 16 | same | surf north to face `WHIRLISLANDLUGIACHAMBER_LUGIA` at (9,5) | talk | `SILVER_WING` in bag, `EVENT_FOUGHT_LUGIA` clear | `EVENT_FOUGHT_LUGIA` set, wild L40 Lugia battle | | 17 | battle | throw `ULTRA_BALL` or `HEAVY_BALL` | weaken, prefer sleep/freeze | see catch math | caught or reset to step 15 | | 18 | same | `ESCAPE_ROPE` | use item | `TILESET_DARK_CAVE` | back outside | **Catch math a bot can plan against** (`engine/items/item_effects.asm PokeBallEffect`): - Both legendaries have base catch rate 3. - `UltraBallMultiplier` doubles it -> 6. - Status bonus is added after the HP term, but only sleep and freeze actually apply: the `and` that tests for them leaves burn/poison/paralysis at 0 (a documented cart bug the port reproduces, see `src/battle/gen2/Catching.lua`). So paralysis is worth nothing here; sleep or freeze is worth +10, which is larger than the entire ball-adjusted rate. - `HeavyBallMultiplier` works off the Pokedex weight word, converted in-place to tenths of a kilogram. Lugia is `dw 1701, 4760` (476.0 lb -> 215.8 kg), which lands in the 204.8-307.2 kg bucket for **+20**, giving 23 versus the Ultra Ball's 6. Ho-Oh is `dw 1206, 4390` (439.0 lb -> 199.0 kg), which is above the 102.4 kg light threshold but under 204.8 kg, so its bucket boost is **+0**: Heavy Ball leaves Ho-Oh at 3 and the Ultra Ball is strictly better. --- ## 5. Port coverage | Beat | Port file | Status | |---|---|---| | Map headers / dimensions / warps / coord events / bg events / object events for every map here | `src/import/RomExtractorGen2.lua:804-862, :974` (generic per-map-group walk, `MAP_GROUP_COUNT = 26`) | implemented (data-driven, nothing map-specific needed) | | `MAPCALLBACK_OBJECTS` (what spawns Ho-Oh / Lugia) | `src/world/gen2/World.lua:5694-5700`, `:5983` | implemented | | `checkitem` / `appear` / `disappear` / `checkevent` used by both legendary callbacks | `src/script/gen2/Vm.lua:315, :323, :523`; `src/script/gen2/Opcodes.lua:0x21/0x6d/0x6e` | implemented | | `checkver` version split (L40 vs L70) | `src/script/gen2/Vm.lua:774-778` (the comment there cites `WhirlIslandLugiaChamber` by name) | implemented | | `loadwildmon` + `startbattle` legendary encounter | `src/script/gen2/Vm.lua:817, :836-856` | implemented | | `BATTLETYPE_FORCEITEM` forcing Ho-Oh's `SACRED_ASH` | `src/world/gen2/World.lua:103-110, :4586` | implemented | | `cry` / `pause` / `faceplayer` / `reloadmapafterbattle` in the encounter scripts | `src/script/gen2/Vm.lua:621, :886`; `src/script/gen2/Opcodes.lua:0x83, 0x8a, 0x6a, 0x5f` | implemented | | Item balls (`OBJECTTYPE_ITEMBALL`) and hidden items (`BGEVENT_ITEM`) | `src/world/gen2/HiddenItems.lua`; `src/world/gen2/World.lua:1373, :5147-5280` | implemented | | Whirlpool field move + `ENGINE_GLACIERBADGE` gate + block replacement | `src/world/gen2/FieldMoves.lua:109, :219, :247, :540-556, :626-641`; `src/world/gen2/Permissions.lua:129 isWhirlpool` | implemented | | Flash + `ENGINE_ZEPHYRBADGE` + `PALETTE_DARK` / `DARKNESS_PALSET` | `src/world/gen2/FieldMoves.lua:105, :465-474`; `src/world/gen2/Palettes.lua:75-103` | implemented | | Surf / Fly / Waterfall badge gates | `src/world/gen2/FieldMoves.lua:101-109` (`SURF = FOG`, `FLY = STORM`, `WATERFALL` via `World.lua:4219`) | implemented | | Strength boulder (`jumpstd StrengthBoulderScript`, Whirl Island B1F) | `src/world/gen2/World.lua:4071-4088, :4234` | implemented | | Scene scripts / `setmapscene` (the Ecruteak sage block) | `src/script/gen2/Opcodes.lua:0x12 setmapscene`; `src/world/gen2/World.lua:5013` (coord event scan) | implemented | | **Forced-tile movement (`COLL_WATERFALL` riding down, `COLL_CURRENT_*`)** | no implementation found: `Permissions.lua:135` defines `isWaterfall` but nothing mirrors `player_movement.asm` `.CheckTile` / `.water_table`; a grep for `HI_NYBBLE_CURRENT` / forced direction over `src/world/gen2/*.lua` returns only `Bike.forcedDirection` | **missing** - the Whirl Island B2F waterfall descent to the Lugia Chamber has no forced-down step | | **Apricorn ball multipliers (Heavy / Level / Lure / Fast / Moon / Love / Friend)** | `src/battle/gen2/Catching.lua:28-43` sets all of them to a flat `1`, with a comment that they "key off conditions the caller supplies"; no caller supplies them (grep for `HEAVY_BALL` outside that table finds nothing) | **missing** - the Heavy Ball advantage on Lugia is not reproduced, and the three documented ball bugs are not modelled either way | | Ultra/Great ball multipliers and the two catch-rate cart bugs | `src/battle/gen2/Catching.lua:1-48, :60-80` | implemented (bugs deliberately preserved, `fixBugs` opt-out) | | Headless driver for either legendary | `tests/drivers/gold_*.lua` has `gold_roamers.lua`, `gold_icepath_boulder.lua`, `gold_battle_smoke.lua` etc., but **no** Tin Tower or Whirl Islands driver | **missing** - nothing exercises the wing-in-bag callback path end to end | --- ## 6. Unresolved / verify by hand 1. **"You'll need eight badges in order to use Whirlpool."** The asm disagrees: `engine/events/overworld.asm:1077` (`WhirlpoolFunction.TryWhirlpool`) and `:1171` (`TryWhirlpoolOW`) both check `ENGINE_GLACIERBADGE`, which is badge 7 (Pryce, Mahogany), not badge 8. Nothing reads `VAR_BADGES` for Whirlpool. 2. **Ho-Oh's moves.** The walkthrough says "Safeguard and Ancient Power (damage w/ stat boosts), Punishment, and Sacred Fire". `HoOhEvosAttacks` (`data/pokemon/evos_attacks.asm:3324`) gives a level 40 Ho-Oh **Sacred Fire, Safeguard, Gust, Recover**. AncientPower is learned at 88, and Punishment does not exist in Generation 2 (`constants/move_constants.asm` has no such constant). This reads like a Heart Gold / Soul Silver moveset. 3. **Tin Tower item list.** The walkthrough calls out a **PP Up** on the Ultra Ball floor, a **Max Potion then a Full Heal** on the bridge floor, and an **HP Up** near the end. The asm has, in those positions, `SUPER_POTION` (`TinTower4F.asm`, (17,14)), nothing at all on `TIN_TOWER_6F` (the file has an empty `def_object_events` and no bg events), and `NUGGET` / `FULL_RESTORE` on `TIN_TOWER_8F`. The `MAX_POTION` that does exist is the **hidden** item at (11,6) on 4F. No `PP_UP` or `HP_UP` appears in any `TinTower*.asm`. Treat the walkthrough's floor-by-floor item order as unreliable and use the tables in section 2. 4. **Whirl Islands item list.** The walkthrough lists "Max Revive x2"; the asm has exactly one `MAX_REVIVE`, on B2F at (6,4) (`EVENT_WHIRL_ISLAND_B2F_MAX_REVIVE`). It also omits the B1F Full Restore, Carbos and Nugget, the three B1F hidden items, and the B2F Full Restore and Max Elixer, all of which are real. 5. **Hop / ledge / bridge routing.** Every "jump left x5, down, right x2" instruction in the walkthrough is block-layout geometry that lives in the `.blk` files (`maps/TinTower4F.blk` .. `TinTower9F.blk`, `maps/WhirlIslandB1F.blk`, `WhirlIslandB2F.blk`), not in any asm text row. I did not decode the block data, so those step sequences are unverified. The warp tables in section 2 are the verified part; a bot should path to warp coordinates and let a collision-aware pathfinder handle the ledges. 6. **Whirlpool tile coordinates on Route 41.** "The whirlpool is just northwest of where you fought Swimmer Kara" could not be pinned to an asm row: the whirlpool tiles are `COLL_WHIRLPOOL` ($24) entries inside `maps/Route41.blk`, with no coordinate list anywhere in `maps/Route41.asm`. Kara's own position, (44,28), is verified. 7. **"The Ho-Oh in-game music."** There is no dedicated legendary battle theme in Gold/Silver: `engine/battle/start_battle.asm` selects `MUSIC_JOHTO_WILD_BATTLE` / `..._NIGHT` for a wild encounter and has no case for Ho-Oh or Lugia. The distinctive track is the map music `MUSIC_TIN_TOWER` (`data/maps/maps.asm:344`), which stops when the battle starts. Whirl Islands play `MUSIC_UNION_CAVE` (`:136-143`), so Lugia gets no special music at all. 8. **"About a 2% catch rate at low health."** I did not re-derive the shake check, only the rate formula; the numeric claim is unverified. The inputs are verified: base catch rate 3, Ultra Ball x2, sleep/freeze +10, and the two reproduced cart bugs in `PokeBallEffect`. 9. **Thard_Verad's Apriball note** checks out against the source, with one correction worth recording. `FastBallMultiplier` (`engine/items/item_effects.asm:986 FastBallMultiplier`) is documented in-file as buggy: it only matches the **first three** entries of `FleeMons`, which `data/wild/flee_mons.asm:4` shows are `MAGNEMITE`, `GRIMER`, `TANGELA` - so the correct species list is those three, and Raikou/Entei/Suicune (in `AlwaysFleeMons`) indeed get nothing. `LoveBallMultiplier` (`:921`) is commented "for the intended effect, this should be `ret z`", i.e. same gender rather than opposite. `MoonBallMultiplier` (`:875`) compares against `MOON_STONE_RED`, which is `BURN_HEAL` in Gen 2, so it never fires. `LevelBallMultiplier`, `LureBallMultiplier`, `HeavyBallMultiplier` and the Friend Ball happiness path (`:559`, `:622`) carry no bug comments.